npcsh 1.1.17__py3-none-any.whl → 1.1.18__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 (169) hide show
  1. npcsh/_state.py +114 -91
  2. npcsh/alicanto.py +2 -2
  3. npcsh/benchmark/__init__.py +8 -2
  4. npcsh/benchmark/npcsh_agent.py +46 -12
  5. npcsh/benchmark/runner.py +85 -43
  6. npcsh/benchmark/templates/install-npcsh.sh.j2 +35 -0
  7. npcsh/build.py +2 -4
  8. npcsh/completion.py +2 -6
  9. npcsh/config.py +1 -3
  10. npcsh/conversation_viewer.py +389 -0
  11. npcsh/corca.py +0 -1
  12. npcsh/execution.py +0 -1
  13. npcsh/guac.py +0 -1
  14. npcsh/mcp_helpers.py +2 -3
  15. npcsh/mcp_server.py +5 -10
  16. npcsh/npc.py +10 -11
  17. npcsh/npc_team/jinxs/bin/benchmark.jinx +1 -1
  18. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +321 -17
  19. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +312 -67
  20. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +366 -44
  21. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +73 -0
  22. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +328 -20
  23. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +242 -10
  24. npcsh/npc_team/jinxs/lib/core/sleep.jinx +22 -11
  25. npcsh/npc_team/jinxs/lib/core/sql.jinx +10 -6
  26. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +387 -76
  27. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +372 -55
  28. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +299 -144
  29. npcsh/npc_team/jinxs/modes/alicanto.jinx +356 -0
  30. npcsh/npc_team/jinxs/modes/arxiv.jinx +720 -0
  31. npcsh/npc_team/jinxs/modes/corca.jinx +430 -0
  32. npcsh/npc_team/jinxs/modes/guac.jinx +544 -0
  33. npcsh/npc_team/jinxs/modes/plonk.jinx +379 -0
  34. npcsh/npc_team/jinxs/modes/pti.jinx +357 -0
  35. npcsh/npc_team/jinxs/modes/reattach.jinx +291 -0
  36. npcsh/npc_team/jinxs/modes/spool.jinx +350 -0
  37. npcsh/npc_team/jinxs/modes/wander.jinx +455 -0
  38. npcsh/npc_team/jinxs/{bin → modes}/yap.jinx +13 -7
  39. npcsh/npcsh.py +7 -4
  40. npcsh/plonk.py +0 -1
  41. npcsh/pti.py +0 -1
  42. npcsh/routes.py +1 -3
  43. npcsh/spool.py +0 -1
  44. npcsh/ui.py +0 -1
  45. npcsh/wander.py +0 -1
  46. npcsh/yap.py +0 -1
  47. npcsh-1.1.18.data/data/npcsh/npc_team/alicanto.jinx +356 -0
  48. npcsh-1.1.18.data/data/npcsh/npc_team/arxiv.jinx +720 -0
  49. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/benchmark.jinx +1 -1
  50. npcsh-1.1.18.data/data/npcsh/npc_team/corca.jinx +430 -0
  51. npcsh-1.1.18.data/data/npcsh/npc_team/db_search.jinx +348 -0
  52. npcsh-1.1.18.data/data/npcsh/npc_team/file_search.jinx +339 -0
  53. npcsh-1.1.18.data/data/npcsh/npc_team/guac.jinx +544 -0
  54. npcsh-1.1.18.data/data/npcsh/npc_team/jinxs.jinx +331 -0
  55. npcsh-1.1.18.data/data/npcsh/npc_team/kg_search.jinx +418 -0
  56. npcsh-1.1.18.data/data/npcsh/npc_team/mem_review.jinx +73 -0
  57. npcsh-1.1.18.data/data/npcsh/npc_team/mem_search.jinx +388 -0
  58. npcsh-1.1.18.data/data/npcsh/npc_team/paper_search.jinx +412 -0
  59. npcsh-1.1.18.data/data/npcsh/npc_team/plonk.jinx +379 -0
  60. npcsh-1.1.18.data/data/npcsh/npc_team/pti.jinx +357 -0
  61. npcsh-1.1.18.data/data/npcsh/npc_team/reattach.jinx +291 -0
  62. npcsh-1.1.18.data/data/npcsh/npc_team/semantic_scholar.jinx +386 -0
  63. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sleep.jinx +22 -11
  64. npcsh-1.1.18.data/data/npcsh/npc_team/spool.jinx +350 -0
  65. npcsh-1.1.18.data/data/npcsh/npc_team/sql.jinx +20 -0
  66. npcsh-1.1.18.data/data/npcsh/npc_team/wander.jinx +455 -0
  67. npcsh-1.1.18.data/data/npcsh/npc_team/web_search.jinx +283 -0
  68. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.jinx +13 -7
  69. {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/METADATA +90 -1
  70. npcsh-1.1.18.dist-info/RECORD +235 -0
  71. {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/WHEEL +1 -1
  72. {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/entry_points.txt +0 -3
  73. npcsh/npc_team/jinxs/bin/spool.jinx +0 -161
  74. npcsh/npc_team/jinxs/bin/wander.jinx +0 -242
  75. npcsh/npc_team/jinxs/lib/research/arxiv.jinx +0 -76
  76. npcsh-1.1.17.data/data/npcsh/npc_team/arxiv.jinx +0 -76
  77. npcsh-1.1.17.data/data/npcsh/npc_team/db_search.jinx +0 -44
  78. npcsh-1.1.17.data/data/npcsh/npc_team/file_search.jinx +0 -94
  79. npcsh-1.1.17.data/data/npcsh/npc_team/jinxs.jinx +0 -176
  80. npcsh-1.1.17.data/data/npcsh/npc_team/kg_search.jinx +0 -96
  81. npcsh-1.1.17.data/data/npcsh/npc_team/mem_search.jinx +0 -80
  82. npcsh-1.1.17.data/data/npcsh/npc_team/paper_search.jinx +0 -101
  83. npcsh-1.1.17.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -69
  84. npcsh-1.1.17.data/data/npcsh/npc_team/spool.jinx +0 -161
  85. npcsh-1.1.17.data/data/npcsh/npc_team/sql.jinx +0 -16
  86. npcsh-1.1.17.data/data/npcsh/npc_team/wander.jinx +0 -242
  87. npcsh-1.1.17.data/data/npcsh/npc_team/web_search.jinx +0 -51
  88. npcsh-1.1.17.dist-info/RECORD +0 -219
  89. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  90. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  91. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/alicanto.png +0 -0
  92. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  93. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  94. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/build.jinx +0 -0
  95. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/chat.jinx +0 -0
  96. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/click.jinx +0 -0
  97. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  98. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  99. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  100. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  101. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compile.jinx +0 -0
  102. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/compress.jinx +0 -0
  103. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  104. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/convene.jinx +0 -0
  105. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.npc +0 -0
  106. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca.png +0 -0
  107. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/corca_example.png +0 -0
  108. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  109. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  110. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  111. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic.npc +0 -0
  112. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/frederic4.png +0 -0
  113. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.npc +0 -0
  114. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/guac.png +0 -0
  115. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/help.jinx +0 -0
  116. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  117. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/init.jinx +0 -0
  118. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  119. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  120. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  121. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  122. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  123. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  124. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  125. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/notify.jinx +0 -0
  126. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  127. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  128. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/nql.jinx +0 -0
  129. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  130. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  131. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/ots.jinx +0 -0
  132. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/paste.jinx +0 -0
  133. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.npc +0 -0
  134. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonk.png +0 -0
  135. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  136. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  137. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/python.jinx +0 -0
  138. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  139. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/roll.jinx +0 -0
  140. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  141. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sample.jinx +0 -0
  142. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  143. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/search.jinx +0 -0
  144. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  145. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/serve.jinx +0 -0
  146. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/set.jinx +0 -0
  147. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sh.jinx +0 -0
  148. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/shh.jinx +0 -0
  149. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  150. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sibiji.png +0 -0
  151. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  152. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/spool.png +0 -0
  153. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch.jinx +0 -0
  154. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  155. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  156. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/switches.jinx +0 -0
  157. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/sync.jinx +0 -0
  158. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  159. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  160. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  161. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/usage.jinx +0 -0
  162. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  163. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  164. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/wait.jinx +0 -0
  165. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  166. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/yap.png +0 -0
  167. {npcsh-1.1.17.data → npcsh-1.1.18.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  168. {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/licenses/LICENSE +0 -0
  169. {npcsh-1.1.17.dist-info → npcsh-1.1.18.dist-info}/top_level.txt +0 -0
npcsh/_state.py CHANGED
@@ -1,11 +1,14 @@
1
1
  # Standard library imports
2
2
  import atexit
3
+ import base64
4
+ import os
3
5
  from dataclasses import dataclass, field
4
6
  from datetime import datetime
5
7
  import filecmp
6
8
  import inspect
9
+
7
10
  import logging
8
- import os
11
+
9
12
  from pathlib import Path
10
13
  import platform
11
14
  import re
@@ -16,8 +19,11 @@ import signal
16
19
  import sqlite3
17
20
  import subprocess
18
21
  import sys
22
+ import tempfile
19
23
  import time
20
24
  import textwrap
25
+ import readline
26
+ import json
21
27
  from typing import Dict, List, Any, Tuple, Union, Optional, Callable
22
28
  import yaml
23
29
 
@@ -40,9 +46,9 @@ try:
40
46
  import pty
41
47
  import tty
42
48
  import termios
43
- import readline
49
+
44
50
  except ImportError:
45
- readline = None
51
+
46
52
  pty = None
47
53
  tty = None
48
54
  termios = None
@@ -53,15 +59,21 @@ try:
53
59
  except ImportError:
54
60
  chromadb = None
55
61
 
62
+ try:
63
+ import ollama
64
+ except ImportError:
65
+ ollama = None
66
+
56
67
  # Third-party imports
57
- from colorama import Fore, Back, Style
68
+ from colorama import Style
58
69
  from litellm import RateLimitError
70
+ import numpy as np
59
71
  from termcolor import colored
60
72
 
61
73
  # npcpy imports
62
74
  from npcpy.data.load import load_file_contents
63
75
  from npcpy.data.web import search_web
64
- from npcpy.gen.embeddings import get_embeddings
76
+
65
77
  from npcpy.llm_funcs import (
66
78
  check_llm_command,
67
79
  get_llm_response,
@@ -74,25 +86,25 @@ from npcpy.memory.command_history import (
74
86
  save_conversation_message,
75
87
  load_kg_from_db,
76
88
  save_kg_to_db,
89
+ format_memory_context,
77
90
  )
78
91
  from npcpy.memory.knowledge_graph import kg_evolve_incremental
79
92
  from npcpy.memory.search import execute_rag_command, execute_brainblast_command
80
- from npcpy.npc_compiler import NPC, Team, load_jinxs_from_directory, build_jinx_tool_catalog
93
+ from npcpy.npc_compiler import NPC, Team, build_jinx_tool_catalog
81
94
  from npcpy.npc_sysenv import (
82
95
  print_and_process_stream_with_markdown,
83
96
  render_markdown,
84
97
  get_model_and_provider,
85
98
  get_locally_available_models,
86
- lookup_provider
99
+
87
100
  )
88
101
  from npcpy.tools import auto_tools
102
+ from npcpy.gen.embeddings import get_embeddings
89
103
 
90
104
  # Local module imports
91
105
  from .config import (
92
- VERSION,
93
106
  DEFAULT_NPC_TEAM_PATH,
94
107
  PROJECT_NPC_TEAM_PATH,
95
- HISTORY_DB_DEFAULT_PATH,
96
108
  READLINE_HISTORY_FILE,
97
109
  NPCSH_CHAT_MODEL,
98
110
  NPCSH_CHAT_PROVIDER,
@@ -156,6 +168,9 @@ class ShellState:
156
168
  video_gen_provider: str = NPCSH_VIDEO_GEN_PROVIDER
157
169
  current_mode: str = NPCSH_DEFAULT_MODE
158
170
  build_kg: bool = NPCSH_BUILD_KG
171
+ kg_link_facts: bool = False # Link facts to concepts (requires LLM calls)
172
+ kg_link_concepts: bool = False # Link concepts to concepts (requires LLM calls)
173
+ kg_link_facts_facts: bool = False # Link facts to facts (requires LLM calls)
159
174
  api_key: Optional[str] = None
160
175
  api_url: Optional[str] = NPCSH_API_URL
161
176
  current_path: str = field(default_factory=os.getcwd)
@@ -303,6 +318,33 @@ def set_npcsh_config_value(key: str, value: str):
303
318
  }
304
319
  if env_key in field_map:
305
320
  setattr(ShellState, field_map[env_key], parsed_val)
321
+
322
+ # Persist to ~/.npcshrc
323
+ npcshrc_path = os.path.expanduser("~/.npcshrc")
324
+ try:
325
+ existing_lines = []
326
+ if os.path.exists(npcshrc_path):
327
+ with open(npcshrc_path, 'r') as f:
328
+ existing_lines = f.readlines()
329
+
330
+ # Update or add the export line
331
+ export_line = f"export {env_key}=\"{value}\"\n"
332
+ found = False
333
+ for i, line in enumerate(existing_lines):
334
+ if line.strip().startswith(f"export {env_key}="):
335
+ existing_lines[i] = export_line
336
+ found = True
337
+ break
338
+
339
+ if not found:
340
+ existing_lines.append(export_line)
341
+
342
+ with open(npcshrc_path, 'w') as f:
343
+ f.writelines(existing_lines)
344
+ except Exception as e:
345
+ print(f"Warning: Could not persist config to {npcshrc_path}: {e}")
346
+
347
+
306
348
  def get_npc_path(npc_name: str, db_path: str) -> str:
307
349
  project_npc_team_dir = os.path.abspath("./npc_team")
308
350
  project_npc_path = os.path.join(project_npc_team_dir, f"{npc_name}.npc")
@@ -317,7 +359,7 @@ def get_npc_path(npc_name: str, db_path: str) -> str:
317
359
  if result:
318
360
  return result[0]
319
361
 
320
- except Exception as e:
362
+ except Exception:
321
363
  try:
322
364
  with sqlite3.connect(db_path) as conn:
323
365
  cursor = conn.cursor()
@@ -424,10 +466,10 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
424
466
  old_package_jinxs = set()
425
467
  if os.path.exists(manifest_path):
426
468
  try:
427
- import json
469
+
428
470
  with open(manifest_path, 'r') as f:
429
471
  old_package_jinxs = set(json.load(f).get('jinxs', []))
430
- except:
472
+ except Exception:
431
473
  pass
432
474
 
433
475
  # Track current package jinxs
@@ -480,7 +522,7 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
480
522
 
481
523
  # Save updated manifest
482
524
  try:
483
- import json
525
+
484
526
  with open(manifest_path, 'w') as f:
485
527
  json.dump({'jinxs': list(current_package_jinxs), 'updated': str(__import__('datetime').datetime.now())}, f, indent=2)
486
528
  except Exception as e:
@@ -561,9 +603,6 @@ def get_relevant_memories(
561
603
  max_memories: int = 10,
562
604
  state: Optional[ShellState] = None
563
605
  ) -> List[Dict]:
564
-
565
- engine = command_history.engine
566
-
567
606
  all_memories = command_history.get_memories_for_scope(
568
607
  npc=npc_name,
569
608
  team=team_name,
@@ -588,7 +627,7 @@ def get_relevant_memories(
588
627
 
589
628
  if state and state.embedding_model and state.embedding_provider:
590
629
  try:
591
- from npcpy.gen.embeddings import get_embeddings
630
+
592
631
 
593
632
  search_text = query if query else "recent context"
594
633
  query_embedding = get_embeddings(
@@ -606,7 +645,7 @@ def get_relevant_memories(
606
645
  state.embedding_provider
607
646
  )
608
647
 
609
- import numpy as np
648
+
610
649
  similarities = []
611
650
  for mem_emb in memory_embeddings:
612
651
  similarity = np.dot(query_embedding, mem_emb) / (
@@ -816,7 +855,6 @@ BASH_COMMANDS = [
816
855
  "command",
817
856
  "compgen",
818
857
  "complete",
819
- "continue",
820
858
  "declare",
821
859
  "dirs",
822
860
  "disown",
@@ -1283,7 +1321,7 @@ def get_setting_windows(key, default=None):
1283
1321
 
1284
1322
 
1285
1323
  def setup_readline() -> str:
1286
- import readline
1324
+
1287
1325
  if readline is None:
1288
1326
  return None
1289
1327
  try:
@@ -1429,7 +1467,7 @@ def make_completer(shell_state: ShellState, router: Any):
1429
1467
  else:
1430
1468
  return None # readline expects None when no more completions
1431
1469
 
1432
- except Exception as e:
1470
+ except Exception:
1433
1471
  # Using completion_logger for internal debugging, not printing to stdout for user.
1434
1472
  # completion_logger.error(f"Exception in completion: {e}", exc_info=True)
1435
1473
  return None
@@ -1589,7 +1627,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
1589
1627
  try:
1590
1628
  import termios
1591
1629
  import tty
1592
- import readline
1630
+
1593
1631
  except ImportError:
1594
1632
  return input(prompt)
1595
1633
 
@@ -1625,7 +1663,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
1625
1663
  try:
1626
1664
  import shutil
1627
1665
  term_width = shutil.get_terminal_size().columns
1628
- except:
1666
+ except json.JSONDecodeError:
1629
1667
  term_width = 80
1630
1668
 
1631
1669
  def draw():
@@ -1638,7 +1676,6 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
1638
1676
  sys.stdout.write('\r')
1639
1677
  # Move up for each wrapped line we're on
1640
1678
  cursor_total = prompt_visible_len + pos
1641
- cursor_line = cursor_total // term_width
1642
1679
  # Go up to the first line of input
1643
1680
  for _ in range(num_lines - 1):
1644
1681
  sys.stdout.write('\033[A')
@@ -1721,7 +1758,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
1721
1758
  # Check if this looks like binary/image data
1722
1759
  # Image signatures: PNG (\x89PNG), JPEG (\xff\xd8\xff), GIF (GIF8), BMP (BM)
1723
1760
  # Also check for high ratio of non-printable chars
1724
- is_binary = False
1761
+
1725
1762
  if len(paste_buffer) > 4:
1726
1763
  # Check for common image magic bytes
1727
1764
  if paste_buffer[:4] == '\x89PNG' or paste_buffer[:8] == '\x89PNG\r\n\x1a\n':
@@ -1742,8 +1779,8 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
1742
1779
 
1743
1780
  if is_binary:
1744
1781
  # Save image data to temp file
1745
- import tempfile
1746
- import os
1782
+
1783
+
1747
1784
  try:
1748
1785
  # Determine extension from magic bytes
1749
1786
  ext = '.bin'
@@ -1766,14 +1803,14 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
1766
1803
  with os.fdopen(fd, 'wb') as f:
1767
1804
  if paste_buffer.startswith('data:image/'):
1768
1805
  # Decode base64 data URL
1769
- import base64
1806
+
1770
1807
  _, data = paste_buffer.split(',', 1)
1771
1808
  f.write(base64.b64decode(data))
1772
1809
  else:
1773
1810
  f.write(paste_buffer.encode('latin-1'))
1774
1811
  pasted_content = temp_path # Store path to image
1775
1812
  placeholder = f"[pasted image: {temp_path}]"
1776
- except:
1813
+ except Exception:
1777
1814
  pasted_content = None
1778
1815
  placeholder = "[pasted image: failed to save]"
1779
1816
  else:
@@ -1971,7 +2008,7 @@ def _input_with_hint_below(prompt: str, state=None, router=None, token_hint: str
1971
2008
  sys.stdout.flush()
1972
2009
  else:
1973
2010
  pass # No tool call to show
1974
- except:
2011
+ except Exception:
1975
2012
  pass
1976
2013
 
1977
2014
  elif c and ord(c) >= 32: # Printable
@@ -2000,7 +2037,7 @@ def _get_slash_hints(state, router, prefix='/') -> str:
2000
2037
  try:
2001
2038
  import shutil
2002
2039
  term_width = shutil.get_terminal_size().columns
2003
- except:
2040
+ except Exception:
2004
2041
  term_width = 80
2005
2042
 
2006
2043
  # Build hint string that fits in terminal
@@ -2149,44 +2186,20 @@ def wrap_text(text: str, width: int = 80) -> str:
2149
2186
 
2150
2187
 
2151
2188
 
2152
- def setup_readline() -> str:
2153
- """Setup readline with history and completion"""
2154
- try:
2155
- readline.read_history_file(READLINE_HISTORY_FILE)
2156
- readline.set_history_length(1000)
2157
-
2158
-
2159
- readline.parse_and_bind("tab: complete")
2160
-
2161
- readline.parse_and_bind("set enable-bracketed-paste on")
2162
- readline.parse_and_bind(r'"\C-r": reverse-search-history')
2163
- readline.parse_and_bind(r'"\C-e": end-of-line')
2164
- readline.parse_and_bind(r'"\C-a": beginning-of-line')
2165
-
2166
- return READLINE_HISTORY_FILE
2167
-
2168
- except FileNotFoundError:
2169
- pass
2170
- except OSError as e:
2171
- print(f"Warning: Could not read readline history file {READLINE_HISTORY_FILE}: {e}")
2172
2189
 
2173
-
2174
- def save_readline_history():
2175
- try:
2176
- readline.write_history_file(READLINE_HISTORY_FILE)
2177
- except OSError as e:
2178
- print(f"Warning: Could not write readline history file {READLINE_HISTORY_FILE}: {e}")
2179
2190
 
2180
2191
  def store_command_embeddings(command: str, output: Any, state: ShellState):
2181
2192
  if not chroma_client or not state.embedding_model or not state.embedding_provider:
2182
- if not chroma_client: print("Warning: ChromaDB client not available for embeddings.", file=sys.stderr)
2193
+ if not chroma_client:
2194
+ print("Warning: ChromaDB client not available for embeddings.", file=sys.stderr)
2183
2195
  return
2184
2196
  if not command and not output:
2185
2197
  return
2186
2198
 
2187
2199
  try:
2188
2200
  output_str = str(output) if output else ""
2189
- if not command and not output_str: return
2201
+ if not command and not output_str:
2202
+ return
2190
2203
 
2191
2204
  texts_to_embed = [command, output_str]
2192
2205
 
@@ -2358,10 +2371,7 @@ def _ollama_supports_tools(model: str) -> Optional[bool]:
2358
2371
  Best-effort check for tool-call support on an Ollama model by inspecting its template/metadata.
2359
2372
  Mirrors the lightweight check used in the Flask serve path.
2360
2373
  """
2361
- try:
2362
- import ollama # Local import to avoid hard dependency when Ollama isn't installed
2363
- except Exception:
2364
- return None
2374
+
2365
2375
 
2366
2376
  try:
2367
2377
  details = ollama.show(model)
@@ -2468,7 +2478,7 @@ def wrap_tool_with_display(tool_name: str, tool_func: Callable, state: ShellStat
2468
2478
  print(colored(f" ⚡ {tool_name}", "cyan") + colored(f" {args_display}", "white", attrs=["dark"]), end="", flush=True)
2469
2479
  else:
2470
2480
  print(colored(f" ⚡ {tool_name}", "cyan"), end="", flush=True)
2471
- except:
2481
+ except Exception:
2472
2482
  pass
2473
2483
 
2474
2484
  # Execute tool
@@ -2484,14 +2494,14 @@ def wrap_tool_with_display(tool_name: str, tool_func: Callable, state: ShellStat
2484
2494
  result_preview = result_preview[:200] + "..."
2485
2495
  if result_preview and result_preview not in ('None', '', '{}', '[]'):
2486
2496
  print(colored(f" → {result_preview}", "white", attrs=["dark"]), flush=True)
2487
- except:
2497
+ except Exception:
2488
2498
  pass
2489
2499
  return result
2490
2500
  except Exception as e:
2491
2501
  if log_level != "silent":
2492
2502
  try:
2493
2503
  print(colored(f" ✗ {str(e)[:100]}", "red"), flush=True)
2494
- except:
2504
+ except Exception as e:
2495
2505
  pass
2496
2506
  raise
2497
2507
  return wrapped
@@ -2642,10 +2652,10 @@ def should_skip_kg_processing(user_input: str, assistant_output: str) -> bool:
2642
2652
 
2643
2653
  return False
2644
2654
 
2645
- def execute_slash_command(command: str,
2646
- stdin_input: Optional[str],
2647
- state: ShellState,
2648
- stream: bool,
2655
+ def execute_slash_command(command: str,
2656
+ stdin_input: Optional[str],
2657
+ state: ShellState,
2658
+ stream: bool,
2649
2659
  router) -> Tuple[ShellState, Any]:
2650
2660
  """Executes slash commands using the router."""
2651
2661
  try:
@@ -2653,7 +2663,13 @@ def execute_slash_command(command: str,
2653
2663
  except ValueError:
2654
2664
  all_command_parts = command.split()
2655
2665
  command_name = all_command_parts[0].lstrip('/')
2666
+
2667
+ # --- QUIT/EXIT HANDLING ---
2668
+ if command_name in ['quit', 'exit', 'q']:
2656
2669
 
2670
+ print("Goodbye!")
2671
+ sys.exit(0)
2672
+
2657
2673
  # --- NPC SWITCHING LOGIC ---
2658
2674
  if command_name in ['n', 'npc']:
2659
2675
  npc_to_switch_to = all_command_parts[1] if len(all_command_parts) > 1 else None
@@ -2914,9 +2930,6 @@ def process_pipeline_command(
2914
2930
  "tools": tools_for_llm,
2915
2931
  "tool_map": tool_exec_map,
2916
2932
  }
2917
- # Only add tool_choice for providers that support it (not gemini)
2918
- is_gemini = (exec_provider and "gemini" in exec_provider.lower()) or \
2919
- (exec_model and "gemini" in exec_model.lower())
2920
2933
  llm_kwargs["tool_choice"] = 'auto'
2921
2934
 
2922
2935
  # Agent loop: keep calling LLM until it stops making tool calls
@@ -3105,7 +3118,7 @@ def _delegate_to_npc(state: ShellState, npc_name: str, command: str, delegation_
3105
3118
  MAX_DELEGATION_DEPTH = 1 # Only allow one level of delegation
3106
3119
 
3107
3120
  if delegation_depth > MAX_DELEGATION_DEPTH:
3108
- return state, {'output': f"⚠ Maximum delegation depth reached."}
3121
+ return state, {'output': "⚠ Maximum delegation depth reached."}
3109
3122
 
3110
3123
  if not state.team or not hasattr(state.team, 'npcs') or npc_name not in state.team.npcs:
3111
3124
  return state, {'output': f"⚠ NPC '{npc_name}' not found in team"}
@@ -3311,7 +3324,7 @@ def execute_command(
3311
3324
  )
3312
3325
  )
3313
3326
  stdin_for_next = full_stream_output
3314
- except:
3327
+ except Exception:
3315
3328
  if output is not None:
3316
3329
  try:
3317
3330
  stdin_for_next = str(output)
@@ -3413,7 +3426,7 @@ def execute_command(
3413
3426
  def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
3414
3427
  setup_npcsh_config()
3415
3428
 
3416
- db_path = os.getenv("NPCSH_DB_PATH", HISTORY_DB_DEFAULT_PATH)
3429
+ db_path = NPCSH_DB_PATH
3417
3430
  db_path = os.path.expanduser(db_path)
3418
3431
  os.makedirs(os.path.dirname(db_path), exist_ok=True)
3419
3432
  command_history = CommandHistory(db_path)
@@ -3424,11 +3437,11 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
3424
3437
  print("NPCSH initialization complete. Restart or source ~/.npcshrc.")
3425
3438
 
3426
3439
  try:
3427
- history_file = setup_readline()
3440
+ setup_readline()
3428
3441
  atexit.register(save_readline_history)
3429
3442
  atexit.register(command_history.close)
3430
- except:
3431
- pass
3443
+ except OSError as e:
3444
+ print(f"Warning: Failed to setup readline history: {e}", file=sys.stderr)
3432
3445
 
3433
3446
  project_team_path = os.path.abspath(PROJECT_NPC_TEAM_PATH)
3434
3447
  global_team_path = os.path.expanduser(DEFAULT_NPC_TEAM_PATH)
@@ -3437,7 +3450,7 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
3437
3450
  default_forenpc_name = None
3438
3451
  global_team_path = os.path.expanduser(DEFAULT_NPC_TEAM_PATH)
3439
3452
  if not os.path.exists(global_team_path):
3440
- print(f"Global NPC team directory doesn't exist. Initializing...")
3453
+ print("Global NPC team directory doesn't exist. Initializing...")
3441
3454
  initialize_base_npcs_if_needed(db_path)
3442
3455
  if os.path.exists(project_team_path):
3443
3456
  team_dir = project_team_path
@@ -3657,6 +3670,10 @@ def process_result(
3657
3670
  final_output_str = None
3658
3671
 
3659
3672
  # FIX: Handle dict output properly
3673
+ msg_input_tokens = None
3674
+ msg_output_tokens = None
3675
+ msg_cost = None
3676
+
3660
3677
  if isinstance(output, dict):
3661
3678
  # Use None-safe check to not skip empty strings
3662
3679
  output_content = output.get('output') if 'output' in output else output.get('response')
@@ -3666,15 +3683,18 @@ def process_result(
3666
3683
  # Accumulate token usage if available
3667
3684
  if 'usage' in output:
3668
3685
  usage = output['usage']
3669
- result_state.session_input_tokens += usage.get('input_tokens', 0)
3670
- result_state.session_output_tokens += usage.get('output_tokens', 0)
3686
+ msg_input_tokens = usage.get('input_tokens', 0)
3687
+ msg_output_tokens = usage.get('output_tokens', 0)
3688
+ result_state.session_input_tokens += msg_input_tokens
3689
+ result_state.session_output_tokens += msg_output_tokens
3671
3690
  # Calculate cost
3672
3691
  from npcpy.gen.response import calculate_cost
3673
- result_state.session_cost_usd += calculate_cost(
3692
+ msg_cost = calculate_cost(
3674
3693
  model_for_stream,
3675
- usage.get('input_tokens', 0),
3676
- usage.get('output_tokens', 0)
3694
+ msg_input_tokens,
3695
+ msg_output_tokens
3677
3696
  )
3697
+ result_state.session_cost_usd += msg_cost
3678
3698
 
3679
3699
  # If output_content is still a dict, convert to string
3680
3700
  if isinstance(output_content, dict):
@@ -3736,6 +3756,9 @@ def process_result(
3736
3756
  provider=active_npc.provider,
3737
3757
  npc=npc_name,
3738
3758
  team=team_name,
3759
+ input_tokens=msg_input_tokens,
3760
+ output_tokens=msg_output_tokens,
3761
+ cost=msg_cost,
3739
3762
  )
3740
3763
 
3741
3764
  result_state.turn_count += 1
@@ -3844,15 +3867,15 @@ def process_result(
3844
3867
  result_state.current_path
3845
3868
  )
3846
3869
  evolved_npc_kg, _ = kg_evolve_incremental(
3847
- existing_kg=npc_kg,
3870
+ existing_kg=npc_kg,
3848
3871
  new_facts=approved_facts,
3849
- model=active_npc.model,
3850
- provider=active_npc.provider,
3872
+ model=active_npc.model,
3873
+ provider=active_npc.provider,
3851
3874
  npc=active_npc,
3852
3875
  get_concepts=True,
3853
- link_concepts_facts=False,
3854
- link_concepts_concepts=False,
3855
- link_facts_facts=False,
3876
+ link_concepts_facts=result_state.kg_link_facts,
3877
+ link_concepts_concepts=result_state.kg_link_concepts,
3878
+ link_facts_facts=result_state.kg_link_facts_facts,
3856
3879
  )
3857
3880
  save_kg_to_db(
3858
3881
  engine,
npcsh/alicanto.py CHANGED
@@ -4,7 +4,7 @@ alicanto - Deep research mode CLI entry point
4
4
  This is a thin wrapper that executes the alicanto.jinx through the jinx mechanism.
5
5
  """
6
6
  import argparse
7
- import os
7
+
8
8
  import sys
9
9
 
10
10
  from npcsh._state import setup_shell
@@ -30,7 +30,7 @@ def main():
30
30
  sys.exit(1)
31
31
 
32
32
  # Setup shell to get team and default NPC
33
- command_history, team, default_npc = setup_shell()
33
+ _, team, default_npc = setup_shell()
34
34
 
35
35
  if not team or "alicanto" not in team.jinxs_dict:
36
36
  print("Error: alicanto jinx not found. Ensure npc_team/jinxs/modes/alicanto.jinx exists.")
@@ -16,7 +16,13 @@ Usage:
16
16
  run_benchmark(model="claude-sonnet-4-20250514", provider="anthropic")
17
17
  """
18
18
 
19
- from .npcsh_agent import NpcshAgent
20
19
  from .runner import run_benchmark, BenchmarkRunner
21
20
 
22
- __all__ = ["NpcshAgent", "run_benchmark", "BenchmarkRunner"]
21
+ __all__ = ["run_benchmark", "BenchmarkRunner"]
22
+
23
+ # NpcshAgent requires harbor to be installed - import lazily
24
+ try:
25
+ from .npcsh_agent import NpcshAgent
26
+ __all__.append("NpcshAgent")
27
+ except ImportError:
28
+ NpcshAgent = None # Harbor not installed
@@ -9,7 +9,6 @@ import json
9
9
  import os
10
10
  import shlex
11
11
  from pathlib import Path
12
- from typing import Optional
13
12
 
14
13
  from harbor.agents.installed.base import BaseInstalledAgent, ExecInput
15
14
  from harbor.models.agent.context import AgentContext
@@ -54,7 +53,16 @@ class NpcshAgent(BaseInstalledAgent):
54
53
  Returns:
55
54
  List of ExecInput commands to execute
56
55
  """
57
- escaped_instruction = shlex.quote(instruction)
56
+ # Wrap the instruction with explicit jinx usage directions
57
+ tool_instruction = f"""You have access to jinxs including edit_file (for writing/creating files), sh (for running shell commands), and python (for running Python code).
58
+
59
+ IMPORTANT: You MUST use these jinxs to complete the task. Do NOT just output code as text - use the edit_file jinx to actually write files to disk.
60
+
61
+ Task: {instruction}
62
+
63
+ Remember: Use edit_file to write any code files. Use sh to run shell commands like gcc, make, etc."""
64
+
65
+ escaped_instruction = shlex.quote(tool_instruction)
58
66
  model_name = self.model_name
59
67
 
60
68
  if model_name and "/" in model_name:
@@ -105,24 +113,33 @@ class NpcshAgent(BaseInstalledAgent):
105
113
 
106
114
  # Create output directory
107
115
  commands.append(ExecInput(
108
- cmd=f"mkdir -p {shlex.quote(output_dir)}",
109
- timeout=30
116
+ command=f"mkdir -p {shlex.quote(output_dir)}",
117
+ timeout_sec=30
118
+ ))
119
+
120
+ # Create .npcsh_global file to use global team and avoid interactive prompts
121
+ commands.append(ExecInput(
122
+ command="touch /app/.npcsh_global",
123
+ timeout_sec=10
110
124
  ))
111
125
 
112
126
  # Run npcsh with the instruction
127
+ # Using corca NPC which has edit_file tool for writing files
113
128
  # Using the npc CLI which supports single-command execution
129
+ # NPCSH_DEFAULT_MODE=agent enables automatic tool execution
114
130
  npcsh_cmd = (
115
131
  f'{env_prefix}'
116
132
  f'NPCSH_CHAT_MODEL="{model}" '
117
133
  f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
118
134
  f'NPCSH_STREAM_OUTPUT=0 '
119
- f'npc {escaped_instruction} '
135
+ f'NPCSH_DEFAULT_MODE=agent '
136
+ f'npc --npc corca {escaped_instruction} '
120
137
  f'2>&1 | tee {shlex.quote(output_file)}'
121
138
  )
122
139
 
123
140
  commands.append(ExecInput(
124
- cmd=npcsh_cmd,
125
- timeout=600, # 10 minute timeout for complex tasks
141
+ command=npcsh_cmd,
142
+ timeout_sec=600, # 10 minute timeout for complex tasks
126
143
  ))
127
144
 
128
145
  return commands
@@ -198,7 +215,16 @@ class NpcshAgentWithNpc(NpcshAgent):
198
215
 
199
216
  def create_run_agent_commands(self, instruction: str) -> list:
200
217
  """Create commands using a specific NPC."""
201
- escaped_instruction = shlex.quote(instruction)
218
+ # Wrap the instruction with explicit jinx usage directions
219
+ tool_instruction = f"""You have access to jinxs including edit_file (for writing/creating files), sh (for running shell commands), and python (for running Python code).
220
+
221
+ IMPORTANT: You MUST use these jinxs to complete the task. Do NOT just output code as text - use the edit_file jinx to actually write files to disk.
222
+
223
+ Task: {instruction}
224
+
225
+ Remember: Use edit_file to write any code files. Use sh to run shell commands like gcc, make, etc."""
226
+
227
+ escaped_instruction = shlex.quote(tool_instruction)
202
228
  model_name = self.model_name
203
229
 
204
230
  if model_name and "/" in model_name:
@@ -240,23 +266,31 @@ class NpcshAgentWithNpc(NpcshAgent):
240
266
  commands = []
241
267
 
242
268
  commands.append(ExecInput(
243
- cmd=f"mkdir -p {shlex.quote(output_dir)}",
244
- timeout=30
269
+ command=f"mkdir -p {shlex.quote(output_dir)}",
270
+ timeout_sec=30
271
+ ))
272
+
273
+ # Create .npcsh_global file to use global team and avoid interactive prompts
274
+ commands.append(ExecInput(
275
+ command="touch /app/.npcsh_global",
276
+ timeout_sec=10
245
277
  ))
246
278
 
247
279
  # Use specific NPC with --npc flag
280
+ # NPCSH_DEFAULT_MODE=agent enables automatic tool execution
248
281
  npcsh_cmd = (
249
282
  f'{env_prefix}'
250
283
  f'NPCSH_CHAT_MODEL="{model}" '
251
284
  f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
252
285
  f'NPCSH_STREAM_OUTPUT=0 '
286
+ f'NPCSH_DEFAULT_MODE=agent '
253
287
  f'npc --npc {self.npc_name} {escaped_instruction} '
254
288
  f'2>&1 | tee {shlex.quote(output_file)}'
255
289
  )
256
290
 
257
291
  commands.append(ExecInput(
258
- cmd=npcsh_cmd,
259
- timeout=600,
292
+ command=npcsh_cmd,
293
+ timeout_sec=600,
260
294
  ))
261
295
 
262
296
  return commands