npcsh 1.1.22__py3-none-any.whl → 1.1.23__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 +272 -120
- npcsh/benchmark/npcsh_agent.py +77 -240
- npcsh/benchmark/templates/install-npcsh.sh.j2 +12 -4
- npcsh/config.py +5 -2
- npcsh/npc_team/alicanto.npc +4 -8
- npcsh/npc_team/corca.npc +5 -11
- npcsh/npc_team/frederic.npc +4 -6
- npcsh/npc_team/guac.npc +4 -4
- npcsh/npc_team/jinxs/lib/core/delegate.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/edit_file.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/sh.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/skill.jinx +59 -0
- npcsh/npc_team/jinxs/lib/utils/help.jinx +194 -10
- npcsh/npc_team/jinxs/lib/utils/init.jinx +528 -37
- npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +0 -1
- npcsh/npc_team/jinxs/lib/utils/serve.jinx +938 -21
- npcsh-1.1.22.data/data/npcsh/npc_team/config_tui.jinx → npcsh/npc_team/jinxs/modes/config.jinx +1 -1
- npcsh/npc_team/jinxs/modes/convene.jinx +76 -3
- npcsh/npc_team/jinxs/modes/crond.jinx +818 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +76 -14
- npcsh/npc_team/jinxs/modes/roll.jinx +368 -55
- npcsh/npc_team/jinxs/modes/skills.jinx +621 -0
- npcsh/npc_team/jinxs/modes/yap.jinx +504 -30
- npcsh/npc_team/jinxs/skills/code-review/SKILL.md +45 -0
- npcsh/npc_team/jinxs/skills/debugging/SKILL.md +44 -0
- npcsh/npc_team/jinxs/skills/git-workflow.jinx +44 -0
- npcsh/npc_team/kadiefa.npc +4 -5
- npcsh/npc_team/npcsh.ctx +16 -0
- npcsh/npc_team/plonk.npc +5 -9
- npcsh/npc_team/sibiji.npc +13 -5
- npcsh/npcsh.py +1 -0
- npcsh/routes.py +0 -4
- npcsh/yap.py +22 -4
- npcsh-1.1.23.data/data/npcsh/npc_team/SKILL.md +44 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/alicanto.npc +4 -8
- npcsh/npc_team/jinxs/modes/config_tui.jinx → npcsh-1.1.23.data/data/npcsh/npc_team/config.jinx +1 -1
- npcsh-1.1.23.data/data/npcsh/npc_team/convene.jinx +670 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca.npc +5 -11
- npcsh-1.1.23.data/data/npcsh/npc_team/crond.jinx +818 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/delegate.jinx +1 -1
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/edit_file.jinx +1 -1
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/frederic.npc +4 -6
- npcsh-1.1.23.data/data/npcsh/npc_team/git-workflow.jinx +44 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/guac.npc +4 -4
- npcsh-1.1.23.data/data/npcsh/npc_team/help.jinx +236 -0
- npcsh-1.1.23.data/data/npcsh/npc_team/init.jinx +532 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/jinxs.jinx +0 -1
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/kadiefa.npc +4 -5
- npcsh-1.1.23.data/data/npcsh/npc_team/npcsh.ctx +34 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonk.jinx +76 -14
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonk.npc +5 -9
- npcsh-1.1.23.data/data/npcsh/npc_team/roll.jinx +378 -0
- npcsh-1.1.23.data/data/npcsh/npc_team/serve.jinx +943 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sh.jinx +1 -1
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sibiji.npc +13 -5
- npcsh-1.1.23.data/data/npcsh/npc_team/skill.jinx +59 -0
- npcsh-1.1.23.data/data/npcsh/npc_team/skills.jinx +621 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/yap.jinx +504 -30
- {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/METADATA +168 -7
- npcsh-1.1.23.dist-info/RECORD +216 -0
- npcsh/npc_team/jinxs/incognide/add_tab.jinx +0 -11
- npcsh/npc_team/jinxs/incognide/close_pane.jinx +0 -9
- npcsh/npc_team/jinxs/incognide/close_tab.jinx +0 -10
- npcsh/npc_team/jinxs/incognide/confirm.jinx +0 -10
- npcsh/npc_team/jinxs/incognide/focus_pane.jinx +0 -9
- npcsh/npc_team/jinxs/incognide/list_panes.jinx +0 -8
- npcsh/npc_team/jinxs/incognide/navigate.jinx +0 -10
- npcsh/npc_team/jinxs/incognide/notify.jinx +0 -10
- npcsh/npc_team/jinxs/incognide/open_pane.jinx +0 -13
- npcsh/npc_team/jinxs/incognide/read_pane.jinx +0 -9
- npcsh/npc_team/jinxs/incognide/run_terminal.jinx +0 -10
- npcsh/npc_team/jinxs/incognide/send_message.jinx +0 -10
- npcsh/npc_team/jinxs/incognide/split_pane.jinx +0 -12
- npcsh/npc_team/jinxs/incognide/switch_npc.jinx +0 -10
- npcsh/npc_team/jinxs/incognide/switch_tab.jinx +0 -10
- npcsh/npc_team/jinxs/incognide/write_file.jinx +0 -11
- npcsh/npc_team/jinxs/incognide/zen_mode.jinx +0 -9
- npcsh/npc_team/jinxs/lib/core/convene.jinx +0 -232
- npcsh-1.1.22.data/data/npcsh/npc_team/add_tab.jinx +0 -11
- npcsh-1.1.22.data/data/npcsh/npc_team/close_pane.jinx +0 -9
- npcsh-1.1.22.data/data/npcsh/npc_team/close_tab.jinx +0 -10
- npcsh-1.1.22.data/data/npcsh/npc_team/confirm.jinx +0 -10
- npcsh-1.1.22.data/data/npcsh/npc_team/convene.jinx +0 -232
- npcsh-1.1.22.data/data/npcsh/npc_team/focus_pane.jinx +0 -9
- npcsh-1.1.22.data/data/npcsh/npc_team/help.jinx +0 -52
- npcsh-1.1.22.data/data/npcsh/npc_team/init.jinx +0 -41
- npcsh-1.1.22.data/data/npcsh/npc_team/list_panes.jinx +0 -8
- npcsh-1.1.22.data/data/npcsh/npc_team/navigate.jinx +0 -10
- npcsh-1.1.22.data/data/npcsh/npc_team/notify.jinx +0 -10
- npcsh-1.1.22.data/data/npcsh/npc_team/npcsh.ctx +0 -18
- npcsh-1.1.22.data/data/npcsh/npc_team/open_pane.jinx +0 -13
- npcsh-1.1.22.data/data/npcsh/npc_team/read_pane.jinx +0 -9
- npcsh-1.1.22.data/data/npcsh/npc_team/roll.jinx +0 -65
- npcsh-1.1.22.data/data/npcsh/npc_team/run_terminal.jinx +0 -10
- npcsh-1.1.22.data/data/npcsh/npc_team/send_message.jinx +0 -10
- npcsh-1.1.22.data/data/npcsh/npc_team/serve.jinx +0 -26
- npcsh-1.1.22.data/data/npcsh/npc_team/split_pane.jinx +0 -12
- npcsh-1.1.22.data/data/npcsh/npc_team/switch_npc.jinx +0 -10
- npcsh-1.1.22.data/data/npcsh/npc_team/switch_tab.jinx +0 -10
- npcsh-1.1.22.data/data/npcsh/npc_team/write_file.jinx +0 -11
- npcsh-1.1.22.data/data/npcsh/npc_team/zen_mode.jinx +0 -9
- npcsh-1.1.22.dist-info/RECORD +0 -240
- /npcsh/npc_team/jinxs/{incognide → lib/utils}/incognide.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/alicanto.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/arxiv.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/benchmark.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/click.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/db_search.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/file_search.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/git.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/guac.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/incognide.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/key_press.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/kg.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/memories.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/models.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/nql.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/papers.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/paste.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/pti.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/reattach.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/setup.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/shh.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sql.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/switch.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/switches.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/sync.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/team.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/type_text.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/verbose.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/wait.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/wander.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/web_search.jinx +0 -0
- {npcsh-1.1.22.data → npcsh-1.1.23.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/WHEEL +0 -0
- {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/entry_points.txt +0 -0
- {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.22.dist-info → npcsh-1.1.23.dist-info}/top_level.txt +0 -0
npcsh/benchmark/npcsh_agent.py
CHANGED
|
@@ -31,6 +31,9 @@ class NpcshAgent(BaseInstalledAgent):
|
|
|
31
31
|
|
|
32
32
|
SUPPORTS_ATIF = True # Agent Trajectory Interchange Format
|
|
33
33
|
|
|
34
|
+
# Root of the npcsh source tree (two levels up from this file)
|
|
35
|
+
_NPCSH_SRC = Path(__file__).resolve().parent.parent.parent
|
|
36
|
+
|
|
34
37
|
def __init__(self, logs_dir: Path = None, model_name: str = None, logger=None, **kwargs):
|
|
35
38
|
super().__init__(logs_dir=logs_dir, model_name=model_name, logger=logger, **kwargs)
|
|
36
39
|
|
|
@@ -43,138 +46,96 @@ class NpcshAgent(BaseInstalledAgent):
|
|
|
43
46
|
"""Path to the jinja template script for installing npcsh in the container."""
|
|
44
47
|
return Path(__file__).parent / "templates" / "install-npcsh.sh.j2"
|
|
45
48
|
|
|
46
|
-
def
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
async def setup(self, environment) -> None:
|
|
50
|
+
"""Upload local npcsh + npcpy source, then run install script."""
|
|
51
|
+
import shutil
|
|
52
|
+
import tempfile
|
|
53
|
+
|
|
54
|
+
npcsh_src = self._NPCSH_SRC
|
|
55
|
+
npcpy_src = npcsh_src.parent / "npcpy"
|
|
56
|
+
|
|
57
|
+
# Create /src in container
|
|
58
|
+
await environment.exec(command="mkdir -p /src")
|
|
59
|
+
|
|
60
|
+
# Copy source to temp dir excluding .git and caches
|
|
61
|
+
def _copy_clean(src, name):
|
|
62
|
+
tmp = Path(tempfile.mkdtemp()) / name
|
|
63
|
+
shutil.copytree(
|
|
64
|
+
src, tmp,
|
|
65
|
+
ignore=shutil.ignore_patterns(
|
|
66
|
+
'.git', '__pycache__', '*.pyc', 'dist', 'build',
|
|
67
|
+
'*.egg-info', 'jobs', 'dataset_cache',
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
return tmp
|
|
71
|
+
|
|
72
|
+
clean_npcsh = _copy_clean(npcsh_src, "npcsh")
|
|
73
|
+
await environment.upload_dir(
|
|
74
|
+
source_dir=str(clean_npcsh),
|
|
75
|
+
target_dir="/src/npcsh",
|
|
76
|
+
)
|
|
65
77
|
|
|
66
|
-
|
|
78
|
+
if npcpy_src.exists():
|
|
79
|
+
clean_npcpy = _copy_clean(npcpy_src, "npcpy")
|
|
80
|
+
await environment.upload_dir(
|
|
81
|
+
source_dir=str(clean_npcpy),
|
|
82
|
+
target_dir="/src/npcpy",
|
|
83
|
+
)
|
|
67
84
|
|
|
68
|
-
|
|
69
|
-
1. Call edit_file to write code files. Call sh to run commands.
|
|
70
|
-
2. Run any test scripts mentioned in the task
|
|
71
|
-
3. Check the output carefully - look for "PASS", "SUCCESS", "OK" or similar
|
|
72
|
-
4. If the test failed, analyze why and try a completely different approach
|
|
73
|
-
5. Repeat until the test passes"""
|
|
85
|
+
await super().setup(environment)
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
def create_run_agent_commands(self, instruction: str) -> list:
|
|
88
|
+
"""Run instruction through npcsh -c, which handles everything."""
|
|
89
|
+
escaped_instruction = shlex.quote(instruction)
|
|
77
90
|
|
|
78
|
-
|
|
91
|
+
# Forward env vars into the container — npcsh reads these directly
|
|
92
|
+
env_vars = []
|
|
93
|
+
for key in [
|
|
94
|
+
"ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_API_KEY",
|
|
95
|
+
"GEMINI_API_KEY", "DEEPSEEK_API_KEY", "GROQ_API_KEY",
|
|
96
|
+
"OPENROUTER_API_KEY", "OLLAMA_HOST",
|
|
97
|
+
]:
|
|
98
|
+
val = os.environ.get(key)
|
|
99
|
+
if val:
|
|
100
|
+
env_vars.append(f'{key}="{val}"')
|
|
101
|
+
|
|
102
|
+
# Model/provider from Harbor's model_name (e.g. "ollama/phi4")
|
|
103
|
+
model_name = self.model_name or ""
|
|
104
|
+
if "/" in model_name:
|
|
79
105
|
provider, model = model_name.split("/", 1)
|
|
80
|
-
elif model_name:
|
|
81
|
-
provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
|
|
82
|
-
model = model_name
|
|
83
106
|
else:
|
|
84
107
|
provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
|
|
85
|
-
model = os.environ.get("NPCSH_CHAT_MODEL", "")
|
|
86
|
-
|
|
87
|
-
# Map provider names to npcsh provider format
|
|
88
|
-
provider_map = {
|
|
89
|
-
"anthropic": "anthropic",
|
|
90
|
-
"openai": "openai",
|
|
91
|
-
"google": "gemini",
|
|
92
|
-
"gemini": "gemini",
|
|
93
|
-
"deepseek": "deepseek",
|
|
94
|
-
"ollama": "ollama",
|
|
95
|
-
"groq": "groq",
|
|
96
|
-
"openrouter": "openrouter",
|
|
97
|
-
}
|
|
98
|
-
npcsh_provider = provider_map.get(provider.lower(), provider)
|
|
99
|
-
|
|
100
|
-
# Build environment variables for API keys
|
|
101
|
-
env_vars = []
|
|
102
|
-
api_key_map = {
|
|
103
|
-
"anthropic": ["ANTHROPIC_API_KEY"],
|
|
104
|
-
"openai": ["OPENAI_API_KEY"],
|
|
105
|
-
"gemini": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
106
|
-
"google": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
107
|
-
"deepseek": ["DEEPSEEK_API_KEY"],
|
|
108
|
-
"groq": ["GROQ_API_KEY"],
|
|
109
|
-
"openrouter": ["OPENROUTER_API_KEY"],
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
added_keys = set()
|
|
113
|
-
for prov, env_keys in api_key_map.items():
|
|
114
|
-
for env_key in env_keys:
|
|
115
|
-
if env_key in os.environ:
|
|
116
|
-
# For Gemini, always pass as GOOGLE_API_KEY (what litellm expects)
|
|
117
|
-
target_key = "GOOGLE_API_KEY" if env_key == "GEMINI_API_KEY" else env_key
|
|
118
|
-
if target_key not in added_keys:
|
|
119
|
-
env_vars.append(f'{target_key}="{os.environ[env_key]}"')
|
|
120
|
-
added_keys.add(target_key)
|
|
121
|
-
break
|
|
122
|
-
|
|
123
|
-
env_prefix = " ".join(env_vars) + " " if env_vars else ""
|
|
124
|
-
|
|
125
|
-
# Output directory for logs
|
|
126
|
-
output_dir = str(self.logs_dir / "npcsh_output")
|
|
127
|
-
output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
|
|
108
|
+
model = model_name or os.environ.get("NPCSH_CHAT_MODEL", "")
|
|
128
109
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
command="touch /app/.npcsh_global",
|
|
140
|
-
timeout_sec=10
|
|
141
|
-
))
|
|
142
|
-
|
|
143
|
-
# Run npcsh with the instruction
|
|
144
|
-
# Using corca NPC which has edit_file tool for writing files
|
|
145
|
-
# Using the npc CLI which supports single-command execution
|
|
146
|
-
# NPCSH_DEFAULT_MODE=agent enables automatic tool execution
|
|
147
|
-
ollama_env = ""
|
|
148
|
-
if npcsh_provider == "ollama":
|
|
149
|
-
ollama_host = os.environ.get("OLLAMA_HOST", "http://host.docker.internal:11434")
|
|
150
|
-
ollama_env = f'OLLAMA_HOST="{ollama_host}" '
|
|
151
|
-
|
|
152
|
-
npcsh_cmd = (
|
|
153
|
-
f'{env_prefix}'
|
|
154
|
-
f'{ollama_env}'
|
|
155
|
-
f'NPCSH_CHAT_MODEL="{model}" '
|
|
156
|
-
f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
|
|
157
|
-
f'NPCSH_STREAM_OUTPUT=0 '
|
|
158
|
-
f'NPCSH_DEFAULT_MODE=agent '
|
|
159
|
-
f'npc --npc corca {escaped_instruction} '
|
|
160
|
-
f'2>&1 | tee {shlex.quote(output_file)}'
|
|
161
|
-
)
|
|
110
|
+
env_vars.append(f'NPCSH_CHAT_MODEL="{model}"')
|
|
111
|
+
env_vars.append(f'NPCSH_CHAT_PROVIDER="{provider}"')
|
|
112
|
+
env_vars.append('NPCSH_STREAM_OUTPUT=0')
|
|
113
|
+
env_vars.append('NPCSH_DEBUG=1')
|
|
114
|
+
|
|
115
|
+
if provider == "ollama":
|
|
116
|
+
if "OLLAMA_HOST" not in os.environ:
|
|
117
|
+
env_vars.append('OLLAMA_HOST="http://host.docker.internal:11434"')
|
|
118
|
+
# Use 16k context for ollama models to avoid losing instructions
|
|
119
|
+
env_vars.append('NPCSH_OLLAMA_NUM_CTX=16384')
|
|
162
120
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
))
|
|
121
|
+
env_prefix = " ".join(env_vars)
|
|
122
|
+
output_dir = str(self.logs_dir / "npcsh_output")
|
|
123
|
+
output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
|
|
167
124
|
|
|
168
|
-
return
|
|
125
|
+
return [
|
|
126
|
+
ExecInput(command=f"mkdir -p {shlex.quote(output_dir)}", timeout_sec=30),
|
|
127
|
+
ExecInput(command="touch /app/.npcsh_global", timeout_sec=10),
|
|
128
|
+
ExecInput(
|
|
129
|
+
command=f'{env_prefix} npcsh -c {escaped_instruction} 2>&1 | tee {shlex.quote(output_file)}',
|
|
130
|
+
timeout_sec=1800,
|
|
131
|
+
),
|
|
132
|
+
]
|
|
169
133
|
|
|
170
134
|
def populate_context_post_run(self, context: AgentContext) -> None:
|
|
171
135
|
"""
|
|
172
136
|
Populate the context with results of the agent execution.
|
|
173
137
|
|
|
174
138
|
Parses the output file to extract token usage metrics.
|
|
175
|
-
|
|
176
|
-
Args:
|
|
177
|
-
context: The AgentContext to populate with metrics
|
|
178
139
|
"""
|
|
179
140
|
output_file = self.logs_dir / "npcsh_output" / "output.jsonl"
|
|
180
141
|
|
|
@@ -187,149 +148,25 @@ WORKFLOW:
|
|
|
187
148
|
with open(output_file, 'r') as f:
|
|
188
149
|
content = f.read()
|
|
189
150
|
|
|
190
|
-
# Try to parse as JSONL first
|
|
191
151
|
for line in content.strip().split('\n'):
|
|
192
152
|
if not line.strip():
|
|
193
153
|
continue
|
|
194
154
|
try:
|
|
195
155
|
event = json.loads(line)
|
|
196
|
-
# Extract token usage from events if present
|
|
197
156
|
if isinstance(event, dict):
|
|
198
157
|
usage = event.get('usage', {})
|
|
199
158
|
total_input_tokens += usage.get('input_tokens', 0)
|
|
200
159
|
total_output_tokens += usage.get('output_tokens', 0)
|
|
201
160
|
total_cost_usd += usage.get('cost_usd', 0.0)
|
|
202
161
|
except json.JSONDecodeError:
|
|
203
|
-
# Not JSON, just regular output
|
|
204
162
|
pass
|
|
205
163
|
|
|
206
164
|
except Exception as e:
|
|
207
165
|
self.logger.warning(f"Failed to parse npcsh output: {e}")
|
|
208
166
|
|
|
209
|
-
# Set context metrics
|
|
210
167
|
if hasattr(context, 'input_tokens'):
|
|
211
168
|
context.input_tokens = total_input_tokens
|
|
212
169
|
if hasattr(context, 'output_tokens'):
|
|
213
170
|
context.output_tokens = total_output_tokens
|
|
214
171
|
if hasattr(context, 'cost_usd'):
|
|
215
172
|
context.cost_usd = total_cost_usd
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
class NpcshAgentWithNpc(NpcshAgent):
|
|
219
|
-
"""
|
|
220
|
-
Variant that uses a specific NPC for task execution.
|
|
221
|
-
|
|
222
|
-
This allows benchmarking specific NPCs like sibiji (orchestrator),
|
|
223
|
-
corca (coding), or custom NPCs.
|
|
224
|
-
|
|
225
|
-
Usage:
|
|
226
|
-
harbor run -d terminal-bench@2.0 \\
|
|
227
|
-
--agent-import-path "npcsh.benchmark:NpcshAgentWithNpc" \\
|
|
228
|
-
-m anthropic/claude-sonnet-4-20250514 -n 4
|
|
229
|
-
"""
|
|
230
|
-
|
|
231
|
-
def __init__(self, *args, npc_name: str = "sibiji", **kwargs):
|
|
232
|
-
super().__init__(*args, **kwargs)
|
|
233
|
-
self.npc_name = npc_name
|
|
234
|
-
|
|
235
|
-
@staticmethod
|
|
236
|
-
def name() -> str:
|
|
237
|
-
return "npcsh-npc"
|
|
238
|
-
|
|
239
|
-
def create_run_agent_commands(self, instruction: str) -> list:
|
|
240
|
-
"""Create commands using a specific NPC."""
|
|
241
|
-
# Wrap the instruction with tool usage directions and retry logic
|
|
242
|
-
tool_instruction = f"""You have access to tools: edit_file (for writing/creating files), sh (for running shell commands), and python (for running Python code).
|
|
243
|
-
|
|
244
|
-
IMPORTANT RULES:
|
|
245
|
-
1. You MUST call these tools using the function calling interface to complete the task. Do NOT write tool names as text - invoke them as function calls.
|
|
246
|
-
2. After implementing a solution, you MUST verify it works by running any provided test scripts.
|
|
247
|
-
3. If a test fails or produces an error, you MUST try a DIFFERENT approach. Do not give up.
|
|
248
|
-
4. Keep trying different approaches until you succeed or have tried at least 10 different solutions.
|
|
249
|
-
5. NEVER assume success - always check the actual output of test commands.
|
|
250
|
-
|
|
251
|
-
Task: {instruction}
|
|
252
|
-
|
|
253
|
-
WORKFLOW:
|
|
254
|
-
1. Call edit_file to write code files. Call sh to run commands.
|
|
255
|
-
2. Run any test scripts mentioned in the task
|
|
256
|
-
3. Check the output carefully - look for "PASS", "SUCCESS", "OK" or similar
|
|
257
|
-
4. If the test failed, analyze why and try a completely different approach
|
|
258
|
-
5. Repeat until the test passes"""
|
|
259
|
-
|
|
260
|
-
escaped_instruction = shlex.quote(tool_instruction)
|
|
261
|
-
model_name = self.model_name
|
|
262
|
-
|
|
263
|
-
if model_name and "/" in model_name:
|
|
264
|
-
provider, model = model_name.split("/", 1)
|
|
265
|
-
elif model_name:
|
|
266
|
-
provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
|
|
267
|
-
model = model_name
|
|
268
|
-
else:
|
|
269
|
-
provider = os.environ.get("NPCSH_CHAT_PROVIDER", "")
|
|
270
|
-
model = os.environ.get("NPCSH_CHAT_MODEL", "")
|
|
271
|
-
|
|
272
|
-
provider_map = {
|
|
273
|
-
"anthropic": "anthropic",
|
|
274
|
-
"openai": "openai",
|
|
275
|
-
"google": "gemini",
|
|
276
|
-
"gemini": "gemini",
|
|
277
|
-
"deepseek": "deepseek",
|
|
278
|
-
"ollama": "ollama",
|
|
279
|
-
}
|
|
280
|
-
npcsh_provider = provider_map.get(provider.lower(), provider)
|
|
281
|
-
|
|
282
|
-
env_vars = []
|
|
283
|
-
api_key_map = {
|
|
284
|
-
"anthropic": "ANTHROPIC_API_KEY",
|
|
285
|
-
"openai": "OPENAI_API_KEY",
|
|
286
|
-
"gemini": "GOOGLE_API_KEY",
|
|
287
|
-
"deepseek": "DEEPSEEK_API_KEY",
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
for prov, env_key in api_key_map.items():
|
|
291
|
-
if env_key in os.environ:
|
|
292
|
-
env_vars.append(f'{env_key}="{os.environ[env_key]}"')
|
|
293
|
-
|
|
294
|
-
env_prefix = " ".join(env_vars) + " " if env_vars else ""
|
|
295
|
-
|
|
296
|
-
output_dir = str(self.logs_dir / "npcsh_output")
|
|
297
|
-
output_file = str(self.logs_dir / "npcsh_output" / "output.jsonl")
|
|
298
|
-
|
|
299
|
-
commands = []
|
|
300
|
-
|
|
301
|
-
commands.append(ExecInput(
|
|
302
|
-
command=f"mkdir -p {shlex.quote(output_dir)}",
|
|
303
|
-
timeout_sec=30
|
|
304
|
-
))
|
|
305
|
-
|
|
306
|
-
# Create .npcsh_global file to use global team and avoid interactive prompts
|
|
307
|
-
commands.append(ExecInput(
|
|
308
|
-
command="touch /app/.npcsh_global",
|
|
309
|
-
timeout_sec=10
|
|
310
|
-
))
|
|
311
|
-
|
|
312
|
-
# Use specific NPC with --npc flag
|
|
313
|
-
# NPCSH_DEFAULT_MODE=agent enables automatic tool execution
|
|
314
|
-
ollama_env = ""
|
|
315
|
-
if npcsh_provider == "ollama":
|
|
316
|
-
ollama_host = os.environ.get("OLLAMA_HOST", "http://host.docker.internal:11434")
|
|
317
|
-
ollama_env = f'OLLAMA_HOST="{ollama_host}" '
|
|
318
|
-
|
|
319
|
-
npcsh_cmd = (
|
|
320
|
-
f'{env_prefix}'
|
|
321
|
-
f'{ollama_env}'
|
|
322
|
-
f'NPCSH_CHAT_MODEL="{model}" '
|
|
323
|
-
f'NPCSH_CHAT_PROVIDER="{npcsh_provider}" '
|
|
324
|
-
f'NPCSH_STREAM_OUTPUT=0 '
|
|
325
|
-
f'NPCSH_DEFAULT_MODE=agent '
|
|
326
|
-
f'npc --npc {self.npc_name} {escaped_instruction} '
|
|
327
|
-
f'2>&1 | tee {shlex.quote(output_file)}'
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
commands.append(ExecInput(
|
|
331
|
-
command=npcsh_cmd,
|
|
332
|
-
timeout_sec=600,
|
|
333
|
-
))
|
|
334
|
-
|
|
335
|
-
return commands
|
|
@@ -12,10 +12,18 @@ if ! command -v pip &> /dev/null; then
|
|
|
12
12
|
apt-get update && apt-get install -y python3-pip
|
|
13
13
|
fi
|
|
14
14
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
# Upgrade pip first to handle modern packages and extras
|
|
16
|
+
echo "Upgrading pip..."
|
|
17
|
+
pip install --upgrade pip 2>/dev/null || pip install --break-system-packages --upgrade pip 2>/dev/null || true
|
|
18
|
+
|
|
19
|
+
# Install from local source (uploaded by agent setup)
|
|
20
|
+
echo "Installing npcpy from local source..."
|
|
21
|
+
if [ -d /src/npcpy ]; then
|
|
22
|
+
pip install --break-system-packages -e /src/npcpy 2>/dev/null || pip install -e /src/npcpy
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
echo "Installing npcsh from local source..."
|
|
26
|
+
pip install --break-system-packages -e /src/npcsh ollama 2>/dev/null || pip install -e /src/npcsh ollama
|
|
19
27
|
|
|
20
28
|
# Verify installation
|
|
21
29
|
echo "Verifying npcsh installation..."
|
npcsh/config.py
CHANGED
|
@@ -28,9 +28,9 @@ NPCSH_DEFAULT_MODE = os.environ.get("NPCSH_DEFAULT_MODE", "agent")
|
|
|
28
28
|
NPCSH_VISION_MODEL = os.environ.get("NPCSH_VISION_MODEL", "gemma3:4b")
|
|
29
29
|
NPCSH_VISION_PROVIDER = os.environ.get("NPCSH_VISION_PROVIDER", "ollama")
|
|
30
30
|
NPCSH_IMAGE_GEN_MODEL = os.environ.get(
|
|
31
|
-
"NPCSH_IMAGE_GEN_MODEL", "
|
|
31
|
+
"NPCSH_IMAGE_GEN_MODEL", "x/z-image-turbo"
|
|
32
32
|
)
|
|
33
|
-
NPCSH_IMAGE_GEN_PROVIDER = os.environ.get("NPCSH_IMAGE_GEN_PROVIDER", "
|
|
33
|
+
NPCSH_IMAGE_GEN_PROVIDER = os.environ.get("NPCSH_IMAGE_GEN_PROVIDER", "ollama")
|
|
34
34
|
NPCSH_VIDEO_GEN_MODEL = os.environ.get(
|
|
35
35
|
"NPCSH_VIDEO_GEN_MODEL", "damo-vilab/text-to-video-ms-1.7b"
|
|
36
36
|
)
|
|
@@ -44,6 +44,9 @@ NPCSH_API_URL = os.environ.get("NPCSH_API_URL", None)
|
|
|
44
44
|
NPCSH_SEARCH_PROVIDER = os.environ.get("NPCSH_SEARCH_PROVIDER", "duckduckgo")
|
|
45
45
|
NPCSH_BUILD_KG = os.environ.get("NPCSH_BUILD_KG", "1") != "0"
|
|
46
46
|
NPCSH_EDIT_APPROVAL = os.environ.get("NPCSH_EDIT_APPROVAL", "off") # off, interactive, auto
|
|
47
|
+
NPCSH_TTS_ENGINE = os.environ.get("NPCSH_TTS_ENGINE", "")
|
|
48
|
+
NPCSH_TTS_VOICE = os.environ.get("NPCSH_TTS_VOICE", "")
|
|
49
|
+
NPCSH_YAP_SETUP_DONE = os.environ.get("NPCSH_YAP_SETUP_DONE", "0") == "1"
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
def get_shell_config_file() -> str:
|
npcsh/npc_team/alicanto.npc
CHANGED
|
@@ -11,14 +11,10 @@ colors:
|
|
|
11
11
|
top: "255,215,0"
|
|
12
12
|
bottom: "218,165,32"
|
|
13
13
|
primary_directive: |
|
|
14
|
-
You are alicanto,
|
|
15
|
-
Search
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Use shell commands for data processing and system tasks.
|
|
19
|
-
Create files to record your findings, analyses, and evidence.
|
|
20
|
-
When exploring a hypothesis, gather evidence from multiple sources, analyze it quantitatively where possible, and document what you find.
|
|
21
|
-
Say RESEARCH_COMPLETE when you have sufficient evidence to evaluate your hypothesis.
|
|
14
|
+
You are alicanto, the deep research specialist. You produce thorough research reports.
|
|
15
|
+
Search multiple sources, read papers, gather evidence, then synthesize into a report.
|
|
16
|
+
Use web_search and file_search to find sources. Use python to analyze data.
|
|
17
|
+
Say RESEARCH_COMPLETE when you have enough evidence to answer.
|
|
22
18
|
jinxs:
|
|
23
19
|
- lib/core/search/web_search
|
|
24
20
|
- lib/core/search/file_search
|
npcsh/npc_team/corca.npc
CHANGED
|
@@ -11,17 +11,11 @@ colors:
|
|
|
11
11
|
top: "64,224,208"
|
|
12
12
|
bottom: "255,165,0"
|
|
13
13
|
primary_directive: |
|
|
14
|
-
You are corca, the software development
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
CRITICAL: You MUST ALWAYS use FULL ABSOLUTE PATHS for all file operations.
|
|
21
|
-
- NEVER use relative paths like "apps/api" or "./src"
|
|
22
|
-
- ALWAYS expand paths starting from root like "/Users/username/project/apps/api"
|
|
23
|
-
- When given a task, first determine the absolute path of the working directory using pwd
|
|
24
|
-
- Prefix all file paths with the full absolute path
|
|
14
|
+
You are corca, the software development and shell specialist.
|
|
15
|
+
You run commands, write code, debug, edit files, and handle system tasks.
|
|
16
|
+
Always use FULL ABSOLUTE PATHS. Run pwd first if you need the working directory.
|
|
17
|
+
When counting things, use wc -l. When debugging, read the error, fix it, retry.
|
|
18
|
+
Keep it simple. Do the task, report the result.
|
|
25
19
|
jinxs:
|
|
26
20
|
- lib/core/sh
|
|
27
21
|
- lib/core/python
|
npcsh/npc_team/frederic.npc
CHANGED
|
@@ -11,12 +11,10 @@ colors:
|
|
|
11
11
|
top: "224,255,255"
|
|
12
12
|
bottom: "173,216,230"
|
|
13
13
|
primary_directive: |
|
|
14
|
-
You are frederic the polar bear
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
You help users with hard math, physics problems, and music composition.
|
|
19
|
-
Cut through the ice to get to what matters. Make the complex feel simple and beautiful.
|
|
14
|
+
You are frederic the polar bear — Feynman's curiosity meets Chopin's soul.
|
|
15
|
+
Make the complex simple and beautiful. Find elegance in structure.
|
|
16
|
+
Use analogies, strip away jargon, show why things are beautiful not just correct.
|
|
17
|
+
Use python to demonstrate ideas visually when math is involved.
|
|
20
18
|
jinxs:
|
|
21
19
|
- lib/core/python
|
|
22
20
|
- lib/core/sql
|
npcsh/npc_team/guac.npc
CHANGED
|
@@ -11,10 +11,10 @@ ascii_art: |
|
|
|
11
11
|
🟢 🟢 🟢 🟢 ⚫🥑🍅⚫ 🟢
|
|
12
12
|
🟢🟢🟢🟢🟢🟢 🟢🟢🟢🟢 ⚫⚫🟢 🟢🟢🟢
|
|
13
13
|
primary_directive: |
|
|
14
|
-
You are guac, the data analysis specialist
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
You are guac, the data analysis specialist.
|
|
15
|
+
You load, analyze, and visualize data with pandas, numpy, matplotlib, and SQL.
|
|
16
|
+
Load data first, inspect it, do the analysis, report results with numbers.
|
|
17
|
+
For plots, save to file with plt.savefig() and report the path.
|
|
18
18
|
jinxs:
|
|
19
19
|
- lib/core/python
|
|
20
20
|
- lib/core/sql
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
jinx_name: delegate
|
|
2
|
-
description:
|
|
2
|
+
description: ONLY for complex multi-step tasks. Sends a task to a specialist NPC who works on it with feedback until done. Do NOT use this for simple commands — use sh or python instead and answer directly.
|
|
3
3
|
inputs:
|
|
4
4
|
- npc_name:
|
|
5
5
|
description: "Name of the NPC to delegate to"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
jinx_name: sh
|
|
2
|
-
description:
|
|
2
|
+
description: Run a shell command and get the output. Use for counting files (ls /etc | wc -l), checking paths, listing directories. After you get the result, stop and answer the user. Do not call any other tools after this.
|
|
3
3
|
inputs:
|
|
4
4
|
- bash_command
|
|
5
5
|
steps:
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
jinx_name: skill
|
|
2
|
+
description: Constructs and serves a structured skill. Receives the full skill definition and returns the requested part. Do not call directly.
|
|
3
|
+
inputs:
|
|
4
|
+
- skill_name
|
|
5
|
+
- skill_description
|
|
6
|
+
- sections
|
|
7
|
+
- scripts_json
|
|
8
|
+
- references_json
|
|
9
|
+
- assets_json
|
|
10
|
+
- section
|
|
11
|
+
steps:
|
|
12
|
+
- name: handle_skill
|
|
13
|
+
engine: python
|
|
14
|
+
code: |
|
|
15
|
+
import json as _json, base64 as _b64
|
|
16
|
+
|
|
17
|
+
_name = {{ skill_name | tojson }}
|
|
18
|
+
_desc = {{ skill_description | tojson }}
|
|
19
|
+
_sections_raw = {{ sections | tojson }}
|
|
20
|
+
if isinstance(_sections_raw, dict):
|
|
21
|
+
_sections = _sections_raw
|
|
22
|
+
elif isinstance(_sections_raw, str):
|
|
23
|
+
try:
|
|
24
|
+
_sections = _json.loads(_b64.b64decode(_sections_raw).decode('utf-8'))
|
|
25
|
+
except Exception:
|
|
26
|
+
_sections = _json.loads(_sections_raw)
|
|
27
|
+
else:
|
|
28
|
+
_sections = {}
|
|
29
|
+
_scripts = _json.loads({{ scripts_json | tojson }}) if {{ scripts_json | tojson }} else []
|
|
30
|
+
_references = _json.loads({{ references_json | tojson }}) if {{ references_json | tojson }} else []
|
|
31
|
+
_assets = _json.loads({{ assets_json | tojson }}) if {{ assets_json | tojson }} else []
|
|
32
|
+
_section = {{ section | tojson }} or 'all'
|
|
33
|
+
_section = _section.strip()
|
|
34
|
+
|
|
35
|
+
if _section == 'meta':
|
|
36
|
+
output = _json.dumps({
|
|
37
|
+
'name': _name,
|
|
38
|
+
'description': _desc,
|
|
39
|
+
'sections': list(_sections.keys()),
|
|
40
|
+
'scripts': _scripts,
|
|
41
|
+
'references': _references,
|
|
42
|
+
'assets': _assets,
|
|
43
|
+
}, indent=2)
|
|
44
|
+
elif _section == 'list':
|
|
45
|
+
output = 'Available sections: ' + ', '.join(_sections.keys())
|
|
46
|
+
elif _section == 'all':
|
|
47
|
+
_parts = []
|
|
48
|
+
for _k, _v in _sections.items():
|
|
49
|
+
_parts.append(f'## {_k}\n{_v.strip()}')
|
|
50
|
+
output = '\n\n'.join(_parts)
|
|
51
|
+
else:
|
|
52
|
+
if _section in _sections:
|
|
53
|
+
output = _sections[_section].strip()
|
|
54
|
+
else:
|
|
55
|
+
_matches = [_k for _k in _sections if _section.lower() in _k.lower()]
|
|
56
|
+
if _matches:
|
|
57
|
+
output = _sections[_matches[0]].strip()
|
|
58
|
+
else:
|
|
59
|
+
output = f"Section '{_section}' not found. Available sections: " + ', '.join(_sections.keys())
|