npcsh 1.1.3__py3-none-any.whl → 1.1.5__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 +48 -64
- npcsh/npc_team/corca_example.png +0 -0
- npcsh/npc_team/jinxs/{python_executor.jinx → code/python.jinx} +1 -1
- npcsh/npc_team/jinxs/{bash_executer.jinx → code/sh.jinx} +1 -1
- npcsh/npc_team/jinxs/code/sql.jinx +18 -0
- npcsh/npc_team/jinxs/modes/alicanto.jinx +88 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +28 -0
- npcsh/npc_team/jinxs/modes/guac.jinx +46 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +57 -0
- npcsh/npc_team/jinxs/modes/pti.jinx +28 -0
- npcsh/npc_team/jinxs/modes/spool.jinx +40 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +81 -0
- npcsh/npc_team/jinxs/modes/yap.jinx +25 -0
- npcsh/npc_team/jinxs/utils/breathe.jinx +20 -0
- npcsh/npc_team/jinxs/utils/core/build.jinx +65 -0
- npcsh/npc_team/jinxs/utils/core/compile.jinx +50 -0
- npcsh/npc_team/jinxs/utils/core/help.jinx +52 -0
- npcsh/npc_team/jinxs/utils/core/init.jinx +41 -0
- npcsh/npc_team/jinxs/utils/core/jinxs.jinx +32 -0
- npcsh/npc_team/jinxs/utils/core/set.jinx +40 -0
- npcsh/npc_team/jinxs/{edit_file.jinx → utils/edit_file.jinx} +1 -1
- npcsh/npc_team/jinxs/utils/flush.jinx +39 -0
- npcsh/npc_team/jinxs/utils/npc-studio.jinx +82 -0
- npcsh/npc_team/jinxs/utils/ots.jinx +92 -0
- npcsh/npc_team/jinxs/utils/plan.jinx +33 -0
- npcsh/npc_team/jinxs/utils/roll.jinx +66 -0
- npcsh/npc_team/jinxs/utils/sample.jinx +56 -0
- npcsh/npc_team/jinxs/utils/search/brainblast.jinx +51 -0
- npcsh/npc_team/jinxs/utils/search/rag.jinx +70 -0
- npcsh/npc_team/jinxs/utils/search/search.jinx +192 -0
- npcsh/npc_team/jinxs/utils/serve.jinx +29 -0
- npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
- npcsh/npc_team/jinxs/utils/trigger.jinx +36 -0
- npcsh/npc_team/jinxs/utils/vixynt.jinx +129 -0
- npcsh/npcsh.py +14 -12
- npcsh/routes.py +80 -1420
- npcsh-1.1.5.data/data/npcsh/npc_team/alicanto.jinx +88 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/brainblast.jinx +51 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/breathe.jinx +20 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/build.jinx +65 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/compile.jinx +50 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/corca.jinx +28 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/edit_file.jinx +1 -1
- npcsh-1.1.5.data/data/npcsh/npc_team/flush.jinx +39 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/guac.jinx +46 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/help.jinx +52 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/init.jinx +41 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/jinxs.jinx +32 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/npc-studio.jinx +82 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/ots.jinx +92 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/plan.jinx +33 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/plonk.jinx +57 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/pti.jinx +28 -0
- npcsh-1.1.3.data/data/npcsh/npc_team/python_executor.jinx → npcsh-1.1.5.data/data/npcsh/npc_team/python.jinx +1 -1
- npcsh-1.1.5.data/data/npcsh/npc_team/rag.jinx +70 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/roll.jinx +66 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/sample.jinx +56 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/search.jinx +192 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/serve.jinx +29 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/set.jinx +40 -0
- npcsh-1.1.3.data/data/npcsh/npc_team/bash_executer.jinx → npcsh-1.1.5.data/data/npcsh/npc_team/sh.jinx +1 -1
- npcsh-1.1.5.data/data/npcsh/npc_team/sleep.jinx +116 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/spool.jinx +40 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/sql.jinx +18 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/trigger.jinx +36 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/vixynt.jinx +129 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/wander.jinx +81 -0
- npcsh-1.1.5.data/data/npcsh/npc_team/yap.jinx +25 -0
- {npcsh-1.1.3.dist-info → npcsh-1.1.5.dist-info}/METADATA +1 -1
- npcsh-1.1.5.dist-info/RECORD +132 -0
- npcsh/npc_team/jinxs/image_generation.jinx +0 -29
- npcsh/npc_team/jinxs/internet_search.jinx +0 -31
- npcsh/npc_team/jinxs/screen_cap.jinx +0 -25
- npcsh-1.1.3.data/data/npcsh/npc_team/image_generation.jinx +0 -29
- npcsh-1.1.3.data/data/npcsh/npc_team/internet_search.jinx +0 -31
- npcsh-1.1.3.data/data/npcsh/npc_team/screen_cap.jinx +0 -25
- npcsh-1.1.3.dist-info/RECORD +0 -78
- /npcsh/npc_team/jinxs/{kg_search.jinx → utils/search/kg_search.jinx} +0 -0
- /npcsh/npc_team/jinxs/{memory_search.jinx → utils/search/memory_search.jinx} +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/kg_search.jinx +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/memory_search.jinx +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.3.data → npcsh-1.1.5.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.3.dist-info → npcsh-1.1.5.dist-info}/WHEEL +0 -0
- {npcsh-1.1.3.dist-info → npcsh-1.1.5.dist-info}/entry_points.txt +0 -0
- {npcsh-1.1.3.dist-info → npcsh-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.3.dist-info → npcsh-1.1.5.dist-info}/top_level.txt +0 -0
npcsh/_state.py
CHANGED
|
@@ -1875,17 +1875,15 @@ def execute_slash_command(command: str,
|
|
|
1875
1875
|
all_command_parts = shlex.split(command)
|
|
1876
1876
|
command_name = all_command_parts[0].lstrip('/')
|
|
1877
1877
|
|
|
1878
|
-
|
|
1879
1878
|
if command_name in ['n', 'npc']:
|
|
1880
1879
|
npc_to_switch_to = all_command_parts[1] if len(all_command_parts) > 1 else None
|
|
1881
1880
|
if npc_to_switch_to and state.team and npc_to_switch_to in state.team.npcs:
|
|
1882
1881
|
state.npc = state.team.npcs[npc_to_switch_to]
|
|
1883
|
-
return state, f"Switched to NPC: {npc_to_switch_to}"
|
|
1882
|
+
return state, {"output": f"Switched to NPC: {npc_to_switch_to}", "messages": state.messages}
|
|
1884
1883
|
else:
|
|
1885
1884
|
available_npcs = list(state.team.npcs.keys()) if state.team else []
|
|
1886
|
-
return state, colored(f"NPC '{npc_to_switch_to}' not found. Available NPCs: {', '.join(available_npcs)}", "red")
|
|
1885
|
+
return state, {"output": colored(f"NPC '{npc_to_switch_to}' not found. Available NPCs: {', '.join(available_npcs)}", "red"), "messages": state.messages}
|
|
1887
1886
|
|
|
1888
|
-
|
|
1889
1887
|
handler = router.get_route(command_name)
|
|
1890
1888
|
if handler:
|
|
1891
1889
|
parsed_flags, positional_args = parse_generic_command_flags(all_command_parts[1:])
|
|
@@ -1901,12 +1899,10 @@ def execute_slash_command(command: str,
|
|
|
1901
1899
|
'positional_args': positional_args,
|
|
1902
1900
|
'plonk_context': state.team.shared_context.get('PLONK_CONTEXT') if state.team and hasattr(state.team, 'shared_context') else None,
|
|
1903
1901
|
|
|
1904
|
-
|
|
1905
1902
|
'model': state.npc.model if isinstance(state.npc, NPC) and state.npc.model else state.chat_model,
|
|
1906
1903
|
'provider': state.npc.provider if isinstance(state.npc, NPC) and state.npc.provider else state.chat_provider,
|
|
1907
1904
|
'npc': state.npc,
|
|
1908
1905
|
|
|
1909
|
-
|
|
1910
1906
|
'sprovider': state.search_provider,
|
|
1911
1907
|
'emodel': state.embedding_model,
|
|
1912
1908
|
'eprovider': state.embedding_provider,
|
|
@@ -1927,7 +1923,6 @@ def execute_slash_command(command: str,
|
|
|
1927
1923
|
|
|
1928
1924
|
render_markdown(f'- Calling {command_name} handler {kwarg_part} ')
|
|
1929
1925
|
|
|
1930
|
-
|
|
1931
1926
|
if 'model' in normalized_flags and 'provider' not in normalized_flags:
|
|
1932
1927
|
inferred_provider = lookup_provider(normalized_flags['model'])
|
|
1933
1928
|
if inferred_provider:
|
|
@@ -1943,20 +1938,22 @@ def execute_slash_command(command: str,
|
|
|
1943
1938
|
handler_kwargs.update(normalized_flags)
|
|
1944
1939
|
|
|
1945
1940
|
try:
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
if isinstance(
|
|
1949
|
-
state.messages =
|
|
1950
|
-
return state,
|
|
1941
|
+
result = handler(command=command, **handler_kwargs)
|
|
1942
|
+
|
|
1943
|
+
if isinstance(result, dict):
|
|
1944
|
+
state.messages = result.get("messages", state.messages)
|
|
1945
|
+
return state, result
|
|
1946
|
+
elif isinstance(result, str):
|
|
1947
|
+
return state, {"output": result, "messages": state.messages}
|
|
1951
1948
|
else:
|
|
1952
|
-
return state,
|
|
1949
|
+
return state, {"output": str(result), "messages": state.messages}
|
|
1950
|
+
|
|
1953
1951
|
except Exception as e:
|
|
1954
1952
|
import traceback
|
|
1955
1953
|
print(f"Error executing slash command '{command_name}':", file=sys.stderr)
|
|
1956
1954
|
traceback.print_exc()
|
|
1957
|
-
return state, colored(f"Error executing slash command '{command_name}': {e}", "red")
|
|
1955
|
+
return state, {"output": colored(f"Error executing slash command '{command_name}': {e}", "red"), "messages": state.messages}
|
|
1958
1956
|
|
|
1959
|
-
|
|
1960
1957
|
active_npc = state.npc if isinstance(state.npc, NPC) else None
|
|
1961
1958
|
jinx_to_execute = None
|
|
1962
1959
|
executor = None
|
|
@@ -1970,14 +1967,12 @@ def execute_slash_command(command: str,
|
|
|
1970
1967
|
if jinx_to_execute:
|
|
1971
1968
|
args = all_command_parts[1:]
|
|
1972
1969
|
try:
|
|
1973
|
-
|
|
1974
1970
|
input_values = {}
|
|
1975
1971
|
if hasattr(jinx_to_execute, 'inputs') and jinx_to_execute.inputs:
|
|
1976
1972
|
for i, input_name in enumerate(jinx_to_execute.inputs):
|
|
1977
1973
|
if i < len(args):
|
|
1978
1974
|
input_values[input_name] = args[i]
|
|
1979
1975
|
|
|
1980
|
-
|
|
1981
1976
|
if isinstance(executor, NPC):
|
|
1982
1977
|
jinx_output = jinx_to_execute.execute(
|
|
1983
1978
|
input_values=input_values,
|
|
@@ -1994,25 +1989,24 @@ def execute_slash_command(command: str,
|
|
|
1994
1989
|
)
|
|
1995
1990
|
if isinstance(jinx_output, dict) and 'messages' in jinx_output:
|
|
1996
1991
|
state.messages = jinx_output['messages']
|
|
1997
|
-
return state,
|
|
1992
|
+
return state, jinx_output
|
|
1998
1993
|
elif isinstance(jinx_output, dict):
|
|
1999
|
-
return state, str(jinx_output.get('output', jinx_output))
|
|
2000
|
-
else:
|
|
2001
1994
|
return state, jinx_output
|
|
1995
|
+
else:
|
|
1996
|
+
return state, {"output": str(jinx_output), "messages": state.messages}
|
|
2002
1997
|
|
|
2003
1998
|
except Exception as e:
|
|
2004
1999
|
import traceback
|
|
2005
2000
|
print(f"Error executing jinx '{command_name}':", file=sys.stderr)
|
|
2006
2001
|
traceback.print_exc()
|
|
2007
|
-
return state, colored(f"Error executing jinx '{command_name}': {e}", "red")
|
|
2002
|
+
return state, {"output": colored(f"Error executing jinx '{command_name}': {e}", "red"), "messages": state.messages}
|
|
2003
|
+
|
|
2008
2004
|
if state.team and command_name in state.team.npcs:
|
|
2009
2005
|
new_npc = state.team.npcs[command_name]
|
|
2010
2006
|
state.npc = new_npc
|
|
2011
|
-
return state, f"Switched to NPC: {new_npc.name}"
|
|
2012
|
-
|
|
2013
|
-
return state, colored(f"Unknown slash command, jinx, or NPC: {command_name}", "red")
|
|
2014
|
-
|
|
2007
|
+
return state, {"output": f"Switched to NPC: {new_npc.name}", "messages": state.messages}
|
|
2015
2008
|
|
|
2009
|
+
return state, {"output": colored(f"Unknown slash command, jinx, or NPC: {command_name}", "red"), "messages": state.messages}
|
|
2016
2010
|
|
|
2017
2011
|
|
|
2018
2012
|
def process_pipeline_command(
|
|
@@ -2190,7 +2184,7 @@ def check_mode_switch(command:str , state: ShellState):
|
|
|
2190
2184
|
def execute_command(
|
|
2191
2185
|
command: str,
|
|
2192
2186
|
state: ShellState,
|
|
2193
|
-
review =
|
|
2187
|
+
review = False,
|
|
2194
2188
|
router = None,
|
|
2195
2189
|
command_history = None,
|
|
2196
2190
|
) -> Tuple[ShellState, Any]:
|
|
@@ -2319,7 +2313,6 @@ def execute_command(
|
|
|
2319
2313
|
return state, response['response']
|
|
2320
2314
|
|
|
2321
2315
|
def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
2322
|
-
|
|
2323
2316
|
setup_npcsh_config()
|
|
2324
2317
|
|
|
2325
2318
|
db_path = os.getenv("NPCSH_DB_PATH", HISTORY_DB_DEFAULT_PATH)
|
|
@@ -2327,14 +2320,11 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2327
2320
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
2328
2321
|
command_history = CommandHistory(db_path)
|
|
2329
2322
|
|
|
2330
|
-
|
|
2331
2323
|
if not is_npcsh_initialized():
|
|
2332
2324
|
print("Initializing NPCSH...")
|
|
2333
2325
|
initialize_base_npcs_if_needed(db_path)
|
|
2334
2326
|
print("NPCSH initialization complete. Restart or source ~/.npcshrc.")
|
|
2335
2327
|
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
2328
|
try:
|
|
2339
2329
|
history_file = setup_readline()
|
|
2340
2330
|
atexit.register(save_readline_history)
|
|
@@ -2397,7 +2387,6 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2397
2387
|
team_dir = global_team_path
|
|
2398
2388
|
default_forenpc_name = "sibiji"
|
|
2399
2389
|
|
|
2400
|
-
|
|
2401
2390
|
team_ctx = {}
|
|
2402
2391
|
team_ctx_path = get_team_ctx_path(team_dir)
|
|
2403
2392
|
if team_ctx_path:
|
|
@@ -2410,34 +2399,12 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2410
2399
|
|
|
2411
2400
|
print('forenpc_name:', forenpc_name)
|
|
2412
2401
|
|
|
2413
|
-
if team_ctx.get("use_global_jinxs", False):
|
|
2414
|
-
jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
|
|
2415
|
-
else:
|
|
2416
|
-
jinxs_dir = os.path.join(team_dir, "jinxs")
|
|
2417
|
-
|
|
2418
|
-
jinxs_list = load_jinxs_from_directory(jinxs_dir)
|
|
2419
|
-
jinxs_dict = {jinx.jinx_name: jinx for jinx in jinxs_list}
|
|
2420
|
-
|
|
2421
|
-
forenpc_obj = None
|
|
2422
2402
|
forenpc_path = os.path.join(team_dir, f"{forenpc_name}.npc")
|
|
2423
|
-
|
|
2424
2403
|
print('forenpc_path:', forenpc_path)
|
|
2425
2404
|
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
db_conn=command_history.engine)
|
|
2430
|
-
if forenpc_obj.model is None:
|
|
2431
|
-
forenpc_obj.model= team_ctx.get("model", initial_state.chat_model)
|
|
2432
|
-
if forenpc_obj.provider is None:
|
|
2433
|
-
forenpc_obj.provider=team_ctx.get('provider', initial_state.chat_provider)
|
|
2434
|
-
|
|
2435
|
-
else:
|
|
2436
|
-
print(f"Warning: Forenpc file '{forenpc_name}.npc' not found in {team_dir}.")
|
|
2437
|
-
|
|
2438
|
-
team = Team(team_path=team_dir,
|
|
2439
|
-
forenpc=forenpc_obj,
|
|
2440
|
-
jinxs=jinxs_dict)
|
|
2405
|
+
team = Team(team_path=team_dir, db_conn=command_history.engine)
|
|
2406
|
+
|
|
2407
|
+
forenpc_obj = team.forenpc if hasattr(team, 'forenpc') and team.forenpc else None
|
|
2441
2408
|
|
|
2442
2409
|
for npc_name, npc_obj in team.npcs.items():
|
|
2443
2410
|
if not npc_obj.model:
|
|
@@ -2445,12 +2412,12 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2445
2412
|
if not npc_obj.provider:
|
|
2446
2413
|
npc_obj.provider = initial_state.chat_provider
|
|
2447
2414
|
|
|
2448
|
-
|
|
2449
2415
|
if team.forenpc and isinstance(team.forenpc, NPC):
|
|
2450
2416
|
if not team.forenpc.model:
|
|
2451
2417
|
team.forenpc.model = initial_state.chat_model
|
|
2452
2418
|
if not team.forenpc.provider:
|
|
2453
2419
|
team.forenpc.provider = initial_state.chat_provider
|
|
2420
|
+
|
|
2454
2421
|
team_name_from_ctx = team_ctx.get("name")
|
|
2455
2422
|
if team_name_from_ctx:
|
|
2456
2423
|
team.name = team_name_from_ctx
|
|
@@ -2464,11 +2431,19 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
|
|
|
2464
2431
|
else:
|
|
2465
2432
|
team.name = "npcsh"
|
|
2466
2433
|
|
|
2467
|
-
|
|
2468
2434
|
return command_history, team, forenpc_obj
|
|
2469
2435
|
|
|
2470
|
-
|
|
2471
|
-
|
|
2436
|
+
def initialize_router_with_jinxs(team, router):
|
|
2437
|
+
"""Load global and team Jinxs into router"""
|
|
2438
|
+
global_jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
|
|
2439
|
+
router.load_jinx_routes(global_jinxs_dir)
|
|
2440
|
+
|
|
2441
|
+
if team and team.team_path:
|
|
2442
|
+
team_jinxs_dir = os.path.join(team.team_path, "jinxs")
|
|
2443
|
+
if os.path.exists(team_jinxs_dir):
|
|
2444
|
+
router.load_jinx_routes(team_jinxs_dir)
|
|
2445
|
+
|
|
2446
|
+
return router
|
|
2472
2447
|
|
|
2473
2448
|
from npcpy.memory.memory_processor import memory_approval_ui
|
|
2474
2449
|
from npcpy.ft.memory_trainer import MemoryTrainer
|
|
@@ -2688,13 +2663,22 @@ def process_result(
|
|
|
2688
2663
|
result_state.attachments = None
|
|
2689
2664
|
|
|
2690
2665
|
final_output_str = None
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2666
|
+
|
|
2667
|
+
if isinstance(output, dict):
|
|
2668
|
+
output_content = output.get('output')
|
|
2669
|
+
model_for_stream = output.get('model', active_npc.model)
|
|
2670
|
+
provider_for_stream = output.get('provider', active_npc.provider)
|
|
2671
|
+
else:
|
|
2672
|
+
output_content = output
|
|
2673
|
+
model_for_stream = active_npc.model
|
|
2674
|
+
provider_for_stream = active_npc.provider
|
|
2694
2675
|
|
|
2695
2676
|
print('\n')
|
|
2696
2677
|
if user_input == '/help':
|
|
2697
|
-
|
|
2678
|
+
if isinstance(output_content, str):
|
|
2679
|
+
render_markdown(output_content)
|
|
2680
|
+
else:
|
|
2681
|
+
render_markdown(str(output_content))
|
|
2698
2682
|
elif result_state.stream_output:
|
|
2699
2683
|
final_output_str = print_and_process_stream_with_markdown(
|
|
2700
2684
|
output_content,
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
jinx_name: sql_executor
|
|
2
|
+
description: Execute queries on the ~/npcsh_history.db to pull data. The database
|
|
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
|
|
5
|
+
inputs:
|
|
6
|
+
- sql_query
|
|
7
|
+
- user_query
|
|
8
|
+
- interpret: true
|
|
9
|
+
steps:
|
|
10
|
+
- engine: python
|
|
11
|
+
code: |
|
|
12
|
+
import pandas as pd
|
|
13
|
+
query = "{{ sql_query }}"
|
|
14
|
+
try:
|
|
15
|
+
df = pd.read_sql_query(query, npc.db_conn)
|
|
16
|
+
except Exception as e:
|
|
17
|
+
df = pd.DataFrame({'Error': [str(e)]})
|
|
18
|
+
output = df.to_string()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
jinx_name: "alicanto"
|
|
2
|
+
description: "Conduct deep research with multiple perspectives, identifying gold insights and cliff warnings"
|
|
3
|
+
inputs:
|
|
4
|
+
- query: "" # Required research query.
|
|
5
|
+
- num_npcs: 5 # Number of NPCs to involve in research.
|
|
6
|
+
- depth: 3 # Depth of research.
|
|
7
|
+
- model: "" # LLM model to use. Defaults to NPCSH_CHAT_MODEL or NPC's model.
|
|
8
|
+
- provider: "" # LLM provider to use. Defaults to NPCSH_CHAT_PROVIDER or NPC's provider.
|
|
9
|
+
- max_steps: 20 # Maximum number of steps in Alicanto research.
|
|
10
|
+
- skip_research: True # Whether to skip the research phase.
|
|
11
|
+
- exploration: "" # Exploration factor (float).
|
|
12
|
+
- creativity: "" # Creativity factor (float).
|
|
13
|
+
- format: "" # Output format (report, summary, full).
|
|
14
|
+
steps:
|
|
15
|
+
- name: "conduct_alicanto_research"
|
|
16
|
+
engine: "python"
|
|
17
|
+
code: |
|
|
18
|
+
import traceback
|
|
19
|
+
import logging
|
|
20
|
+
from npcsh.alicanto import alicanto
|
|
21
|
+
# Assuming NPCSH_CHAT_MODEL and NPCSH_CHAT_PROVIDER are accessible
|
|
22
|
+
|
|
23
|
+
query = context.get('query')
|
|
24
|
+
num_npcs = int(context.get('num_npcs', 5)) # Ensure int type
|
|
25
|
+
depth = int(context.get('depth', 3)) # Ensure int type
|
|
26
|
+
llm_model = context.get('model')
|
|
27
|
+
llm_provider = context.get('provider')
|
|
28
|
+
max_steps = int(context.get('max_steps', 20)) # Ensure int type
|
|
29
|
+
skip_research = context.get('skip_research', True)
|
|
30
|
+
exploration_factor = context.get('exploration')
|
|
31
|
+
creativity_factor = context.get('creativity')
|
|
32
|
+
output_format = context.get('format')
|
|
33
|
+
output_messages = context.get('messages', [])
|
|
34
|
+
current_npc = context.get('npc')
|
|
35
|
+
|
|
36
|
+
if not query or not query.strip():
|
|
37
|
+
context['output'] = "Usage: /alicanto <research query> [--num-npcs N] [--depth N] [--exploration 0.3] [--creativity 0.5] [--format report|summary|full]"
|
|
38
|
+
context['messages'] = output_messages
|
|
39
|
+
exit()
|
|
40
|
+
|
|
41
|
+
# Fallback for model/provider if not explicitly set in Jinx inputs
|
|
42
|
+
if not llm_model and current_npc and current_npc.model:
|
|
43
|
+
llm_model = current_npc.model
|
|
44
|
+
if not llm_provider and current_npc and current_npc.provider:
|
|
45
|
+
llm_provider = current_npc.provider
|
|
46
|
+
|
|
47
|
+
# Final fallbacks (these would ideally come from npcsh._state config)
|
|
48
|
+
# Assuming NPCSH_CHAT_MODEL and NPCSH_CHAT_PROVIDER exist and are imported implicitly or set by environment
|
|
49
|
+
# Hardcoding defaults for demonstration if not available through NPC or _state
|
|
50
|
+
if not llm_model: llm_model = "gemini-1.5-pro"
|
|
51
|
+
if not llm_provider: llm_provider = "gemini"
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
logging.info(f"Starting Alicanto research on: {query}")
|
|
55
|
+
|
|
56
|
+
alicanto_kwargs = {
|
|
57
|
+
'query': query,
|
|
58
|
+
'num_npcs': num_npcs,
|
|
59
|
+
'depth': depth,
|
|
60
|
+
'model': llm_model,
|
|
61
|
+
'provider': llm_provider,
|
|
62
|
+
'max_steps': max_steps,
|
|
63
|
+
'skip_research': skip_research,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if exploration_factor: alicanto_kwargs['exploration_factor'] = float(exploration_factor)
|
|
67
|
+
if creativity_factor: alicanto_kwargs['creativity_factor'] = float(creativity_factor)
|
|
68
|
+
if output_format: alicanto_kwargs['output_format'] = output_format
|
|
69
|
+
|
|
70
|
+
result = alicanto(**alicanto_kwargs)
|
|
71
|
+
|
|
72
|
+
output_result = ""
|
|
73
|
+
if isinstance(result, dict):
|
|
74
|
+
if "integration" in result:
|
|
75
|
+
output_result = result["integration"]
|
|
76
|
+
else:
|
|
77
|
+
output_result = "Alicanto research completed. Full results available in returned data."
|
|
78
|
+
else:
|
|
79
|
+
output_result = str(result)
|
|
80
|
+
|
|
81
|
+
context['output'] = output_result
|
|
82
|
+
context['messages'] = output_messages
|
|
83
|
+
context['alicanto_result'] = result # Store full result in context
|
|
84
|
+
except Exception as e:
|
|
85
|
+
traceback.print_exc()
|
|
86
|
+
logging.error(f"Error during Alicanto research: {e}")
|
|
87
|
+
context['output'] = f"Error during Alicanto research: {e}"
|
|
88
|
+
context['messages'] = output_messages
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
jinx_name: "corca"
|
|
2
|
+
description: "Enter the Corca MCP-powered agentic shell. Usage: /corca [--mcp-server-path path]"
|
|
3
|
+
inputs:
|
|
4
|
+
- command: "/corca" # The full command string, e.g., "/corca --mcp-server-path /tmp/mcp"
|
|
5
|
+
steps:
|
|
6
|
+
- name: "enter_corca"
|
|
7
|
+
engine: "python"
|
|
8
|
+
code: |
|
|
9
|
+
# Assume npcsh._state and enter_corca_mode are accessible in the environment
|
|
10
|
+
|
|
11
|
+
from npcsh._state import initial_state, setup_shell
|
|
12
|
+
from npcsh.corca import enter_corca_mode
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
full_command_str = context.get('command')
|
|
16
|
+
output_messages = context.get('messages', [])
|
|
17
|
+
|
|
18
|
+
command_history, team, default_npc = setup_shell()
|
|
19
|
+
|
|
20
|
+
result = enter_corca_mode(
|
|
21
|
+
command=full_command_str,
|
|
22
|
+
command_history=command_history,
|
|
23
|
+
shell_state=initial_state,
|
|
24
|
+
**context # Pass all context as kwargs to enter_corca_mode as it expects
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
context['output'] = result.get('output', 'Entered Corca mode.')
|
|
28
|
+
context['messages'] = result.get('messages', output_messages)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
jinx_name: "guac"
|
|
2
|
+
description: "Enter guac mode for plotting and data visualization."
|
|
3
|
+
inputs:
|
|
4
|
+
- config_dir: "" # Optional configuration directory.
|
|
5
|
+
- plots_dir: "" # Optional directory for plots.
|
|
6
|
+
- refresh_period: 100 # Refresh period for guac mode.
|
|
7
|
+
- lang: "" # Language setting for guac mode.
|
|
8
|
+
steps:
|
|
9
|
+
- name: "enter_guac"
|
|
10
|
+
engine: "python"
|
|
11
|
+
code: |
|
|
12
|
+
import os
|
|
13
|
+
from sqlalchemy import create_engine
|
|
14
|
+
from npcpy.npc_compiler import NPC, Team
|
|
15
|
+
from npcsh.guac import enter_guac_mode
|
|
16
|
+
|
|
17
|
+
config_dir = context.get('config_dir')
|
|
18
|
+
plots_dir = context.get('plots_dir')
|
|
19
|
+
refresh_period = context.get('refresh_period')
|
|
20
|
+
lang = context.get('lang')
|
|
21
|
+
output_messages = context.get('messages', [])
|
|
22
|
+
|
|
23
|
+
db_path = os.path.expanduser('~/npcsh_history.db')
|
|
24
|
+
db_conn = create_engine(f'sqlite:///{db_path}')
|
|
25
|
+
|
|
26
|
+
npc_file = os.path.expanduser('~/.npcsh/guac/npc_team/guac.npc')
|
|
27
|
+
npc_team_dir = os.path.expanduser('~/.npcsh/guac/npc_team/')
|
|
28
|
+
|
|
29
|
+
# Ensure directories exist for guac NPC/Team
|
|
30
|
+
os.makedirs(os.path.dirname(npc_file), exist_ok=True)
|
|
31
|
+
|
|
32
|
+
guac_npc = NPC(file=npc_file, db_conn=db_conn)
|
|
33
|
+
guac_team = Team(npc_team_dir, db_conn=db_conn)
|
|
34
|
+
|
|
35
|
+
enter_guac_mode(
|
|
36
|
+
npc=guac_npc,
|
|
37
|
+
team=guac_team,
|
|
38
|
+
config_dir=config_dir,
|
|
39
|
+
plots_dir=plots_dir,
|
|
40
|
+
npc_team_dir=npc_team_dir,
|
|
41
|
+
refresh_period=int(refresh_period), # Ensure int type
|
|
42
|
+
lang=lang
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
context['output'] = 'Exiting Guac Mode'
|
|
46
|
+
context['messages'] = output_messages
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
jinx_name: "plonk"
|
|
2
|
+
description: "Use vision model to interact with GUI. Usage: /plonk <task description>"
|
|
3
|
+
inputs:
|
|
4
|
+
- task_description: "" # Required task description for GUI interaction.
|
|
5
|
+
- vmodel: "" # Vision model to use. Defaults to NPCSH_VISION_MODEL or NPC's model.
|
|
6
|
+
- vprovider: "" # Vision model provider. Defaults to NPCSH_VISION_PROVIDER or NPC's provider.
|
|
7
|
+
steps:
|
|
8
|
+
- name: "execute_plonk"
|
|
9
|
+
engine: "python"
|
|
10
|
+
code: |
|
|
11
|
+
import traceback
|
|
12
|
+
from npcsh.plonk import execute_plonk_command, format_plonk_summary
|
|
13
|
+
# Assuming NPCSH_VISION_MODEL and NPCSH_VISION_PROVIDER are accessible
|
|
14
|
+
|
|
15
|
+
task_description = context.get('task_description')
|
|
16
|
+
vision_model = context.get('vmodel')
|
|
17
|
+
vision_provider = context.get('vprovider')
|
|
18
|
+
plonk_context = context.get('plonk_context') # Passed from original context
|
|
19
|
+
current_npc = context.get('npc')
|
|
20
|
+
output_messages = context.get('messages', [])
|
|
21
|
+
|
|
22
|
+
if not task_description or not task_description.strip():
|
|
23
|
+
context['output'] = "Usage: /plonk <task_description> [--vmodel model_name] [--vprovider provider_name]"
|
|
24
|
+
context['messages'] = output_messages
|
|
25
|
+
exit()
|
|
26
|
+
|
|
27
|
+
# Fallback for model/provider if not explicitly set in Jinx inputs
|
|
28
|
+
if not vision_model and current_npc and current_npc.model:
|
|
29
|
+
vision_model = current_npc.model
|
|
30
|
+
if not vision_provider and current_npc and current_npc.provider:
|
|
31
|
+
vision_provider = current_npc.provider
|
|
32
|
+
|
|
33
|
+
# Final fallbacks (these would ideally come from npcsh._state config)
|
|
34
|
+
if not vision_model: vision_model = "gemini-1.5-pro-vision" # Example default
|
|
35
|
+
if not vision_provider: vision_provider = "gemini" # Example default
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
summary_data = execute_plonk_command(
|
|
39
|
+
request=task_description,
|
|
40
|
+
model=vision_model,
|
|
41
|
+
provider=vision_provider,
|
|
42
|
+
npc=current_npc,
|
|
43
|
+
plonk_context=plonk_context,
|
|
44
|
+
debug=True # Assuming debug is often desired for plonk
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if summary_data and isinstance(summary_data, list):
|
|
48
|
+
output_report = format_plonk_summary(summary_data)
|
|
49
|
+
context['output'] = output_report
|
|
50
|
+
else:
|
|
51
|
+
context['output'] = "Plonk command did not complete within the maximum number of iterations."
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
traceback.print_exc()
|
|
55
|
+
context['output'] = f"Error executing plonk command: {e}"
|
|
56
|
+
|
|
57
|
+
context['messages'] = output_messages
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
jinx_name: "pti"
|
|
2
|
+
description: "Enter Pardon-The-Interruption mode for human-in-the-loop reasoning."
|
|
3
|
+
inputs:
|
|
4
|
+
- command_args: "" # The full command string or specific arguments for PTI mode.
|
|
5
|
+
steps:
|
|
6
|
+
- name: "enter_pti"
|
|
7
|
+
engine: "python"
|
|
8
|
+
code: |
|
|
9
|
+
import traceback
|
|
10
|
+
from npcsh.pti import enter_pti_mode
|
|
11
|
+
|
|
12
|
+
command_args = context.get('command_args', '') # The full command string from router
|
|
13
|
+
output_messages = context.get('messages', [])
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
# enter_pti_mode likely expects the full command string for its own parsing
|
|
17
|
+
result = enter_pti_mode(command=command_args, **context)
|
|
18
|
+
|
|
19
|
+
if isinstance(result, dict):
|
|
20
|
+
context['output'] = result.get('output', 'Entered PTI mode.')
|
|
21
|
+
context['messages'] = result.get('messages', output_messages)
|
|
22
|
+
else:
|
|
23
|
+
context['output'] = str(result)
|
|
24
|
+
context['messages'] = output_messages
|
|
25
|
+
except Exception as e:
|
|
26
|
+
traceback.print_exc()
|
|
27
|
+
context['output'] = f"Error entering pti mode: {e}"
|
|
28
|
+
context['messages'] = output_messages
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
jinx_name: "spool"
|
|
2
|
+
description: "Enter interactive chat (spool) mode"
|
|
3
|
+
inputs: [] # Spool mode typically takes its parameters directly from the environment/kwargs
|
|
4
|
+
steps:
|
|
5
|
+
- name: "enter_spool"
|
|
6
|
+
engine: "python"
|
|
7
|
+
code: |
|
|
8
|
+
import traceback
|
|
9
|
+
from npcpy.npc_compiler import NPC, Team
|
|
10
|
+
from npcsh.spool import enter_spool_mode
|
|
11
|
+
|
|
12
|
+
output_messages = context.get('messages', [])
|
|
13
|
+
current_npc = context.get('npc')
|
|
14
|
+
current_team = context.get('team')
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
# Handle potential string NPC name if passed from CLI
|
|
18
|
+
if isinstance(current_npc, str) and current_team:
|
|
19
|
+
npc_name = current_npc
|
|
20
|
+
if npc_name in current_team.npcs:
|
|
21
|
+
current_npc = current_team.npcs[npc_name]
|
|
22
|
+
else:
|
|
23
|
+
context['output'] = f"Error: NPC '{npc_name}' not found in team. Available NPCs: {', '.join(current_team.npcs.keys())}"
|
|
24
|
+
context['messages'] = output_messages
|
|
25
|
+
exit()
|
|
26
|
+
context['npc'] = current_npc # Ensure the NPC object is updated in context
|
|
27
|
+
|
|
28
|
+
result = enter_spool_mode(**context) # Pass all context as kwargs
|
|
29
|
+
|
|
30
|
+
if isinstance(result, dict):
|
|
31
|
+
context['output'] = result.get('output', 'Exited Spool Mode.')
|
|
32
|
+
context['messages'] = result.get('messages', output_messages)
|
|
33
|
+
else:
|
|
34
|
+
context['output'] = str(result)
|
|
35
|
+
context['messages'] = output_messages
|
|
36
|
+
|
|
37
|
+
except Exception as e:
|
|
38
|
+
traceback.print_exc()
|
|
39
|
+
context['output'] = f"Error entering spool mode: {e}"
|
|
40
|
+
context['messages'] = output_messages
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
jinx_name: "wander"
|
|
2
|
+
description: "Enter wander mode (experimental)"
|
|
3
|
+
inputs:
|
|
4
|
+
- problem: "" # The problem to wander about.
|
|
5
|
+
- environment: "" # Optional environment for wander mode.
|
|
6
|
+
- low_temp: 0.5 # Low temperature setting for LLM.
|
|
7
|
+
- high_temp: 1.9 # High temperature setting for LLM.
|
|
8
|
+
- interruption_likelihood: 1.0 # Likelihood of interruption.
|
|
9
|
+
- sample_rate: 0.4 # Sample rate.
|
|
10
|
+
- n_high_temp_streams: 5 # Number of high temperature streams.
|
|
11
|
+
- include_events: False # Whether to include events.
|
|
12
|
+
- num_events: 3 # Number of events to include.
|
|
13
|
+
steps:
|
|
14
|
+
- name: "enter_wander"
|
|
15
|
+
engine: "python"
|
|
16
|
+
code: |
|
|
17
|
+
import traceback
|
|
18
|
+
from npcsh.wander import enter_wander_mode
|
|
19
|
+
|
|
20
|
+
problem = context.get('problem')
|
|
21
|
+
environment = context.get('environment')
|
|
22
|
+
low_temp = float(context.get('low_temp', 0.5)) # Ensure float type
|
|
23
|
+
high_temp = float(context.get('high_temp', 1.9)) # Ensure float type
|
|
24
|
+
interruption_likelihood = float(context.get('interruption_likelihood', 1.0)) # Ensure float type
|
|
25
|
+
sample_rate = float(context.get('sample_rate', 0.4)) # Ensure float type
|
|
26
|
+
n_high_temp_streams = int(context.get('n_high_temp_streams', 5)) # Ensure int type
|
|
27
|
+
include_events = context.get('include_events', False) # Boolean type
|
|
28
|
+
num_events = int(context.get('num_events', 3)) # Ensure int type
|
|
29
|
+
|
|
30
|
+
current_npc = context.get('npc')
|
|
31
|
+
llm_model = context.get('model')
|
|
32
|
+
llm_provider = context.get('provider')
|
|
33
|
+
output_messages = context.get('messages', [])
|
|
34
|
+
|
|
35
|
+
if not problem or not problem.strip():
|
|
36
|
+
context['output'] = "Usage: /wander <problem> [key=value...]"
|
|
37
|
+
context['messages'] = output_messages
|
|
38
|
+
exit()
|
|
39
|
+
|
|
40
|
+
# Fallback for model/provider if not explicitly set in Jinx inputs
|
|
41
|
+
if not llm_model and current_npc and current_npc.model:
|
|
42
|
+
llm_model = current_npc.model
|
|
43
|
+
if not llm_provider and current_npc and current_npc.provider:
|
|
44
|
+
llm_provider = current_npc.provider
|
|
45
|
+
|
|
46
|
+
# Final fallbacks (these would ideally come from npcsh._state config)
|
|
47
|
+
if not llm_model: llm_model = "gemini-1.5-pro" # Example default
|
|
48
|
+
if not llm_provider: llm_provider = "gemini" # Example default
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
mode_args = {
|
|
52
|
+
'problem': problem,
|
|
53
|
+
'npc': current_npc,
|
|
54
|
+
'model': llm_model,
|
|
55
|
+
'provider': llm_provider,
|
|
56
|
+
'environment': environment,
|
|
57
|
+
'low_temp': low_temp,
|
|
58
|
+
'high_temp': high_temp,
|
|
59
|
+
'interruption_likelihood': interruption_likelihood,
|
|
60
|
+
'sample_rate': sample_rate,
|
|
61
|
+
'n_high_temp_streams': n_high_temp_streams,
|
|
62
|
+
'include_events': include_events,
|
|
63
|
+
'num_events': num_events
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
result = enter_wander_mode(**mode_args)
|
|
67
|
+
|
|
68
|
+
output_result = ""
|
|
69
|
+
if isinstance(result, list) and result:
|
|
70
|
+
output_result = result[-1].get("insight", "Wander mode session complete.")
|
|
71
|
+
else:
|
|
72
|
+
output_result = str(result) if result else "Wander mode session complete."
|
|
73
|
+
|
|
74
|
+
output_messages.append({"role": "assistant", "content": output_result})
|
|
75
|
+
context['output'] = output_result
|
|
76
|
+
context['messages'] = output_messages
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
traceback.print_exc()
|
|
80
|
+
context['output'] = f"Error during wander mode: {e}"
|
|
81
|
+
context['messages'] = output_messages
|