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
@@ -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,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
@@ -1,13 +1,10 @@
1
1
  jinx_name: "ots"
2
- description: "Take screenshot and analyze with vision model"
2
+ description: "Take screenshot and analyze with vision model. Usage: /ots <prompt>"
3
3
  inputs:
4
- - image_paths_args: "" # Optional comma-separated paths to image files for analysis.
5
- - prompt: "" # The prompt for the LLM about the image(s).
6
- - vmodel: "" # Vision model to use. Defaults to NPCSH_VISION_MODEL or NPC's model.
7
- - vprovider: "" # Vision model provider. Defaults to NPCSH_VISION_PROVIDER or NPC's provider.
8
- - stream: False # Whether to stream the output from the LLM.
9
- - api_url: "" # API URL for the LLM.
10
- - api_key: "" # API key for the LLM.
4
+ - prompt
5
+ - image_paths_args: ""
6
+ - vmodel: ""
7
+ - vprovider: ""
11
8
  steps:
12
9
  - name: "analyze_screenshot_or_image"
13
10
  engine: "python"
@@ -16,77 +13,49 @@ steps:
16
13
  import traceback
17
14
  from npcpy.llm_funcs import get_llm_response
18
15
  from npcpy.data.image import capture_screenshot
19
- # Assuming NPCSH_VISION_MODEL and NPCSH_VISION_PROVIDER are accessible through _state or defaults
20
- # For simplicity in Jinx, we'll use fallbacks or assume context will provide
21
16
 
22
- image_paths_args_str = context.get('image_paths_args')
23
- user_prompt = context.get('prompt')
24
- vision_model = context.get('vmodel')
25
- vision_provider = context.get('vprovider')
26
- stream_output = context.get('stream')
27
- api_url = context.get('api_url')
28
- api_key = context.get('api_key')
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 ""
29
24
  output_messages = context.get('messages', [])
30
25
  current_npc = context.get('npc')
31
26
 
32
27
  image_paths = []
33
- if image_paths_args_str and image_paths_args_str.strip():
28
+ if image_paths_args_str.strip():
34
29
  for img_path_arg in image_paths_args_str.split(','):
35
30
  full_path = os.path.abspath(os.path.expanduser(img_path_arg.strip()))
36
31
  if os.path.exists(full_path):
37
32
  image_paths.append(full_path)
38
- else:
39
- context['output'] = f"Error: Image file not found at {full_path}"
40
- context['messages'] = output_messages
41
- exit()
42
33
 
43
34
  if not image_paths:
44
35
  screenshot_info = capture_screenshot(full=False)
45
36
  if screenshot_info and "file_path" in screenshot_info:
46
37
  image_paths.append(screenshot_info["file_path"])
47
- print(f"Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
48
- else:
49
- context['output'] = "Error: Failed to capture screenshot."
50
- context['messages'] = output_messages
51
- exit()
38
+ print(f"📸 Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
52
39
 
53
- if not image_paths:
54
- context['output'] = "No valid images found or captured."
55
- context['messages'] = output_messages
56
- exit()
57
-
58
- if not user_prompt or not user_prompt.strip():
59
- # In a non-interactive Jinx, a default prompt is better than waiting for input
60
- user_prompt = "Describe the image(s)."
61
-
62
- # Fallback for model/provider if not explicitly set in Jinx inputs
63
- if not vision_model and current_npc and current_npc.model:
64
- vision_model = current_npc.model
65
- if not vision_provider and current_npc and current_npc.provider:
66
- vision_provider = current_npc.provider
40
+ if not vision_model:
41
+ vision_model = getattr(current_npc, 'model', 'gpt-4o-mini')
67
42
 
68
- # Final fallbacks (these would ideally come from npcsh._state config)
69
- if not vision_model: vision_model = "gemini-1.5-pro-vision" # Example default
70
- if not vision_provider: vision_provider = "gemini" # Example default
43
+ if not vision_provider:
44
+ vision_provider = getattr(current_npc, 'provider', 'openai')
71
45
 
72
- try:
73
- response_data = get_llm_response(
74
- prompt=user_prompt,
75
- model=vision_model,
76
- provider=vision_provider,
77
- messages=output_messages, # Pass current messages to LLM
78
- images=image_paths,
79
- stream=stream_output,
80
- npc=current_npc,
81
- api_url=api_url,
82
- api_key=api_key
83
- )
84
- context['output'] = response_data.get('response')
85
- context['messages'] = response_data.get('messages', output_messages)
86
- context['model'] = vision_model
87
- context['provider'] = vision_provider
88
-
89
- except Exception as e:
90
- traceback.print_exc()
91
- context['output'] = f"Error during /ots command: {e}"
92
- context['messages'] = output_messages
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,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
npcsh/routes.py CHANGED
@@ -2,9 +2,11 @@ from typing import Callable, Dict, Any, List, Optional
2
2
  import functools
3
3
  import os
4
4
  import traceback
5
+ import sys
6
+ import inspect
5
7
  from pathlib import Path
6
8
 
7
- from npcpy.npc_compiler import Jinx, load_jinxs_from_directory
9
+ from npcpy.npc_compiler import Jinx, load_jinxs_from_directory, extract_jinx_inputs
8
10
 
9
11
 
10
12
  class CommandRouter:
@@ -55,27 +57,43 @@ class CommandRouter:
55
57
 
56
58
  try:
57
59
  import shlex
60
+
58
61
  parts = shlex.split(command)
59
62
  args = parts[1:] if len(parts) > 1 else []
60
63
 
61
- input_values = {}
62
- if hasattr(jinx, 'inputs') and jinx.inputs:
63
- for i, input_spec in enumerate(jinx.inputs):
64
- if isinstance(input_spec, str):
65
- input_name = input_spec
66
- elif isinstance(input_spec, dict):
67
- input_name = list(input_spec.keys())[0]
68
- else:
69
- continue
70
-
71
- if i < len(args):
72
- input_values[input_name] = args[i]
64
+ # Use extract_jinx_inputs
65
+ input_values = extract_jinx_inputs(args, jinx)
66
+
67
+ # Build extra_globals for jinx execution
68
+ from npcpy.memory.command_history import CommandHistory, load_kg_from_db
69
+ from npcpy.memory.search import execute_rag_command, execute_brainblast_command
70
+ from npcpy.data.load import load_file_contents
71
+ from npcpy.data.web import search_web
72
+
73
+ application_globals_for_jinx = {
74
+ "CommandHistory": CommandHistory,
75
+ "load_kg_from_db": load_kg_from_db,
76
+ "execute_rag_command": execute_rag_command,
77
+ "execute_brainblast_command": execute_brainblast_command,
78
+ "load_file_contents": load_file_contents,
79
+ "search_web": search_web,
80
+ 'state': kwargs.get('state')
81
+ }
82
+
83
+ # Add functions from _state module if available
84
+ try:
85
+ from npcsh import _state
86
+ for name, func in inspect.getmembers(_state, inspect.isfunction):
87
+ application_globals_for_jinx[name] = func
88
+ except:
89
+ pass
73
90
 
74
91
  jinx_output = jinx.execute(
75
92
  input_values=input_values,
76
93
  jinxs_dict=kwargs.get('jinxs_dict', {}),
77
94
  npc=npc,
78
- messages=messages
95
+ messages=messages,
96
+ extra_globals=application_globals_for_jinx
79
97
  )
80
98
 
81
99
  if isinstance(jinx_output, dict):