npcsh 0.3.28__tar.gz → 0.3.29__tar.gz

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 (70) hide show
  1. {npcsh-0.3.28/npcsh.egg-info → npcsh-0.3.29}/PKG-INFO +43 -3
  2. {npcsh-0.3.28 → npcsh-0.3.29}/README.md +42 -2
  3. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/shell_helpers.py +525 -0
  4. {npcsh-0.3.28 → npcsh-0.3.29/npcsh.egg-info}/PKG-INFO +43 -3
  5. {npcsh-0.3.28 → npcsh-0.3.29}/setup.py +1 -1
  6. {npcsh-0.3.28 → npcsh-0.3.29}/LICENSE +0 -0
  7. {npcsh-0.3.28 → npcsh-0.3.29}/MANIFEST.in +0 -0
  8. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/__init__.py +0 -0
  9. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/audio.py +0 -0
  10. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/cli.py +0 -0
  11. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/command_history.py +0 -0
  12. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/conversation.py +0 -0
  13. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/data_models.py +0 -0
  14. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/dataframes.py +0 -0
  15. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/embeddings.py +0 -0
  16. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/helpers.py +0 -0
  17. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/image.py +0 -0
  18. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/image_gen.py +0 -0
  19. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/knowledge_graph.py +0 -0
  20. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/llm_funcs.py +0 -0
  21. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/load_data.py +0 -0
  22. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/main.py +0 -0
  23. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/model_runner.py +0 -0
  24. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_compiler.py +0 -0
  25. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_sysenv.py +0 -0
  26. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/assembly_lines/test_pipeline.py +0 -0
  27. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/corca.npc +0 -0
  28. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/foreman.npc +0 -0
  29. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/npcsh.ctx +0 -0
  30. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/sibiji.npc +0 -0
  31. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/templates/analytics/celona.npc +0 -0
  32. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/templates/hr_support/raone.npc +0 -0
  33. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/templates/humanities/eriane.npc +0 -0
  34. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/templates/it_support/lineru.npc +0 -0
  35. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/templates/marketing/slean.npc +0 -0
  36. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/templates/philosophy/maurawa.npc +0 -0
  37. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/templates/sales/turnic.npc +0 -0
  38. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/templates/software/welxor.npc +0 -0
  39. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/tools/calculator.tool +0 -0
  40. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/tools/generic_search.tool +0 -0
  41. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/tools/image_generation.tool +0 -0
  42. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/tools/local_search.tool +0 -0
  43. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/tools/screen_cap.tool +0 -0
  44. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/npc_team/tools/sql_executor.tool +0 -0
  45. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/plonk.py +0 -0
  46. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/response.py +0 -0
  47. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/search.py +0 -0
  48. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/serve.py +0 -0
  49. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/shell.py +0 -0
  50. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/stream.py +0 -0
  51. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh/video.py +0 -0
  52. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh.egg-info/SOURCES.txt +0 -0
  53. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh.egg-info/dependency_links.txt +0 -0
  54. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh.egg-info/entry_points.txt +0 -0
  55. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh.egg-info/requires.txt +0 -0
  56. {npcsh-0.3.28 → npcsh-0.3.29}/npcsh.egg-info/top_level.txt +0 -0
  57. {npcsh-0.3.28 → npcsh-0.3.29}/setup.cfg +0 -0
  58. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_chromadb.py +0 -0
  59. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_embedding_check.py +0 -0
  60. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_embedding_methods.py +0 -0
  61. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_helpers.py +0 -0
  62. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_knowledge_graph_rag.py +0 -0
  63. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_llm_funcs.py +0 -0
  64. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_networkx_vis.py +0 -0
  65. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_npc_compiler.py +0 -0
  66. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_npcsh.py +0 -0
  67. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_npcteam.py +0 -0
  68. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_shell_helpers.py +0 -0
  69. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_tars.py +0 -0
  70. {npcsh-0.3.28 → npcsh-0.3.29}/tests/test_tool_use.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcsh
3
- Version: 0.3.28
3
+ Version: 0.3.29
4
4
  Summary: npcsh is a command line tool for integrating LLMs into everyday workflows and for orchestrating teams of NPCs.
5
5
  Home-page: https://github.com/cagostino/npcsh
6
6
  Author: Christopher Agostino
@@ -469,6 +469,7 @@ if __name__ == "__main__":
469
469
  ### Linux install
470
470
  ```bash
471
471
 
472
+ # for audio primarily
472
473
  sudo apt-get install espeak
473
474
  sudo apt-get install portaudio19-dev python3-pyaudio
474
475
  sudo apt-get install alsa-base alsa-utils
@@ -476,6 +477,10 @@ sudo apt-get install libcairo2-dev
476
477
  sudo apt-get install libgirepository1.0-dev
477
478
  sudo apt-get install ffmpeg
478
479
 
480
+ # for triggers
481
+ sudo apt install inotify-tools
482
+
483
+
479
484
  #And if you don't have ollama installed, use this:
480
485
  curl -fsSL https://ollama.com/install.sh | sh
481
486
 
@@ -498,11 +503,17 @@ pip install npcsh[all]
498
503
 
499
504
  ### Mac install
500
505
  ```bash
506
+ #mainly for audio
501
507
  brew install portaudio
502
508
  brew install ffmpeg
509
+ brew install pygobject3
510
+
511
+ # for triggers
512
+ brew install ...
513
+
514
+
503
515
  brew install ollama
504
516
  brew services start ollama
505
- brew install pygobject3
506
517
  ollama pull llama3.2
507
518
  ollama pull llava:7b
508
519
  ollama pull nomic-embed-text
@@ -1076,13 +1087,30 @@ npc ots -f test_data/catfight.PNG
1076
1087
  ### Plan : Schedule tasks to be run at regular intervals (under construction)
1077
1088
  Use the /plan macro to schedule tasks to be run at regular intervals.
1078
1089
  ```npcsh
1079
- npcsh> /plan run a rag search on the files in the current directory every 5 minutes
1090
+ npcsh> /plan run a rag search for 'moonbeam' on the files in the current directory every 5 minutes
1080
1091
  ```
1081
1092
 
1093
+ ```npcsh
1094
+ npcsh> /plan record the cpu usage every 5 minutes
1095
+ ```
1096
+
1097
+ ```npcsh
1098
+ npcsh> /plan record the apps that are using the most ram every 5 minutes
1099
+ ```
1100
+
1101
+
1102
+
1103
+
1082
1104
  ```bash
1083
1105
  npc plan -f 30m -t 'task'
1084
1106
  ```
1085
1107
 
1108
+ Plan will use platform-specific scheduling tools. In particular, it uses crontab on Linux and launchd on macOS and Schedule Tasks on Windows.
1109
+
1110
+ Implementations have been provided for Mac and Windows but only has been tested as of 3/23/2025 on Linux.
1111
+
1112
+
1113
+
1086
1114
  ### Plonk : Computer Control
1087
1115
  Use the /plonk macro to allow the LLM to control your computer.
1088
1116
  ```npcsh
@@ -1368,6 +1396,18 @@ npcsh> /spool model=llama3.3
1368
1396
  npc spool -n npc.npc
1369
1397
  ```
1370
1398
 
1399
+ ### Trigger
1400
+ Use the /trigger macro to execute specific actionss based on certain conditions.
1401
+
1402
+ ```npcsh
1403
+ npcsh> /trigger watch for new PDF downloads in the ~/Downloads directory and move them
1404
+ to the ~/Documents/PDFs directory . Ensure that the directory exists or create it if it does not.
1405
+ ```
1406
+
1407
+ On Linux, trigger makes use of inotify-tools to watch for file system events. On macOS, it uses fswatch, and on Windows, it uses Watch-Command.
1408
+
1409
+
1410
+
1371
1411
 
1372
1412
 
1373
1413
  ### Vixynt: Image Generation
@@ -385,6 +385,7 @@ if __name__ == "__main__":
385
385
  ### Linux install
386
386
  ```bash
387
387
 
388
+ # for audio primarily
388
389
  sudo apt-get install espeak
389
390
  sudo apt-get install portaudio19-dev python3-pyaudio
390
391
  sudo apt-get install alsa-base alsa-utils
@@ -392,6 +393,10 @@ sudo apt-get install libcairo2-dev
392
393
  sudo apt-get install libgirepository1.0-dev
393
394
  sudo apt-get install ffmpeg
394
395
 
396
+ # for triggers
397
+ sudo apt install inotify-tools
398
+
399
+
395
400
  #And if you don't have ollama installed, use this:
396
401
  curl -fsSL https://ollama.com/install.sh | sh
397
402
 
@@ -414,11 +419,17 @@ pip install npcsh[all]
414
419
 
415
420
  ### Mac install
416
421
  ```bash
422
+ #mainly for audio
417
423
  brew install portaudio
418
424
  brew install ffmpeg
425
+ brew install pygobject3
426
+
427
+ # for triggers
428
+ brew install ...
429
+
430
+
419
431
  brew install ollama
420
432
  brew services start ollama
421
- brew install pygobject3
422
433
  ollama pull llama3.2
423
434
  ollama pull llava:7b
424
435
  ollama pull nomic-embed-text
@@ -992,13 +1003,30 @@ npc ots -f test_data/catfight.PNG
992
1003
  ### Plan : Schedule tasks to be run at regular intervals (under construction)
993
1004
  Use the /plan macro to schedule tasks to be run at regular intervals.
994
1005
  ```npcsh
995
- npcsh> /plan run a rag search on the files in the current directory every 5 minutes
1006
+ npcsh> /plan run a rag search for 'moonbeam' on the files in the current directory every 5 minutes
996
1007
  ```
997
1008
 
1009
+ ```npcsh
1010
+ npcsh> /plan record the cpu usage every 5 minutes
1011
+ ```
1012
+
1013
+ ```npcsh
1014
+ npcsh> /plan record the apps that are using the most ram every 5 minutes
1015
+ ```
1016
+
1017
+
1018
+
1019
+
998
1020
  ```bash
999
1021
  npc plan -f 30m -t 'task'
1000
1022
  ```
1001
1023
 
1024
+ Plan will use platform-specific scheduling tools. In particular, it uses crontab on Linux and launchd on macOS and Schedule Tasks on Windows.
1025
+
1026
+ Implementations have been provided for Mac and Windows but only has been tested as of 3/23/2025 on Linux.
1027
+
1028
+
1029
+
1002
1030
  ### Plonk : Computer Control
1003
1031
  Use the /plonk macro to allow the LLM to control your computer.
1004
1032
  ```npcsh
@@ -1284,6 +1312,18 @@ npcsh> /spool model=llama3.3
1284
1312
  npc spool -n npc.npc
1285
1313
  ```
1286
1314
 
1315
+ ### Trigger
1316
+ Use the /trigger macro to execute specific actionss based on certain conditions.
1317
+
1318
+ ```npcsh
1319
+ npcsh> /trigger watch for new PDF downloads in the ~/Downloads directory and move them
1320
+ to the ~/Documents/PDFs directory . Ensure that the directory exists or create it if it does not.
1321
+ ```
1322
+
1323
+ On Linux, trigger makes use of inotify-tools to watch for file system events. On macOS, it uses fswatch, and on Windows, it uses Watch-Command.
1324
+
1325
+
1326
+
1287
1327
 
1288
1328
 
1289
1329
  ### Vixynt: Image Generation
@@ -22,6 +22,7 @@ import tty
22
22
  import pty
23
23
  import select
24
24
  import signal
25
+ import platform
25
26
  import time
26
27
 
27
28
 
@@ -66,6 +67,7 @@ from .llm_funcs import (
66
67
  execute_llm_question,
67
68
  get_stream,
68
69
  get_conversation,
70
+ get_llm_response,
69
71
  check_llm_command,
70
72
  generate_image,
71
73
  get_embeddings,
@@ -1090,6 +1092,510 @@ def resize_image_tars(image_path):
1090
1092
  image.save(image_path, format="png")
1091
1093
 
1092
1094
 
1095
+ def execute_plan_command(
1096
+ command, npc=None, model=None, provider=None, messages=None, api_url=None
1097
+ ):
1098
+ parts = command.split(maxsplit=1)
1099
+ if len(parts) < 2:
1100
+ return {
1101
+ "messages": messages,
1102
+ "output": "Usage: /plan <command and schedule description>",
1103
+ }
1104
+
1105
+ request = parts[1]
1106
+ platform_system = platform.system()
1107
+
1108
+ # Create standard directories
1109
+ jobs_dir = os.path.expanduser("~/.npcsh/jobs")
1110
+ logs_dir = os.path.expanduser("~/.npcsh/logs")
1111
+ os.makedirs(jobs_dir, exist_ok=True)
1112
+ os.makedirs(logs_dir, exist_ok=True)
1113
+
1114
+ # First part - just the request formatting
1115
+ linux_request = f"""Convert this scheduling request into a crontab-based script:
1116
+ Request: {request}
1117
+
1118
+ """
1119
+
1120
+ # Second part - the static prompt with examples and requirements
1121
+ linux_prompt_static = """Example for "record CPU usage every 10 minutes":
1122
+ {
1123
+ "script": "#!/bin/bash
1124
+ set -euo pipefail
1125
+ IFS=$'\\n\\t'
1126
+
1127
+ LOGFILE=\"$HOME/.npcsh/logs/cpu_usage.log\"
1128
+
1129
+ log_info() {
1130
+ echo \"[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*\" >> \"$LOGFILE\"
1131
+ }
1132
+
1133
+ log_error() {
1134
+ echo \"[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*\" >> \"$LOGFILE\"
1135
+ }
1136
+
1137
+ record_cpu() {
1138
+ local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
1139
+ local cpu_usage=$(top -bn1 | grep 'Cpu(s)' | awk '{print $2}')
1140
+ log_info \"CPU Usage: $cpu_usage%\"
1141
+ }
1142
+
1143
+ record_cpu",
1144
+ "schedule": "*/10 * * * *",
1145
+ "description": "Record CPU usage every 10 minutes",
1146
+ "name": "record_cpu_usage"
1147
+ }
1148
+
1149
+ Your response must be valid json with the following keys:
1150
+ - script: The shell script content with proper functions and error handling. special characters must be escaped to ensure python json.loads will work correctly.
1151
+ - schedule: Crontab expression (5 fields: minute hour day month weekday)
1152
+ - description: A human readable description
1153
+ - name: A unique name for the job
1154
+
1155
+ Do not include any additional markdown formatting in your response or leading ```json tags."""
1156
+
1157
+ mac_request = f"""Convert this scheduling request into a launchd-compatible script:
1158
+ Request: {request}
1159
+
1160
+ """
1161
+
1162
+ mac_prompt_static = """Example for "record CPU usage every 10 minutes":
1163
+ {
1164
+ "script": "#!/bin/bash
1165
+ set -euo pipefail
1166
+ IFS=$'\\n\\t'
1167
+
1168
+ LOGFILE=\"$HOME/.npcsh/logs/cpu_usage.log\"
1169
+
1170
+ log_info() {
1171
+ echo \"[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*\" >> \"$LOGFILE\"
1172
+ }
1173
+
1174
+ log_error() {
1175
+ echo \"[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*\" >> \"$LOGFILE\"
1176
+ }
1177
+
1178
+ record_cpu() {
1179
+ local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
1180
+ local cpu_usage=$(top -l 1 | grep 'CPU usage' | awk '{print $3}' | tr -d '%')
1181
+ log_info \"CPU Usage: $cpu_usage%\"
1182
+ }
1183
+
1184
+ record_cpu",
1185
+ "schedule": "600",
1186
+ "description": "Record CPU usage every 10 minutes",
1187
+ "name": "record_cpu_usage"
1188
+ }
1189
+
1190
+ Your response must be valid json with the following keys:
1191
+ - script: The shell script content with proper functions and error handling. special characters must be escaped to ensure python json.loads will work correctly.
1192
+ - schedule: Interval in seconds (e.g. 600 for 10 minutes)
1193
+ - description: A human readable description
1194
+ - name: A unique name for the job
1195
+
1196
+ Do not include any additional markdown formatting in your response or leading ```json tags."""
1197
+
1198
+ windows_request = f"""Convert this scheduling request into a PowerShell script with Task Scheduler parameters:
1199
+ Request: {request}
1200
+
1201
+ """
1202
+
1203
+ windows_prompt_static = """Example for "record CPU usage every 10 minutes":
1204
+ {
1205
+ "script": "$ErrorActionPreference = 'Stop'
1206
+
1207
+ $LogFile = \"$HOME\\.npcsh\\logs\\cpu_usage.log\"
1208
+
1209
+ function Write-Log {
1210
+ param($Message, $Type = 'INFO')
1211
+ $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
1212
+ \"[$timestamp] [$Type] $Message\" | Out-File -FilePath $LogFile -Append
1213
+ }
1214
+
1215
+ function Get-CpuUsage {
1216
+ try {
1217
+ $cpu = (Get-Counter '\\Processor(_Total)\\% Processor Time').CounterSamples.CookedValue
1218
+ Write-Log \"CPU Usage: $($cpu)%\"
1219
+ } catch {
1220
+ Write-Log $_.Exception.Message 'ERROR'
1221
+ throw
1222
+ }
1223
+ }
1224
+
1225
+ Get-CpuUsage",
1226
+ "schedule": "/sc minute /mo 10",
1227
+ "description": "Record CPU usage every 10 minutes",
1228
+ "name": "record_cpu_usage"
1229
+ }
1230
+
1231
+ Your response must be valid json with the following keys:
1232
+ - script: The PowerShell script content with proper functions and error handling. special characters must be escaped to ensure python json.loads will work correctly.
1233
+ - schedule: Task Scheduler parameters (e.g. /sc minute /mo 10)
1234
+ - description: A human readable description
1235
+ - name: A unique name for the job
1236
+
1237
+ Do not include any additional markdown formatting in your response or leading ```json tags."""
1238
+
1239
+ prompts = {
1240
+ "Linux": linux_request + linux_prompt_static,
1241
+ "Darwin": mac_request + mac_prompt_static,
1242
+ "Windows": windows_request + windows_prompt_static,
1243
+ }
1244
+
1245
+ prompt = prompts[platform_system]
1246
+ response = get_llm_response(
1247
+ prompt, npc=npc, model=model, provider=provider, format="json"
1248
+ )
1249
+ schedule_info = response.get("response")
1250
+ print("Received schedule info:", schedule_info)
1251
+
1252
+ job_name = f"job_{schedule_info['name']}"
1253
+
1254
+ if platform_system == "Windows":
1255
+ script_path = os.path.join(jobs_dir, f"{job_name}.ps1")
1256
+ else:
1257
+ script_path = os.path.join(jobs_dir, f"{job_name}.sh")
1258
+
1259
+ log_path = os.path.join(logs_dir, f"{job_name}.log")
1260
+
1261
+ # Write the script
1262
+ with open(script_path, "w") as f:
1263
+ f.write(schedule_info["script"])
1264
+ os.chmod(script_path, 0o755)
1265
+
1266
+ if platform_system == "Linux":
1267
+ try:
1268
+ current_crontab = subprocess.check_output(["crontab", "-l"], text=True)
1269
+ except subprocess.CalledProcessError:
1270
+ current_crontab = ""
1271
+
1272
+ crontab_line = f"{schedule_info['schedule']} {script_path} >> {log_path} 2>&1"
1273
+ new_crontab = current_crontab.strip() + "\n" + crontab_line + "\n"
1274
+
1275
+ with tempfile.NamedTemporaryFile(mode="w") as tmp:
1276
+ tmp.write(new_crontab)
1277
+ tmp.flush()
1278
+ subprocess.run(["crontab", tmp.name], check=True)
1279
+
1280
+ output = f"""Job created successfully:
1281
+ - Description: {schedule_info['description']}
1282
+ - Schedule: {schedule_info['schedule']}
1283
+ - Script: {script_path}
1284
+ - Log: {log_path}
1285
+ - Crontab entry: {crontab_line}"""
1286
+
1287
+ elif platform_system == "Darwin":
1288
+ plist_dir = os.path.expanduser("~/Library/LaunchAgents")
1289
+ os.makedirs(plist_dir, exist_ok=True)
1290
+ plist_path = os.path.join(plist_dir, f"com.npcsh.{job_name}.plist")
1291
+
1292
+ plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
1293
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1294
+ <plist version="1.0">
1295
+ <dict>
1296
+ <key>Label</key>
1297
+ <string>com.npcsh.{job_name}</string>
1298
+ <key>ProgramArguments</key>
1299
+ <array>
1300
+ <string>{script_path}</string>
1301
+ </array>
1302
+ <key>StartInterval</key>
1303
+ <integer>{schedule_info['schedule']}</integer>
1304
+ <key>StandardOutPath</key>
1305
+ <string>{log_path}</string>
1306
+ <key>StandardErrorPath</key>
1307
+ <string>{log_path}</string>
1308
+ <key>RunAtLoad</key>
1309
+ <true/>
1310
+ </dict>
1311
+ </plist>"""
1312
+
1313
+ with open(plist_path, "w") as f:
1314
+ f.write(plist_content)
1315
+
1316
+ subprocess.run(["launchctl", "unload", plist_path], check=False)
1317
+ subprocess.run(["launchctl", "load", plist_path], check=True)
1318
+
1319
+ output = f"""Job created successfully:
1320
+ - Description: {schedule_info['description']}
1321
+ - Schedule: Every {schedule_info['schedule']} seconds
1322
+ - Script: {script_path}
1323
+ - Log: {log_path}
1324
+ - Launchd plist: {plist_path}"""
1325
+
1326
+ elif platform_system == "Windows":
1327
+ task_name = f"NPCSH_{job_name}"
1328
+
1329
+ # Parse schedule_info['schedule'] into individual parameters
1330
+ schedule_params = schedule_info["schedule"].split()
1331
+
1332
+ cmd = (
1333
+ [
1334
+ "schtasks",
1335
+ "/create",
1336
+ "/tn",
1337
+ task_name,
1338
+ "/tr",
1339
+ f"powershell -NoProfile -ExecutionPolicy Bypass -File {script_path}",
1340
+ ]
1341
+ + schedule_params
1342
+ + ["/f"]
1343
+ ) # /f forces creation if task exists
1344
+
1345
+ subprocess.run(cmd, check=True)
1346
+
1347
+ output = f"""Job created successfully:
1348
+ - Description: {schedule_info['description']}
1349
+ - Schedule: {schedule_info['schedule']}
1350
+ - Script: {script_path}
1351
+ - Log: {log_path}
1352
+ - Task name: {task_name}"""
1353
+
1354
+ return {"messages": messages, "output": output}
1355
+
1356
+
1357
+ def execute_trigger_command(
1358
+ command, npc=None, model=None, provider=None, messages=None, api_url=None
1359
+ ):
1360
+ parts = command.split(maxsplit=1)
1361
+ if len(parts) < 2:
1362
+ return {
1363
+ "messages": messages,
1364
+ "output": "Usage: /trigger <trigger condition and action description>",
1365
+ }
1366
+
1367
+ request = parts[1]
1368
+ platform_system = platform.system()
1369
+
1370
+ linux_request = f"""Convert this trigger request into a single event-monitoring daemon script:
1371
+ Request: {request}
1372
+
1373
+ """
1374
+
1375
+ linux_prompt_static = """Example for "Move PDFs from Downloads to Documents/PDFs":
1376
+ {
1377
+ "script": "#!/bin/bash\\nset -euo pipefail\\nIFS=$'\\n\\t'\\n\\nLOGFILE=\\\"$HOME/.npcsh/logs/pdf_mover.log\\\"\\nSOURCE=\\\"$HOME/Downloads\\\"\\nTARGET=\\\"$HOME/Documents/PDFs\\\"\\n\\nlog_info() {\\n echo \\\"[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*\\\" >> \\\"$LOGFILE\\\"\\n}\\n\\nlog_error() {\\n echo \\\"[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*\\\" >> \\\"$LOGFILE\\\"\\n}\\n\\ninotifywait -m -q -e create --format '%w%f' \\\"$SOURCE\\\" | while read filepath; do\\n if [[ \\\"$filepath\\\" =~ \\\\.pdf$ ]]; then\\n mv \\\"$filepath\\\" \\\"$TARGET/\\\" && log_info \\\"Moved $filepath to $TARGET\\\" || log_error \\\"Failed to move $filepath\\\"\\n fi\\ndone",
1378
+ "name": "pdf_mover",
1379
+ "description": "Move PDF files from Downloads to Documents/PDFs folder"
1380
+ }
1381
+
1382
+ The script MUST:
1383
+ - Use inotifywait -m -q -e create --format '%w%f' to get full paths
1384
+ - Double quote ALL file operations: "$SOURCE/$FILE"
1385
+ - Use $HOME for absolute paths
1386
+ - Echo both success and failure messages to log
1387
+
1388
+ Your response must be valid json with the following keys:
1389
+ - script: The shell script content with proper functions and error handling
1390
+ - name: A unique name for the trigger
1391
+ - description: A human readable description
1392
+
1393
+ Do not include any additional markdown formatting in your response."""
1394
+
1395
+ mac_request = f"""Convert this trigger request into a single event-monitoring daemon script:
1396
+ Request: {request}
1397
+
1398
+ """
1399
+
1400
+ mac_prompt_static = """Example for "Move PDFs from Downloads to Documents/PDFs":
1401
+ {
1402
+ "script": "#!/bin/bash\\nset -euo pipefail\\nIFS=$'\\n\\t'\\n\\nLOGFILE=\\\"$HOME/.npcsh/logs/pdf_mover.log\\\"\\nSOURCE=\\\"$HOME/Downloads\\\"\\nTARGET=\\\"$HOME/Documents/PDFs\\\"\\n\\nlog_info() {\\n echo \\\"[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*\\\" >> \\\"$LOGFILE\\\"\\n}\\n\\nlog_error() {\\n echo \\\"[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*\\\" >> \\\"$LOGFILE\\\"\\n}\\n\\nfswatch -0 -r -e '.*' --event Created --format '%p' \\\"$SOURCE\\\" | while read -d '' filepath; do\\n if [[ \\\"$filepath\\\" =~ \\\\.pdf$ ]]; then\\n mv \\\"$filepath\\\" \\\"$TARGET/\\\" && log_info \\\"Moved $filepath to $TARGET\\\" || log_error \\\"Failed to move $filepath\\\"\\n fi\\ndone",
1403
+ "name": "pdf_mover",
1404
+ "description": "Move PDF files from Downloads to Documents/PDFs folder"
1405
+ }
1406
+
1407
+ The script MUST:
1408
+ - Use fswatch -0 -r -e '.*' --event Created --format '%p' to get full paths
1409
+ - Double quote ALL file operations: "$SOURCE/$FILE"
1410
+ - Use $HOME for absolute paths
1411
+ - Echo both success and failure messages to log
1412
+
1413
+ Your response must be valid json with the following keys:
1414
+ - script: The shell script content with proper functions and error handling
1415
+ - name: A unique name for the trigger
1416
+ - description: A human readable description
1417
+
1418
+ Do not include any additional markdown formatting in your response."""
1419
+
1420
+ windows_request = f"""Convert this trigger request into a single event-monitoring daemon script:
1421
+ Request: {request}
1422
+
1423
+ """
1424
+
1425
+ windows_prompt_static = """Example for "Move PDFs from Downloads to Documents/PDFs":
1426
+ {
1427
+ "script": "$ErrorActionPreference = 'Stop'\\n\\n$LogFile = \\\"$HOME\\.npcsh\\logs\\pdf_mover.log\\\"\\n$Source = \\\"$HOME\\Downloads\\\"\\n$Target = \\\"$HOME\\Documents\\PDFs\\\"\\n\\nfunction Write-Log {\\n param($Message, $Type = 'INFO')\\n $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'\\n \\\"[$timestamp] [$Type] $Message\\\" | Out-File -FilePath $LogFile -Append\\n}\\n\\n$watcher = New-Object System.IO.FileSystemWatcher\\n$watcher.Path = $Source\\n$watcher.Filter = \\\"*.pdf\\\"\\n$watcher.IncludeSubdirectories = $true\\n$watcher.EnableRaisingEvents = $true\\n\\n$action = {\\n $path = $Event.SourceEventArgs.FullPath\\n try {\\n Move-Item -Path $path -Destination $Target\\n Write-Log \\\"Moved $path to $Target\\\"\\n } catch {\\n Write-Log $_.Exception.Message 'ERROR'\\n }\\n}\\n\\nRegister-ObjectEvent $watcher 'Created' -Action $action\\n\\nwhile ($true) { Start-Sleep 1 }",
1428
+ "name": "pdf_mover",
1429
+ "description": "Move PDF files from Downloads to Documents/PDFs folder"
1430
+ }
1431
+
1432
+ The script MUST:
1433
+ - Use FileSystemWatcher for monitoring
1434
+ - Double quote ALL file operations: "$Source\\$File"
1435
+ - Use $HOME for absolute paths
1436
+ - Echo both success and failure messages to log
1437
+
1438
+ Your response must be valid json with the following keys:
1439
+ - script: The PowerShell script content with proper functions and error handling
1440
+ - name: A unique name for the trigger
1441
+ - description: A human readable description
1442
+
1443
+ Do not include any additional markdown formatting in your response."""
1444
+
1445
+ prompts = {
1446
+ "Linux": linux_request + linux_prompt_static,
1447
+ "Darwin": mac_request + mac_prompt_static,
1448
+ "Windows": windows_request + windows_prompt_static,
1449
+ }
1450
+
1451
+ prompt = prompts[platform_system]
1452
+ response = get_llm_response(
1453
+ prompt, npc=npc, model=model, provider=provider, format="json"
1454
+ )
1455
+ trigger_info = response.get("response")
1456
+ print("Trigger info:", trigger_info)
1457
+
1458
+ triggers_dir = os.path.expanduser("~/.npcsh/triggers")
1459
+ logs_dir = os.path.expanduser("~/.npcsh/logs")
1460
+ os.makedirs(triggers_dir, exist_ok=True)
1461
+ os.makedirs(logs_dir, exist_ok=True)
1462
+
1463
+ trigger_name = f"trigger_{trigger_info['name']}"
1464
+ log_path = os.path.join(logs_dir, f"{trigger_name}.log")
1465
+
1466
+ if platform_system == "Linux":
1467
+ script_path = os.path.join(triggers_dir, f"{trigger_name}.sh")
1468
+
1469
+ with open(script_path, "w") as f:
1470
+ f.write(trigger_info["script"])
1471
+ os.chmod(script_path, 0o755)
1472
+
1473
+ service_dir = os.path.expanduser("~/.config/systemd/user")
1474
+ os.makedirs(service_dir, exist_ok=True)
1475
+ service_path = os.path.join(service_dir, f"npcsh-{trigger_name}.service")
1476
+
1477
+ service_content = f"""[Unit]
1478
+ Description={trigger_info['description']}
1479
+ After=network.target
1480
+
1481
+ [Service]
1482
+ Type=simple
1483
+ ExecStart={script_path}
1484
+ Restart=always
1485
+ StandardOutput=append:{log_path}
1486
+ StandardError=append:{log_path}
1487
+
1488
+ [Install]
1489
+ WantedBy=default.target
1490
+ """
1491
+
1492
+ with open(service_path, "w") as f:
1493
+ f.write(service_content)
1494
+
1495
+ subprocess.run(["systemctl", "--user", "daemon-reload"])
1496
+ subprocess.run(
1497
+ ["systemctl", "--user", "enable", f"npcsh-{trigger_name}.service"]
1498
+ )
1499
+ subprocess.run(
1500
+ ["systemctl", "--user", "start", f"npcsh-{trigger_name}.service"]
1501
+ )
1502
+
1503
+ status = subprocess.run(
1504
+ ["systemctl", "--user", "status", f"npcsh-{trigger_name}.service"],
1505
+ capture_output=True,
1506
+ text=True,
1507
+ )
1508
+
1509
+ output = f"""Trigger service created:
1510
+ - Description: {trigger_info['description']}
1511
+ - Script: {script_path}
1512
+ - Service: {service_path}
1513
+ - Log: {log_path}
1514
+
1515
+ Status:
1516
+ {status.stdout}"""
1517
+
1518
+ elif platform_system == "Darwin":
1519
+ script_path = os.path.join(triggers_dir, f"{trigger_name}.sh")
1520
+
1521
+ with open(script_path, "w") as f:
1522
+ f.write(trigger_info["script"])
1523
+ os.chmod(script_path, 0o755)
1524
+
1525
+ plist_dir = os.path.expanduser("~/Library/LaunchAgents")
1526
+ os.makedirs(plist_dir, exist_ok=True)
1527
+ plist_path = os.path.join(plist_dir, f"com.npcsh.{trigger_name}.plist")
1528
+
1529
+ plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
1530
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1531
+ <plist version="1.0">
1532
+ <dict>
1533
+ <key>Label</key>
1534
+ <string>com.npcsh.{trigger_name}</string>
1535
+ <key>ProgramArguments</key>
1536
+ <array>
1537
+ <string>{script_path}</string>
1538
+ </array>
1539
+ <key>RunAtLoad</key>
1540
+ <true/>
1541
+ <key>KeepAlive</key>
1542
+ <true/>
1543
+ <key>StandardOutPath</key>
1544
+ <string>{log_path}</string>
1545
+ <key>StandardErrorPath</key>
1546
+ <string>{log_path}</string>
1547
+ </dict>
1548
+ </plist>"""
1549
+
1550
+ with open(plist_path, "w") as f:
1551
+ f.write(plist_content)
1552
+
1553
+ subprocess.run(["launchctl", "unload", plist_path], check=False)
1554
+ subprocess.run(["launchctl", "load", plist_path], check=True)
1555
+
1556
+ output = f"""Trigger service created:
1557
+ - Description: {trigger_info['description']}
1558
+ - Script: {script_path}
1559
+ - Launchd plist: {plist_path}
1560
+ - Log: {log_path}"""
1561
+
1562
+ elif platform_system == "Windows":
1563
+ script_path = os.path.join(triggers_dir, f"{trigger_name}.ps1")
1564
+
1565
+ with open(script_path, "w") as f:
1566
+ f.write(trigger_info["script"])
1567
+
1568
+ task_name = f"NPCSH_{trigger_name}"
1569
+
1570
+ # Create a scheduled task that runs at startup
1571
+ cmd = [
1572
+ "schtasks",
1573
+ "/create",
1574
+ "/tn",
1575
+ task_name,
1576
+ "/tr",
1577
+ f"powershell -NoProfile -ExecutionPolicy Bypass -File {script_path}",
1578
+ "/sc",
1579
+ "onstart",
1580
+ "/ru",
1581
+ "System",
1582
+ "/f", # Force creation
1583
+ ]
1584
+
1585
+ subprocess.run(cmd, check=True)
1586
+
1587
+ # Start the task immediately
1588
+ subprocess.run(["schtasks", "/run", "/tn", task_name])
1589
+
1590
+ output = f"""Trigger service created:
1591
+ - Description: {trigger_info['description']}
1592
+ - Script: {script_path}
1593
+ - Task name: {task_name}
1594
+ - Log: {log_path}"""
1595
+
1596
+ return {"messages": messages, "output": output}
1597
+
1598
+
1093
1599
  def enter_wander_mode(args, messages, npc_compiler, npc, model, provider):
1094
1600
  """
1095
1601
  Wander mode is an exploratory mode where an LLM is given a task and they begin to wander through space.
@@ -1334,6 +1840,25 @@ def execute_slash_command(
1334
1840
  print(output)
1335
1841
  elif command_name == "tools":
1336
1842
  return {"messages": messages, "output": print_tools(tools)}
1843
+ elif command_name == "plan":
1844
+ return execute_plan_command(
1845
+ command,
1846
+ npc=npc,
1847
+ model=model,
1848
+ provider=provider,
1849
+ api_url=api_url,
1850
+ messages=messages,
1851
+ )
1852
+ elif command_name == "trigger":
1853
+ return execute_trigger_command(
1854
+ command,
1855
+ npc=npc,
1856
+ model=model,
1857
+ provider=provider,
1858
+ api_url=api_url,
1859
+ messages=messages,
1860
+ )
1861
+
1337
1862
  elif command_name == "plonk":
1338
1863
  request = " ".join(args)
1339
1864
  plonk_call = plonk(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcsh
3
- Version: 0.3.28
3
+ Version: 0.3.29
4
4
  Summary: npcsh is a command line tool for integrating LLMs into everyday workflows and for orchestrating teams of NPCs.
5
5
  Home-page: https://github.com/cagostino/npcsh
6
6
  Author: Christopher Agostino
@@ -469,6 +469,7 @@ if __name__ == "__main__":
469
469
  ### Linux install
470
470
  ```bash
471
471
 
472
+ # for audio primarily
472
473
  sudo apt-get install espeak
473
474
  sudo apt-get install portaudio19-dev python3-pyaudio
474
475
  sudo apt-get install alsa-base alsa-utils
@@ -476,6 +477,10 @@ sudo apt-get install libcairo2-dev
476
477
  sudo apt-get install libgirepository1.0-dev
477
478
  sudo apt-get install ffmpeg
478
479
 
480
+ # for triggers
481
+ sudo apt install inotify-tools
482
+
483
+
479
484
  #And if you don't have ollama installed, use this:
480
485
  curl -fsSL https://ollama.com/install.sh | sh
481
486
 
@@ -498,11 +503,17 @@ pip install npcsh[all]
498
503
 
499
504
  ### Mac install
500
505
  ```bash
506
+ #mainly for audio
501
507
  brew install portaudio
502
508
  brew install ffmpeg
509
+ brew install pygobject3
510
+
511
+ # for triggers
512
+ brew install ...
513
+
514
+
503
515
  brew install ollama
504
516
  brew services start ollama
505
- brew install pygobject3
506
517
  ollama pull llama3.2
507
518
  ollama pull llava:7b
508
519
  ollama pull nomic-embed-text
@@ -1076,13 +1087,30 @@ npc ots -f test_data/catfight.PNG
1076
1087
  ### Plan : Schedule tasks to be run at regular intervals (under construction)
1077
1088
  Use the /plan macro to schedule tasks to be run at regular intervals.
1078
1089
  ```npcsh
1079
- npcsh> /plan run a rag search on the files in the current directory every 5 minutes
1090
+ npcsh> /plan run a rag search for 'moonbeam' on the files in the current directory every 5 minutes
1080
1091
  ```
1081
1092
 
1093
+ ```npcsh
1094
+ npcsh> /plan record the cpu usage every 5 minutes
1095
+ ```
1096
+
1097
+ ```npcsh
1098
+ npcsh> /plan record the apps that are using the most ram every 5 minutes
1099
+ ```
1100
+
1101
+
1102
+
1103
+
1082
1104
  ```bash
1083
1105
  npc plan -f 30m -t 'task'
1084
1106
  ```
1085
1107
 
1108
+ Plan will use platform-specific scheduling tools. In particular, it uses crontab on Linux and launchd on macOS and Schedule Tasks on Windows.
1109
+
1110
+ Implementations have been provided for Mac and Windows but only has been tested as of 3/23/2025 on Linux.
1111
+
1112
+
1113
+
1086
1114
  ### Plonk : Computer Control
1087
1115
  Use the /plonk macro to allow the LLM to control your computer.
1088
1116
  ```npcsh
@@ -1368,6 +1396,18 @@ npcsh> /spool model=llama3.3
1368
1396
  npc spool -n npc.npc
1369
1397
  ```
1370
1398
 
1399
+ ### Trigger
1400
+ Use the /trigger macro to execute specific actionss based on certain conditions.
1401
+
1402
+ ```npcsh
1403
+ npcsh> /trigger watch for new PDF downloads in the ~/Downloads directory and move them
1404
+ to the ~/Documents/PDFs directory . Ensure that the directory exists or create it if it does not.
1405
+ ```
1406
+
1407
+ On Linux, trigger makes use of inotify-tools to watch for file system events. On macOS, it uses fswatch, and on Windows, it uses Watch-Command.
1408
+
1409
+
1410
+
1371
1411
 
1372
1412
 
1373
1413
  ### Vixynt: Image Generation
@@ -107,7 +107,7 @@ extra_files = package_files("npcsh/npc_team/")
107
107
 
108
108
  setup(
109
109
  name="npcsh",
110
- version="0.3.28",
110
+ version="0.3.29",
111
111
  packages=find_packages(exclude=["tests*"]),
112
112
  install_requires=base_requirements, # Only install base requirements by default
113
113
  extras_require={
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes