npcsh 1.1.5__py3-none-any.whl → 1.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. npcsh/_state.py +483 -336
  2. npcsh/npc_team/jinxs/code/sh.jinx +0 -1
  3. npcsh/npc_team/jinxs/code/sql.jinx +1 -3
  4. npcsh/npc_team/jinxs/utils/npc-studio.jinx +33 -38
  5. npcsh/npc_team/jinxs/utils/ots.jinx +34 -65
  6. npcsh/npc_team/jinxs/utils/search.jinx +130 -0
  7. npcsh/npc_team/jinxs/utils/vixynt.jinx +33 -45
  8. npcsh/routes.py +32 -14
  9. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/npc-studio.jinx +33 -38
  10. npcsh-1.1.7.data/data/npcsh/npc_team/ots.jinx +61 -0
  11. npcsh-1.1.7.data/data/npcsh/npc_team/search.jinx +130 -0
  12. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/sh.jinx +0 -1
  13. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/sql.jinx +1 -3
  14. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/vixynt.jinx +33 -45
  15. {npcsh-1.1.5.dist-info → npcsh-1.1.7.dist-info}/METADATA +1 -10
  16. {npcsh-1.1.5.dist-info → npcsh-1.1.7.dist-info}/RECORD +65 -73
  17. npcsh/npc_team/jinxs/utils/search/brainblast.jinx +0 -51
  18. npcsh/npc_team/jinxs/utils/search/kg_search.jinx +0 -43
  19. npcsh/npc_team/jinxs/utils/search/memory_search.jinx +0 -36
  20. npcsh/npc_team/jinxs/utils/search/rag.jinx +0 -70
  21. npcsh/npc_team/jinxs/utils/search/search.jinx +0 -192
  22. npcsh-1.1.5.data/data/npcsh/npc_team/brainblast.jinx +0 -51
  23. npcsh-1.1.5.data/data/npcsh/npc_team/kg_search.jinx +0 -43
  24. npcsh-1.1.5.data/data/npcsh/npc_team/memory_search.jinx +0 -36
  25. npcsh-1.1.5.data/data/npcsh/npc_team/ots.jinx +0 -92
  26. npcsh-1.1.5.data/data/npcsh/npc_team/rag.jinx +0 -70
  27. npcsh-1.1.5.data/data/npcsh/npc_team/search.jinx +0 -192
  28. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/alicanto.jinx +0 -0
  29. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  30. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/alicanto.png +0 -0
  31. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/breathe.jinx +0 -0
  32. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/build.jinx +0 -0
  33. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/compile.jinx +0 -0
  34. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/corca.jinx +0 -0
  35. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/corca.npc +0 -0
  36. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/corca.png +0 -0
  37. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/corca_example.png +0 -0
  38. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  39. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/flush.jinx +0 -0
  40. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/foreman.npc +0 -0
  41. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/frederic.npc +0 -0
  42. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/frederic4.png +0 -0
  43. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/guac.jinx +0 -0
  44. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/guac.png +0 -0
  45. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/help.jinx +0 -0
  46. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/init.jinx +0 -0
  47. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
  48. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  49. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  50. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  51. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  52. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/plan.jinx +0 -0
  53. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/plonk.jinx +0 -0
  54. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/plonk.npc +0 -0
  55. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/plonk.png +0 -0
  56. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  57. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  58. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/pti.jinx +0 -0
  59. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/python.jinx +0 -0
  60. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/roll.jinx +0 -0
  61. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/sample.jinx +0 -0
  62. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/serve.jinx +0 -0
  63. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/set.jinx +0 -0
  64. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  65. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/sibiji.png +0 -0
  66. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  67. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/spool.jinx +0 -0
  68. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/spool.png +0 -0
  69. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  70. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/wander.jinx +0 -0
  71. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/yap.jinx +0 -0
  72. {npcsh-1.1.5.data → npcsh-1.1.7.data}/data/npcsh/npc_team/yap.png +0 -0
  73. {npcsh-1.1.5.dist-info → npcsh-1.1.7.dist-info}/WHEEL +0 -0
  74. {npcsh-1.1.5.dist-info → npcsh-1.1.7.dist-info}/entry_points.txt +0 -0
  75. {npcsh-1.1.5.dist-info → npcsh-1.1.7.dist-info}/licenses/LICENSE +0 -0
  76. {npcsh-1.1.5.dist-info → npcsh-1.1.7.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: "" # Any additional arguments to pass to the npc studio launch.
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
- def ensure_repo():
18
- """Clone or update the npc-studio repo."""
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
- def install_dependencies():
33
- """Install npm and pip dependencies."""
34
- subprocess.check_call(["npm", "install"], cwd=NPC_STUDIO_DIR)
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([sys.executable, "-m", "pip", "install", "-r", str(req_file)])
39
-
40
- def launch_npc_studio(path_to_open: str = None):
41
- """
42
- Launch the NPC Studio backend + frontend.
43
- Returns PIDs for processes.
44
- """
45
- ensure_repo()
46
- install_dependencies()
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
- shell=False
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
@@ -2,7 +2,6 @@ jinx_name: sh
2
2
  description: Execute bash queries. Should be used to grep for file contents, list directories, explore information to answer user questions more practically.
3
3
  inputs:
4
4
  - bash_command
5
- - user_request
6
5
  steps:
7
6
  - engine: python
8
7
  code: |
@@ -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. Returns a LLM summary so no post-summary is required
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 # Required, the text description for image generation or editing.
5
- - output_file_base: "" # Optional string, e.g., './my_image', used as base for output file name.
6
- - attachments: "" # Optional string, comma-separated image paths for editing existing images.
7
- - n_images: 1 # Optional integer, number of images to generate.
8
- - height: 1024 # Optional integer, height of the generated image.
9
- - width: 1024 # Optional integer, width of the generated image.
10
- - model: "" # Optional string, specific model to use for image generation. Defaults to NPC's model or 'runwayml/stable-diffusion-v1-5'.
11
- - provider: "" # Optional string, specific provider for image generation. Defaults to NPC's provider or 'diffusers'.
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 (Jinx execution environment passes them here)
23
+ # Extract inputs from context
24
24
  image_prompt = context.get('prompt', '').strip()
25
- output_file_base = context.get('output_file_base')
25
+ output_name = context.get('output_name')
26
26
  attachments_str = context.get('attachments')
27
- n_images = int(context.get('n_images', 1)) # Ensure it's an integer
28
- height = int(context.get('height', 1024)) # Ensure it's an integer
29
- width = int(context.get('width', 1024)) # Ensure it's an integer
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 if not explicitly provided in jinx inputs
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 if still not set
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'] = "Usage: /vixynt <prompt> [--output_file path] [--attachments path] [--n_images num]"
52
+ context['output'] = "Error: No prompt provided for image generation."
54
53
  context['messages'] = output_messages
55
- exit() # Exit the jinx execution early
54
+ exit()
56
55
 
57
56
  try:
58
- # Generate image(s) or edit
59
- images_list = gen_image(
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 output_file_base and output_file_base.strip():
82
- base_name, ext = os.path.splitext(os.path.expanduser(output_file_base))
83
- if not ext: # No extension provided, default to .png
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
- html_images = ""
114
- for img_b64 in compressed_images:
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 # Store output in context
127
- context['messages'] = output_messages # Ensure messages are returned
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.5
3
+ Version: 1.1.7
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