npcsh 1.1.14__py3-none-any.whl → 1.1.15__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 +488 -77
- npcsh/mcp_server.py +2 -1
- npcsh/npc.py +84 -32
- npcsh/npc_team/alicanto.npc +22 -1
- npcsh/npc_team/corca.npc +28 -9
- npcsh/npc_team/frederic.npc +25 -4
- npcsh/npc_team/guac.npc +22 -0
- npcsh/npc_team/jinxs/bin/nql.jinx +141 -0
- npcsh/npc_team/jinxs/bin/sync.jinx +230 -0
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/bin}/vixynt.jinx +8 -30
- npcsh/npc_team/jinxs/bin/wander.jinx +152 -0
- npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +220 -0
- npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +40 -0
- npcsh/npc_team/jinxs/lib/browser/close_browser.jinx +14 -0
- npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +43 -0
- npcsh/npc_team/jinxs/lib/computer_use/click.jinx +23 -0
- npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +26 -0
- npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +37 -0
- npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +23 -0
- npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +27 -0
- npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +21 -0
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/edit_file.jinx +3 -3
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/load_file.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/paste.jinx +134 -0
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/search.jinx +2 -1
- npcsh/npc_team/jinxs/{code → lib/core}/sh.jinx +2 -8
- npcsh/npc_team/jinxs/{code → lib/core}/sql.jinx +1 -1
- npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +232 -0
- npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +184 -0
- npcsh/npc_team/jinxs/lib/research/arxiv.jinx +76 -0
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +101 -0
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +69 -0
- npcsh/npc_team/jinxs/{utils/core → lib/utils}/build.jinx +8 -8
- npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +176 -0
- npcsh/npc_team/jinxs/lib/utils/shh.jinx +17 -0
- npcsh/npc_team/jinxs/lib/utils/switch.jinx +62 -0
- npcsh/npc_team/jinxs/lib/utils/switches.jinx +61 -0
- npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +205 -0
- npcsh/npc_team/jinxs/lib/utils/verbose.jinx +17 -0
- npcsh/npc_team/kadiefa.npc +19 -1
- npcsh/npc_team/plonk.npc +26 -1
- npcsh/npc_team/plonkjr.npc +22 -1
- npcsh/npc_team/sibiji.npc +23 -2
- npcsh/npcsh.py +153 -39
- npcsh/ui.py +22 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/alicanto.npc +23 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/arxiv.jinx +76 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/browser_action.jinx +220 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/browser_screenshot.jinx +40 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/build.jinx +8 -8
- npcsh-1.1.15.data/data/npcsh/npc_team/click.jinx +23 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/close_browser.jinx +14 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/convene.jinx +232 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/corca.npc +31 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/delegate.jinx +184 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/edit_file.jinx +3 -3
- npcsh-1.1.15.data/data/npcsh/npc_team/frederic.npc +27 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/guac.npc +22 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/jinxs.jinx +176 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/kadiefa.npc +21 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/key_press.jinx +26 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/launch_app.jinx +37 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/load_file.jinx +1 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/nql.jinx +141 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/open_browser.jinx +43 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/paper_search.jinx +101 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/paste.jinx +134 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/plonk.npc +27 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/plonkjr.npc +23 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/screenshot.jinx +23 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/search.jinx +2 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/semantic_scholar.jinx +69 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sh.jinx +2 -8
- npcsh-1.1.15.data/data/npcsh/npc_team/shh.jinx +17 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/sibiji.npc +24 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sql.jinx +1 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/switch.jinx +62 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/switches.jinx +61 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/sync.jinx +230 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/teamviz.jinx +205 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/type_text.jinx +27 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/verbose.jinx +17 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/vixynt.jinx +8 -30
- npcsh-1.1.15.data/data/npcsh/npc_team/wait.jinx +21 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/wander.jinx +152 -0
- {npcsh-1.1.14.dist-info → npcsh-1.1.15.dist-info}/METADATA +399 -58
- npcsh-1.1.15.dist-info/RECORD +170 -0
- npcsh-1.1.15.dist-info/entry_points.txt +19 -0
- npcsh-1.1.15.dist-info/top_level.txt +2 -0
- project/__init__.py +1 -0
- npcsh/npc_team/foreman.npc +0 -7
- npcsh/npc_team/jinxs/modes/alicanto.jinx +0 -194
- npcsh/npc_team/jinxs/modes/corca.jinx +0 -249
- npcsh/npc_team/jinxs/modes/guac.jinx +0 -317
- npcsh/npc_team/jinxs/modes/plonk.jinx +0 -214
- npcsh/npc_team/jinxs/modes/pti.jinx +0 -170
- npcsh/npc_team/jinxs/modes/wander.jinx +0 -186
- npcsh/npc_team/jinxs/utils/agent.jinx +0 -17
- npcsh/npc_team/jinxs/utils/core/jinxs.jinx +0 -32
- npcsh-1.1.14.data/data/npcsh/npc_team/agent.jinx +0 -17
- npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +0 -194
- npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.npc +0 -2
- npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +0 -249
- npcsh-1.1.14.data/data/npcsh/npc_team/corca.npc +0 -12
- npcsh-1.1.14.data/data/npcsh/npc_team/foreman.npc +0 -7
- npcsh-1.1.14.data/data/npcsh/npc_team/frederic.npc +0 -6
- npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +0 -317
- npcsh-1.1.14.data/data/npcsh/npc_team/jinxs.jinx +0 -32
- npcsh-1.1.14.data/data/npcsh/npc_team/kadiefa.npc +0 -3
- npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +0 -214
- npcsh-1.1.14.data/data/npcsh/npc_team/plonk.npc +0 -2
- npcsh-1.1.14.data/data/npcsh/npc_team/plonkjr.npc +0 -2
- npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +0 -170
- npcsh-1.1.14.data/data/npcsh/npc_team/sibiji.npc +0 -3
- npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +0 -186
- npcsh-1.1.14.dist-info/RECORD +0 -135
- npcsh-1.1.14.dist-info/entry_points.txt +0 -9
- npcsh-1.1.14.dist-info/top_level.txt +0 -1
- /npcsh/npc_team/jinxs/{utils → bin}/roll.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → bin}/sample.jinx +0 -0
- /npcsh/npc_team/jinxs/{modes → bin}/spool.jinx +0 -0
- /npcsh/npc_team/jinxs/{modes → bin}/yap.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/computer_use}/trigger.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/chat.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/cmd.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/compress.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/ots.jinx +0 -0
- /npcsh/npc_team/jinxs/{code → lib/core}/python.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/sleep.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/compile.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/help.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/init.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/utils}/serve.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/set.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/utils}/usage.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.14.dist-info → npcsh-1.1.15.dist-info}/WHEEL +0 -0
- {npcsh-1.1.14.dist-info → npcsh-1.1.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
jinx_name: "sync"
|
|
2
|
+
description: "Sync npc_team files from the npcsh repo to ~/.npcsh/npc_team. Detects local modifications before overwriting."
|
|
3
|
+
inputs:
|
|
4
|
+
- force: "" # Use --force or -f to overwrite all files without prompting
|
|
5
|
+
- dry_run: "" # Use --dry-run or -d to preview changes without applying them
|
|
6
|
+
- jinxs: "" # Use --jinxs to sync only .jinx files
|
|
7
|
+
- npcs: "" # Use --npcs to sync only .npc files
|
|
8
|
+
- ctx: "" # Use --ctx to sync only .ctx files
|
|
9
|
+
- images: "" # Use --images to sync only image files (.png, .jpg, .jpeg)
|
|
10
|
+
steps:
|
|
11
|
+
- name: "sync_npc_team"
|
|
12
|
+
engine: "python"
|
|
13
|
+
code: |
|
|
14
|
+
import os
|
|
15
|
+
import hashlib
|
|
16
|
+
import shutil
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
|
|
20
|
+
force = context.get('force', False)
|
|
21
|
+
dry_run = context.get('dry_run', False)
|
|
22
|
+
sync_jinxs = context.get('jinxs', False)
|
|
23
|
+
sync_npcs = context.get('npcs', False)
|
|
24
|
+
sync_ctx = context.get('ctx', False)
|
|
25
|
+
sync_images = context.get('images', False)
|
|
26
|
+
|
|
27
|
+
# Convert string flags to boolean
|
|
28
|
+
def to_bool(val):
|
|
29
|
+
if isinstance(val, bool):
|
|
30
|
+
return val
|
|
31
|
+
if isinstance(val, str):
|
|
32
|
+
return val.lower() in ('true', '1', 'yes', 'y')
|
|
33
|
+
return bool(val)
|
|
34
|
+
|
|
35
|
+
force = to_bool(force)
|
|
36
|
+
dry_run = to_bool(dry_run)
|
|
37
|
+
sync_jinxs = to_bool(sync_jinxs)
|
|
38
|
+
sync_npcs = to_bool(sync_npcs)
|
|
39
|
+
sync_ctx = to_bool(sync_ctx)
|
|
40
|
+
sync_images = to_bool(sync_images)
|
|
41
|
+
|
|
42
|
+
# If none specified, sync all
|
|
43
|
+
sync_all = not (sync_jinxs or sync_npcs or sync_ctx or sync_images)
|
|
44
|
+
|
|
45
|
+
# Find the repo npc_team directory
|
|
46
|
+
# Check common locations
|
|
47
|
+
possible_repo_paths = [
|
|
48
|
+
Path.home() / "npcww" / "npc-core" / "npcsh" / "npcsh" / "npc_team",
|
|
49
|
+
Path.home() / "npc-core" / "npcsh" / "npcsh" / "npc_team",
|
|
50
|
+
Path.home() / "repos" / "npcsh" / "npcsh" / "npc_team",
|
|
51
|
+
Path.home() / "Projects" / "npcsh" / "npcsh" / "npc_team",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Also check if we can find it via pip show
|
|
55
|
+
try:
|
|
56
|
+
import subprocess
|
|
57
|
+
result = subprocess.run(['pip', 'show', 'npcsh', '-f'], capture_output=True, text=True)
|
|
58
|
+
if result.returncode == 0:
|
|
59
|
+
for line in result.stdout.split('\n'):
|
|
60
|
+
if line.startswith('Location:'):
|
|
61
|
+
pip_path = Path(line.split(':', 1)[1].strip()) / "npcsh" / "npc_team"
|
|
62
|
+
if pip_path.exists():
|
|
63
|
+
possible_repo_paths.insert(0, pip_path)
|
|
64
|
+
except:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
repo_npc_team = None
|
|
68
|
+
for path in possible_repo_paths:
|
|
69
|
+
if path.exists() and path.is_dir():
|
|
70
|
+
repo_npc_team = path
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
local_npc_team = Path.home() / ".npcsh" / "npc_team"
|
|
74
|
+
|
|
75
|
+
if not repo_npc_team:
|
|
76
|
+
context['output'] = "Error: Could not find npcsh repo npc_team directory.\nSearched:\n" + "\n".join(f" - {p}" for p in possible_repo_paths)
|
|
77
|
+
exit()
|
|
78
|
+
|
|
79
|
+
if not local_npc_team.exists():
|
|
80
|
+
context['output'] = f"Error: Local npc_team directory not found at {local_npc_team}"
|
|
81
|
+
exit()
|
|
82
|
+
|
|
83
|
+
def get_file_hash(filepath):
|
|
84
|
+
"""Get MD5 hash of file contents."""
|
|
85
|
+
try:
|
|
86
|
+
with open(filepath, 'rb') as f:
|
|
87
|
+
return hashlib.md5(f.read()).hexdigest()
|
|
88
|
+
except:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def get_files_recursive(base_path, extensions=None):
|
|
92
|
+
"""Get all files recursively, optionally filtered by extensions."""
|
|
93
|
+
files = []
|
|
94
|
+
for root, dirs, filenames in os.walk(base_path):
|
|
95
|
+
# Skip .git directories
|
|
96
|
+
dirs[:] = [d for d in dirs if d != '.git']
|
|
97
|
+
for filename in filenames:
|
|
98
|
+
if filename.startswith('.'):
|
|
99
|
+
continue
|
|
100
|
+
if extensions and not any(filename.endswith(ext) for ext in extensions):
|
|
101
|
+
continue
|
|
102
|
+
full_path = Path(root) / filename
|
|
103
|
+
rel_path = full_path.relative_to(base_path)
|
|
104
|
+
files.append(rel_path)
|
|
105
|
+
return files
|
|
106
|
+
|
|
107
|
+
# Build list of extensions to sync based on flags
|
|
108
|
+
sync_extensions = []
|
|
109
|
+
if sync_all or sync_npcs:
|
|
110
|
+
sync_extensions.append('.npc')
|
|
111
|
+
if sync_all or sync_ctx:
|
|
112
|
+
sync_extensions.append('.ctx')
|
|
113
|
+
if sync_all or sync_jinxs:
|
|
114
|
+
sync_extensions.append('.jinx')
|
|
115
|
+
if sync_all or sync_images:
|
|
116
|
+
sync_extensions.extend(['.png', '.jpg', '.jpeg'])
|
|
117
|
+
|
|
118
|
+
# Get files from repo
|
|
119
|
+
repo_files = get_files_recursive(repo_npc_team, sync_extensions)
|
|
120
|
+
|
|
121
|
+
output_lines = []
|
|
122
|
+
output_lines.append(f"Syncing from: {repo_npc_team}")
|
|
123
|
+
output_lines.append(f"Syncing to: {local_npc_team}")
|
|
124
|
+
|
|
125
|
+
# Show what's being synced
|
|
126
|
+
sync_types = []
|
|
127
|
+
if sync_all:
|
|
128
|
+
sync_types.append("all")
|
|
129
|
+
else:
|
|
130
|
+
if sync_npcs: sync_types.append("npcs")
|
|
131
|
+
if sync_ctx: sync_types.append("ctx")
|
|
132
|
+
if sync_jinxs: sync_types.append("jinxs")
|
|
133
|
+
if sync_images: sync_types.append("images")
|
|
134
|
+
output_lines.append(f"Syncing: {', '.join(sync_types)}")
|
|
135
|
+
|
|
136
|
+
if dry_run:
|
|
137
|
+
output_lines.append("\n[DRY RUN - No changes will be made]\n")
|
|
138
|
+
output_lines.append("")
|
|
139
|
+
|
|
140
|
+
new_files = []
|
|
141
|
+
updated_files = []
|
|
142
|
+
modified_locally = []
|
|
143
|
+
unchanged_files = []
|
|
144
|
+
|
|
145
|
+
for rel_path in repo_files:
|
|
146
|
+
repo_file = repo_npc_team / rel_path
|
|
147
|
+
local_file = local_npc_team / rel_path
|
|
148
|
+
|
|
149
|
+
if not local_file.exists():
|
|
150
|
+
new_files.append(rel_path)
|
|
151
|
+
else:
|
|
152
|
+
repo_hash = get_file_hash(repo_file)
|
|
153
|
+
local_hash = get_file_hash(local_file)
|
|
154
|
+
|
|
155
|
+
if repo_hash == local_hash:
|
|
156
|
+
unchanged_files.append(rel_path)
|
|
157
|
+
else:
|
|
158
|
+
# Check if local file is newer (possibly modified by user)
|
|
159
|
+
repo_mtime = repo_file.stat().st_mtime
|
|
160
|
+
local_mtime = local_file.stat().st_mtime
|
|
161
|
+
|
|
162
|
+
if local_mtime > repo_mtime:
|
|
163
|
+
modified_locally.append((rel_path, local_mtime, repo_mtime))
|
|
164
|
+
else:
|
|
165
|
+
updated_files.append(rel_path)
|
|
166
|
+
|
|
167
|
+
# Report findings
|
|
168
|
+
if new_files:
|
|
169
|
+
output_lines.append(f"New files to add ({len(new_files)}):")
|
|
170
|
+
for f in new_files:
|
|
171
|
+
output_lines.append(f" + {f}")
|
|
172
|
+
output_lines.append("")
|
|
173
|
+
|
|
174
|
+
if updated_files:
|
|
175
|
+
output_lines.append(f"Files to update ({len(updated_files)}):")
|
|
176
|
+
for f in updated_files:
|
|
177
|
+
output_lines.append(f" ~ {f}")
|
|
178
|
+
output_lines.append("")
|
|
179
|
+
|
|
180
|
+
if modified_locally:
|
|
181
|
+
output_lines.append(f"Locally modified files ({len(modified_locally)}):")
|
|
182
|
+
for f, local_t, repo_t in modified_locally:
|
|
183
|
+
local_dt = datetime.fromtimestamp(local_t).strftime('%Y-%m-%d %H:%M')
|
|
184
|
+
repo_dt = datetime.fromtimestamp(repo_t).strftime('%Y-%m-%d %H:%M')
|
|
185
|
+
output_lines.append(f" ! {f}")
|
|
186
|
+
output_lines.append(f" local: {local_dt} repo: {repo_dt}")
|
|
187
|
+
if not force:
|
|
188
|
+
output_lines.append(" (use --force to overwrite these)")
|
|
189
|
+
output_lines.append("")
|
|
190
|
+
|
|
191
|
+
if unchanged_files:
|
|
192
|
+
output_lines.append(f"Already up to date: {len(unchanged_files)} files")
|
|
193
|
+
output_lines.append("")
|
|
194
|
+
|
|
195
|
+
# Perform sync if not dry run
|
|
196
|
+
if not dry_run:
|
|
197
|
+
synced = 0
|
|
198
|
+
skipped = 0
|
|
199
|
+
|
|
200
|
+
# Sync new files
|
|
201
|
+
for rel_path in new_files:
|
|
202
|
+
src = repo_npc_team / rel_path
|
|
203
|
+
dst = local_npc_team / rel_path
|
|
204
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
205
|
+
shutil.copy2(src, dst)
|
|
206
|
+
synced += 1
|
|
207
|
+
|
|
208
|
+
# Sync updated files
|
|
209
|
+
for rel_path in updated_files:
|
|
210
|
+
src = repo_npc_team / rel_path
|
|
211
|
+
dst = local_npc_team / rel_path
|
|
212
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
213
|
+
shutil.copy2(src, dst)
|
|
214
|
+
synced += 1
|
|
215
|
+
|
|
216
|
+
# Handle locally modified files
|
|
217
|
+
for rel_path, _, _ in modified_locally:
|
|
218
|
+
if force:
|
|
219
|
+
src = repo_npc_team / rel_path
|
|
220
|
+
dst = local_npc_team / rel_path
|
|
221
|
+
shutil.copy2(src, dst)
|
|
222
|
+
synced += 1
|
|
223
|
+
else:
|
|
224
|
+
skipped += 1
|
|
225
|
+
|
|
226
|
+
output_lines.append(f"Synced: {synced} files")
|
|
227
|
+
if skipped:
|
|
228
|
+
output_lines.append(f"Skipped: {skipped} locally modified files")
|
|
229
|
+
|
|
230
|
+
context['output'] = "\n".join(output_lines)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
jinx_name: teamviz
|
|
2
|
+
description: "Visualize NPC team structure - NPCs, jinxs, and their relationships"
|
|
3
|
+
inputs:
|
|
4
|
+
- team_path: ""
|
|
5
|
+
- save: ""
|
|
6
|
+
|
|
7
|
+
steps:
|
|
8
|
+
- name: visualize_team
|
|
9
|
+
engine: python
|
|
10
|
+
code: |
|
|
11
|
+
import os
|
|
12
|
+
import yaml
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import matplotlib.pyplot as plt
|
|
15
|
+
import networkx as nx
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
|
|
18
|
+
team_path = context.get('team_path') or ''
|
|
19
|
+
save_path = context.get('save') or ''
|
|
20
|
+
|
|
21
|
+
# Find team path
|
|
22
|
+
if not team_path:
|
|
23
|
+
if os.path.exists('./npc_team'):
|
|
24
|
+
team_path = './npc_team'
|
|
25
|
+
elif os.path.exists(os.path.expanduser('~/.npcsh/npc_team')):
|
|
26
|
+
team_path = os.path.expanduser('~/.npcsh/npc_team')
|
|
27
|
+
else:
|
|
28
|
+
output = "No npc_team found. Specify team_path."
|
|
29
|
+
exit()
|
|
30
|
+
|
|
31
|
+
team_path = Path(team_path)
|
|
32
|
+
|
|
33
|
+
# Load NPCs
|
|
34
|
+
npcs = {}
|
|
35
|
+
for npc_file in team_path.glob("*.npc"):
|
|
36
|
+
try:
|
|
37
|
+
with open(npc_file, 'r') as f:
|
|
38
|
+
data = yaml.safe_load(f)
|
|
39
|
+
npcs[npc_file.stem] = {
|
|
40
|
+
'jinxs': data.get('jinxs', []),
|
|
41
|
+
'directive': data.get('primary_directive', '')[:100],
|
|
42
|
+
'model': data.get('model', ''),
|
|
43
|
+
}
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print("Error loading {}: {}".format(npc_file, e))
|
|
46
|
+
|
|
47
|
+
# Load team context
|
|
48
|
+
team_name = "NPC Team"
|
|
49
|
+
forenpc = None
|
|
50
|
+
for ctx_file in team_path.glob("*.ctx"):
|
|
51
|
+
try:
|
|
52
|
+
with open(ctx_file, 'r') as f:
|
|
53
|
+
ctx = yaml.safe_load(f)
|
|
54
|
+
team_name = ctx.get('name', team_name)
|
|
55
|
+
forenpc = ctx.get('forenpc')
|
|
56
|
+
except:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
# Discover all jinxs
|
|
60
|
+
jinxs_dir = team_path / "jinxs"
|
|
61
|
+
all_jinxs = {}
|
|
62
|
+
if jinxs_dir.exists():
|
|
63
|
+
for jinx_file in jinxs_dir.rglob("*.jinx"):
|
|
64
|
+
rel_path = jinx_file.relative_to(jinxs_dir)
|
|
65
|
+
jinx_name = jinx_file.stem
|
|
66
|
+
# Categorize by location
|
|
67
|
+
if 'bin' in str(rel_path):
|
|
68
|
+
category = 'bin'
|
|
69
|
+
elif 'lib' in str(rel_path):
|
|
70
|
+
parts = str(rel_path).split(os.sep)
|
|
71
|
+
category = parts[1] if len(parts) > 1 else 'lib'
|
|
72
|
+
else:
|
|
73
|
+
category = 'other'
|
|
74
|
+
all_jinxs[jinx_name] = {'path': str(rel_path), 'category': category}
|
|
75
|
+
|
|
76
|
+
# Build graph
|
|
77
|
+
G = nx.DiGraph()
|
|
78
|
+
|
|
79
|
+
# Add team node
|
|
80
|
+
G.add_node(team_name, type='team', size=3000)
|
|
81
|
+
|
|
82
|
+
# Add NPC nodes
|
|
83
|
+
for npc_name, npc_data in npcs.items():
|
|
84
|
+
node_type = 'forenpc' if npc_name == forenpc else 'npc'
|
|
85
|
+
G.add_node(npc_name, type=node_type, size=2000)
|
|
86
|
+
G.add_edge(team_name, npc_name, relation='has_npc')
|
|
87
|
+
|
|
88
|
+
# Track jinx usage
|
|
89
|
+
jinx_users = defaultdict(list)
|
|
90
|
+
npc_jinx_patterns = {}
|
|
91
|
+
|
|
92
|
+
for npc_name, npc_data in npcs.items():
|
|
93
|
+
patterns = npc_data.get('jinxs', [])
|
|
94
|
+
npc_jinx_patterns[npc_name] = patterns
|
|
95
|
+
|
|
96
|
+
# Resolve patterns to actual jinxs
|
|
97
|
+
for pattern in patterns:
|
|
98
|
+
pattern = str(pattern)
|
|
99
|
+
if '*' in pattern:
|
|
100
|
+
# Glob pattern like lib/browser/*
|
|
101
|
+
base = pattern.replace('/*', '').replace('*', '')
|
|
102
|
+
for jname, jdata in all_jinxs.items():
|
|
103
|
+
if base in jdata['path']:
|
|
104
|
+
jinx_users[jname].append(npc_name)
|
|
105
|
+
else:
|
|
106
|
+
# Exact match
|
|
107
|
+
jname = pattern.split('/')[-1]
|
|
108
|
+
if jname in all_jinxs:
|
|
109
|
+
jinx_users[jname].append(npc_name)
|
|
110
|
+
|
|
111
|
+
# Add jinx nodes (only ones that are used)
|
|
112
|
+
jinx_categories = defaultdict(list)
|
|
113
|
+
for jname, users in jinx_users.items():
|
|
114
|
+
if jname in all_jinxs:
|
|
115
|
+
cat = all_jinxs[jname]['category']
|
|
116
|
+
jinx_categories[cat].append(jname)
|
|
117
|
+
|
|
118
|
+
# Size based on usage
|
|
119
|
+
size = 500 + len(users) * 200
|
|
120
|
+
G.add_node(jname, type='jinx', category=cat, size=size, users=len(users))
|
|
121
|
+
|
|
122
|
+
for user in users:
|
|
123
|
+
G.add_edge(user, jname, relation='uses')
|
|
124
|
+
|
|
125
|
+
# Check for delegation relationships
|
|
126
|
+
for npc_name, npc_data in npcs.items():
|
|
127
|
+
directive = npc_data.get('directive', '').lower()
|
|
128
|
+
for other_npc in npcs.keys():
|
|
129
|
+
if other_npc != npc_name and other_npc in directive:
|
|
130
|
+
if 'delegate' in directive:
|
|
131
|
+
G.add_edge(npc_name, other_npc, relation='delegates_to')
|
|
132
|
+
|
|
133
|
+
# Create visualization
|
|
134
|
+
fig, ax = plt.subplots(1, 1, figsize=(16, 12))
|
|
135
|
+
|
|
136
|
+
# Layout
|
|
137
|
+
pos = nx.spring_layout(G, k=2, iterations=50, seed=42)
|
|
138
|
+
|
|
139
|
+
# Color map
|
|
140
|
+
color_map = {
|
|
141
|
+
'team': '#FF6B6B',
|
|
142
|
+
'forenpc': '#4ECDC4',
|
|
143
|
+
'npc': '#45B7D1',
|
|
144
|
+
'jinx': '#96CEB4',
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# Draw by type
|
|
148
|
+
for node_type in ['team', 'forenpc', 'npc', 'jinx']:
|
|
149
|
+
nodes = [n for n, d in G.nodes(data=True) if d.get('type') == node_type]
|
|
150
|
+
sizes = [G.nodes[n].get('size', 1000) for n in nodes]
|
|
151
|
+
nx.draw_networkx_nodes(G, pos, nodelist=nodes, node_color=color_map[node_type],
|
|
152
|
+
node_size=sizes, alpha=0.9, ax=ax)
|
|
153
|
+
|
|
154
|
+
# Draw edges by type
|
|
155
|
+
delegate_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('relation') == 'delegates_to']
|
|
156
|
+
use_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('relation') == 'uses']
|
|
157
|
+
has_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('relation') == 'has_npc']
|
|
158
|
+
|
|
159
|
+
nx.draw_networkx_edges(G, pos, edgelist=has_edges, edge_color='#999', alpha=0.5, ax=ax)
|
|
160
|
+
nx.draw_networkx_edges(G, pos, edgelist=use_edges, edge_color='#96CEB4', alpha=0.3, ax=ax)
|
|
161
|
+
nx.draw_networkx_edges(G, pos, edgelist=delegate_edges, edge_color='#FF6B6B',
|
|
162
|
+
width=2, style='dashed', ax=ax, arrows=True, arrowsize=20)
|
|
163
|
+
|
|
164
|
+
# Labels
|
|
165
|
+
nx.draw_networkx_labels(G, pos, font_size=8, font_weight='bold', ax=ax)
|
|
166
|
+
|
|
167
|
+
# Legend
|
|
168
|
+
legend_elements = [
|
|
169
|
+
plt.scatter([], [], c=color_map['team'], s=200, label='Team'),
|
|
170
|
+
plt.scatter([], [], c=color_map['forenpc'], s=200, label='ForeNPC'),
|
|
171
|
+
plt.scatter([], [], c=color_map['npc'], s=200, label='NPC'),
|
|
172
|
+
plt.scatter([], [], c=color_map['jinx'], s=200, label='Jinx'),
|
|
173
|
+
]
|
|
174
|
+
ax.legend(handles=legend_elements, loc='upper left')
|
|
175
|
+
|
|
176
|
+
ax.set_title(team_name + " - Team Structure", fontsize=14, fontweight='bold')
|
|
177
|
+
ax.axis('off')
|
|
178
|
+
|
|
179
|
+
plt.tight_layout()
|
|
180
|
+
|
|
181
|
+
if save_path:
|
|
182
|
+
plt.savefig(save_path, dpi=150, bbox_inches='tight')
|
|
183
|
+
plt.close()
|
|
184
|
+
output = "Saved to " + save_path
|
|
185
|
+
else:
|
|
186
|
+
plt.show()
|
|
187
|
+
output = "Displayed team visualization"
|
|
188
|
+
|
|
189
|
+
# Text summary
|
|
190
|
+
summary = "\n\n--- Team Summary ---\n"
|
|
191
|
+
summary += "Team: {}\n".format(team_name)
|
|
192
|
+
summary += "ForeNPC: {}\n".format(forenpc or 'None')
|
|
193
|
+
summary += "NPCs ({}): {}\n".format(len(npcs), ', '.join(sorted(npcs.keys())))
|
|
194
|
+
summary += "Total Jinxs: {}\n".format(len(all_jinxs))
|
|
195
|
+
summary += "\nJinxs by category:\n"
|
|
196
|
+
for cat, jlist in sorted(jinx_categories.items()):
|
|
197
|
+
summary += " {}: {} jinxs\n".format(cat, len(jlist))
|
|
198
|
+
|
|
199
|
+
summary += "\nMost shared jinxs:\n"
|
|
200
|
+
shared = sorted(jinx_users.items(), key=lambda x: len(x[1]), reverse=True)[:10]
|
|
201
|
+
for jname, users in shared:
|
|
202
|
+
if len(users) > 1:
|
|
203
|
+
summary += " {} (used by {}): {}\n".format(jname, len(users), ', '.join(users))
|
|
204
|
+
|
|
205
|
+
output = output + summary
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
jinx_name: type_text
|
|
2
|
+
description: Type text using keyboard
|
|
3
|
+
inputs:
|
|
4
|
+
- text: "" # Text to type
|
|
5
|
+
|
|
6
|
+
steps:
|
|
7
|
+
- name: perform_type
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
from npcpy.work.desktop import perform_action
|
|
11
|
+
|
|
12
|
+
text = context.get('text', '')
|
|
13
|
+
messages = context.get('messages', [])
|
|
14
|
+
|
|
15
|
+
if not text:
|
|
16
|
+
context['output'] = "Usage: /type_text <text to type>"
|
|
17
|
+
context['messages'] = messages
|
|
18
|
+
exit()
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
perform_action({'type': 'type', 'text': text})
|
|
22
|
+
preview = text[:30] + "..." if len(text) > 30 else text
|
|
23
|
+
context['output'] = f"Typed: {preview}"
|
|
24
|
+
except Exception as e:
|
|
25
|
+
context['output'] = f"Type failed: {e}"
|
|
26
|
+
|
|
27
|
+
context['messages'] = messages
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
jinx_name: "verbose"
|
|
2
|
+
description: "Enable verbose logging - shows debug output from npcpy"
|
|
3
|
+
inputs: []
|
|
4
|
+
steps:
|
|
5
|
+
- name: "set_verbose"
|
|
6
|
+
engine: "python"
|
|
7
|
+
code: |
|
|
8
|
+
state = context.get('state')
|
|
9
|
+
output_messages = context.get('messages', [])
|
|
10
|
+
|
|
11
|
+
if state:
|
|
12
|
+
result = state.set_log_level("verbose")
|
|
13
|
+
context['output'] = result
|
|
14
|
+
else:
|
|
15
|
+
context['output'] = "Error: state not available"
|
|
16
|
+
|
|
17
|
+
context['messages'] = output_messages
|
|
@@ -41,27 +41,15 @@ steps:
|
|
|
41
41
|
except (ValueError, TypeError):
|
|
42
42
|
width = 1024
|
|
43
43
|
|
|
44
|
-
# Get model and provider
|
|
44
|
+
# Get model and provider from context or environment
|
|
45
45
|
model = context.get('model')
|
|
46
46
|
provider = context.get('provider')
|
|
47
|
-
|
|
48
|
-
# Use NPC's model/provider as fallback
|
|
49
|
-
if not model and npc and hasattr(npc, 'model') and npc.model:
|
|
50
|
-
model = npc.model
|
|
51
|
-
if not provider and npc and hasattr(npc, 'provider') and npc.provider:
|
|
52
|
-
provider = npc.provider
|
|
53
|
-
|
|
47
|
+
|
|
54
48
|
# Fallback to environment variables
|
|
55
49
|
if not model:
|
|
56
50
|
model = os.getenv('NPCSH_IMAGE_GEN_MODEL')
|
|
57
51
|
if not provider:
|
|
58
52
|
provider = os.getenv('NPCSH_IMAGE_GEN_PROVIDER')
|
|
59
|
-
|
|
60
|
-
# Final hardcoded fallbacks if nothing else is set
|
|
61
|
-
if not model:
|
|
62
|
-
model = "runwayml/stable-diffusion-v1-5"
|
|
63
|
-
if not provider:
|
|
64
|
-
provider = "diffusers"
|
|
65
53
|
|
|
66
54
|
# Parse attachments
|
|
67
55
|
input_images = []
|
|
@@ -93,12 +81,11 @@ steps:
|
|
|
93
81
|
images_list = result
|
|
94
82
|
|
|
95
83
|
saved_files = []
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
|
|
98
85
|
for i, image in enumerate(images_list):
|
|
99
86
|
if image is None:
|
|
100
87
|
continue
|
|
101
|
-
|
|
88
|
+
|
|
102
89
|
# Determine output filename
|
|
103
90
|
if output_name and str(output_name).strip():
|
|
104
91
|
base_name, ext = os.path.splitext(os.path.expanduser(str(output_name)))
|
|
@@ -111,25 +98,16 @@ steps:
|
|
|
111
98
|
os.path.expanduser("~/.npcsh/images/")
|
|
112
99
|
+ f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{i}.png"
|
|
113
100
|
)
|
|
114
|
-
|
|
101
|
+
|
|
115
102
|
# Save image to file
|
|
116
103
|
image.save(current_output_file)
|
|
117
104
|
saved_files.append(current_output_file)
|
|
118
105
|
|
|
119
|
-
# Convert image to base64 and create an HTML <img> tag
|
|
120
|
-
with open(current_output_file, 'rb') as f:
|
|
121
|
-
img_data = base64.b64encode(f.read()).decode()
|
|
122
|
-
# Using raw HTML <img> tag with data URI
|
|
123
|
-
html_image_tags.append(f'<img src="data:image/png;base64,{img_data}" alt="Generated Image {i+1}" style="max-width: 100%; display: block; margin-top: 10px;">')
|
|
124
|
-
|
|
125
106
|
if saved_files:
|
|
126
|
-
|
|
107
|
+
output = f"Image(s) generated: {', '.join(saved_files)}"
|
|
127
108
|
if input_images:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
output = output_text_message # Keep the text message clean
|
|
131
|
-
output += f"\n\nThe image files have been saved and are ready to view."
|
|
132
|
-
output += "\n\n" + "\n".join(html_image_tags) # Append all HTML <img> tags to the output
|
|
109
|
+
output = f"Image(s) edited: {', '.join(saved_files)}"
|
|
110
|
+
context['generated_images'] = saved_files
|
|
133
111
|
else:
|
|
134
112
|
output = "No images were generated."
|
|
135
113
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
jinx_name: wait
|
|
2
|
+
description: Wait/pause for a specified duration in seconds
|
|
3
|
+
inputs:
|
|
4
|
+
- duration: 1 # Duration to wait in seconds
|
|
5
|
+
|
|
6
|
+
steps:
|
|
7
|
+
- name: perform_wait
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
duration = float(context.get('duration', 1))
|
|
13
|
+
messages = context.get('messages', [])
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
time.sleep(duration)
|
|
17
|
+
context['output'] = f"Waited {duration} seconds"
|
|
18
|
+
except Exception as e:
|
|
19
|
+
context['output'] = f"Wait failed: {e}"
|
|
20
|
+
|
|
21
|
+
context['messages'] = messages
|