npcsh 1.1.17__py3-none-any.whl → 1.1.19__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 +122 -91
- npcsh/alicanto.py +2 -2
- npcsh/benchmark/__init__.py +8 -2
- npcsh/benchmark/npcsh_agent.py +87 -22
- npcsh/benchmark/runner.py +85 -43
- npcsh/benchmark/templates/install-npcsh.sh.j2 +35 -0
- npcsh/build.py +2 -4
- npcsh/completion.py +2 -6
- npcsh/config.py +2 -3
- npcsh/conversation_viewer.py +389 -0
- npcsh/corca.py +0 -1
- npcsh/diff_viewer.py +452 -0
- npcsh/execution.py +0 -1
- npcsh/guac.py +0 -1
- npcsh/mcp_helpers.py +2 -3
- npcsh/mcp_server.py +5 -10
- npcsh/npc.py +10 -11
- npcsh/npc_team/jinxs/bin/benchmark.jinx +1 -1
- npcsh/npc_team/jinxs/bin/config_tui.jinx +299 -0
- npcsh/npc_team/jinxs/bin/memories.jinx +316 -0
- npcsh/npc_team/jinxs/bin/setup.jinx +240 -0
- npcsh/npc_team/jinxs/bin/sync.jinx +143 -150
- npcsh/npc_team/jinxs/bin/team_tui.jinx +327 -0
- npcsh/npc_team/jinxs/incognide/add_tab.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/close_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/close_tab.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/confirm.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/focus_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/list_panes.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/navigate.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/notify.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/open_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/read_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/run_terminal.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/send_message.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/split_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/switch_npc.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/switch_tab.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/write_file.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/zen_mode.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +321 -17
- npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +312 -67
- npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +366 -44
- npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +73 -0
- npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +328 -20
- npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +242 -10
- npcsh/npc_team/jinxs/lib/core/sleep.jinx +22 -11
- npcsh/npc_team/jinxs/lib/core/sql.jinx +10 -6
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +387 -76
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +372 -55
- npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +299 -144
- npcsh/npc_team/jinxs/modes/alicanto.jinx +356 -0
- npcsh/npc_team/jinxs/modes/arxiv.jinx +720 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +430 -0
- npcsh/npc_team/jinxs/modes/guac.jinx +542 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +379 -0
- npcsh/npc_team/jinxs/modes/pti.jinx +357 -0
- npcsh/npc_team/jinxs/modes/reattach.jinx +291 -0
- npcsh/npc_team/jinxs/modes/spool.jinx +350 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +455 -0
- npcsh/npc_team/jinxs/{bin → modes}/yap.jinx +13 -7
- npcsh/npcsh.py +7 -4
- npcsh/plonk.py +0 -1
- npcsh/pti.py +0 -1
- npcsh/routes.py +1 -3
- npcsh/spool.py +0 -1
- npcsh/ui.py +0 -1
- npcsh/wander.py +0 -1
- npcsh/yap.py +0 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/add_tab.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/alicanto.jinx +356 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/arxiv.jinx +720 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/benchmark.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_pane.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_tab.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/config_tui.jinx +299 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/confirm.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/corca.jinx +430 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/db_search.jinx +348 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/file_search.jinx +339 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/focus_pane.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/guac.jinx +542 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/jinxs.jinx +331 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/kg_search.jinx +418 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/list_panes.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/mem_review.jinx +73 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/mem_search.jinx +388 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/memories.jinx +316 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/navigate.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/notify.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_pane.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/paper_search.jinx +412 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/plonk.jinx +379 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/pti.jinx +357 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/read_pane.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/reattach.jinx +291 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/run_terminal.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/semantic_scholar.jinx +386 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/send_message.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/setup.jinx +240 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sleep.jinx +22 -11
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/split_pane.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/spool.jinx +350 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/sql.jinx +20 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_npc.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_tab.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/sync.jinx +223 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/team_tui.jinx +327 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/wander.jinx +455 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/web_search.jinx +283 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/write_file.jinx +1 -1
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.jinx +13 -7
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/zen_mode.jinx +1 -1
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/METADATA +110 -14
- npcsh-1.1.19.dist-info/RECORD +244 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/WHEEL +1 -1
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/entry_points.txt +4 -3
- npcsh/npc_team/jinxs/bin/spool.jinx +0 -161
- npcsh/npc_team/jinxs/bin/wander.jinx +0 -242
- npcsh/npc_team/jinxs/lib/research/arxiv.jinx +0 -76
- npcsh-1.1.17.data/data/npcsh/npc_team/arxiv.jinx +0 -76
- npcsh-1.1.17.data/data/npcsh/npc_team/db_search.jinx +0 -44
- npcsh-1.1.17.data/data/npcsh/npc_team/file_search.jinx +0 -94
- npcsh-1.1.17.data/data/npcsh/npc_team/jinxs.jinx +0 -176
- npcsh-1.1.17.data/data/npcsh/npc_team/kg_search.jinx +0 -96
- npcsh-1.1.17.data/data/npcsh/npc_team/mem_search.jinx +0 -80
- npcsh-1.1.17.data/data/npcsh/npc_team/paper_search.jinx +0 -101
- npcsh-1.1.17.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -69
- npcsh-1.1.17.data/data/npcsh/npc_team/spool.jinx +0 -161
- npcsh-1.1.17.data/data/npcsh/npc_team/sql.jinx +0 -16
- npcsh-1.1.17.data/data/npcsh/npc_team/sync.jinx +0 -230
- npcsh-1.1.17.data/data/npcsh/npc_team/wander.jinx +0 -242
- npcsh-1.1.17.data/data/npcsh/npc_team/web_search.jinx +0 -51
- npcsh-1.1.17.dist-info/RECORD +0 -219
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/click.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/convene.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/delegate.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/incognide.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/key_press.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/nql.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/paste.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/search.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sh.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/shh.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switches.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/type_text.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/verbose.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/wait.jinx +0 -0
- {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/top_level.txt +0 -0
npcsh/diff_viewer.py
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Git-diff approval TUI for npcsh.
|
|
3
|
+
Provides interactive diff viewing with approve/reject functionality.
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import difflib
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import List, Dict, Optional, Tuple
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
# Platform-specific imports
|
|
13
|
+
try:
|
|
14
|
+
import tty
|
|
15
|
+
import termios
|
|
16
|
+
import select
|
|
17
|
+
HAS_TTY = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
HAS_TTY = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HunkDecision(Enum):
|
|
23
|
+
PENDING = "pending"
|
|
24
|
+
APPROVED = "approved"
|
|
25
|
+
REJECTED = "rejected"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class DiffHunk:
|
|
30
|
+
"""Represents a single diff hunk"""
|
|
31
|
+
start_original: int
|
|
32
|
+
count_original: int
|
|
33
|
+
start_modified: int
|
|
34
|
+
count_modified: int
|
|
35
|
+
lines: List[str]
|
|
36
|
+
header: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class DiffViewerState:
|
|
41
|
+
"""State for the diff viewer TUI"""
|
|
42
|
+
file_path: str
|
|
43
|
+
original: str
|
|
44
|
+
modified: str
|
|
45
|
+
hunks: List[DiffHunk] = field(default_factory=list)
|
|
46
|
+
decisions: Dict[int, HunkDecision] = field(default_factory=dict)
|
|
47
|
+
selected_hunk: int = 0
|
|
48
|
+
scroll_offset: int = 0
|
|
49
|
+
mode: str = "normal" # normal, help
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def compute_diff_hunks(original: str, modified: str) -> List[DiffHunk]:
|
|
53
|
+
"""Compute diff hunks between original and modified content."""
|
|
54
|
+
original_lines = original.splitlines(keepends=True)
|
|
55
|
+
modified_lines = modified.splitlines(keepends=True)
|
|
56
|
+
|
|
57
|
+
diff = list(difflib.unified_diff(
|
|
58
|
+
original_lines,
|
|
59
|
+
modified_lines,
|
|
60
|
+
lineterm=''
|
|
61
|
+
))
|
|
62
|
+
|
|
63
|
+
hunks = []
|
|
64
|
+
current_hunk_lines = []
|
|
65
|
+
current_header = ""
|
|
66
|
+
start_orig = 0
|
|
67
|
+
count_orig = 0
|
|
68
|
+
start_mod = 0
|
|
69
|
+
count_mod = 0
|
|
70
|
+
|
|
71
|
+
for line in diff[2:]: # Skip the --- and +++ headers
|
|
72
|
+
if line.startswith('@@'):
|
|
73
|
+
# Save previous hunk if exists
|
|
74
|
+
if current_hunk_lines:
|
|
75
|
+
hunks.append(DiffHunk(
|
|
76
|
+
start_original=start_orig,
|
|
77
|
+
count_original=count_orig,
|
|
78
|
+
start_modified=start_mod,
|
|
79
|
+
count_modified=count_mod,
|
|
80
|
+
lines=current_hunk_lines,
|
|
81
|
+
header=current_header
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
# Parse new hunk header
|
|
85
|
+
current_header = line.strip()
|
|
86
|
+
current_hunk_lines = []
|
|
87
|
+
|
|
88
|
+
# Parse @@ -start,count +start,count @@
|
|
89
|
+
try:
|
|
90
|
+
parts = line.split('@@')[1].strip().split()
|
|
91
|
+
orig_part = parts[0] # -start,count
|
|
92
|
+
mod_part = parts[1] # +start,count
|
|
93
|
+
|
|
94
|
+
if ',' in orig_part:
|
|
95
|
+
start_orig, count_orig = map(int, orig_part[1:].split(','))
|
|
96
|
+
else:
|
|
97
|
+
start_orig = int(orig_part[1:])
|
|
98
|
+
count_orig = 1
|
|
99
|
+
|
|
100
|
+
if ',' in mod_part:
|
|
101
|
+
start_mod, count_mod = map(int, mod_part[1:].split(','))
|
|
102
|
+
else:
|
|
103
|
+
start_mod = int(mod_part[1:])
|
|
104
|
+
count_mod = 1
|
|
105
|
+
except (IndexError, ValueError):
|
|
106
|
+
start_orig = count_orig = start_mod = count_mod = 0
|
|
107
|
+
else:
|
|
108
|
+
current_hunk_lines.append(line)
|
|
109
|
+
|
|
110
|
+
# Save last hunk
|
|
111
|
+
if current_hunk_lines:
|
|
112
|
+
hunks.append(DiffHunk(
|
|
113
|
+
start_original=start_orig,
|
|
114
|
+
count_original=count_orig,
|
|
115
|
+
start_modified=start_mod,
|
|
116
|
+
count_modified=count_mod,
|
|
117
|
+
lines=current_hunk_lines,
|
|
118
|
+
header=current_header
|
|
119
|
+
))
|
|
120
|
+
|
|
121
|
+
return hunks
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_terminal_size() -> Tuple[int, int]:
|
|
125
|
+
"""Get terminal size (width, height)."""
|
|
126
|
+
try:
|
|
127
|
+
size = os.get_terminal_size()
|
|
128
|
+
return size.columns, size.lines
|
|
129
|
+
except:
|
|
130
|
+
return 80, 24
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class DiffViewer:
|
|
134
|
+
"""Interactive diff viewer with approve/reject functionality."""
|
|
135
|
+
|
|
136
|
+
def __init__(self, file_path: str, original: str, modified: str):
|
|
137
|
+
self.state = DiffViewerState(
|
|
138
|
+
file_path=file_path,
|
|
139
|
+
original=original,
|
|
140
|
+
modified=modified
|
|
141
|
+
)
|
|
142
|
+
self.state.hunks = compute_diff_hunks(original, modified)
|
|
143
|
+
|
|
144
|
+
# Initialize all hunks as pending
|
|
145
|
+
for i in range(len(self.state.hunks)):
|
|
146
|
+
self.state.decisions[i] = HunkDecision.PENDING
|
|
147
|
+
|
|
148
|
+
def render_screen(self):
|
|
149
|
+
"""Render the diff viewer screen."""
|
|
150
|
+
width, height = get_terminal_size()
|
|
151
|
+
out = []
|
|
152
|
+
|
|
153
|
+
# Clear screen and move to top
|
|
154
|
+
out.append("\033[2J\033[H")
|
|
155
|
+
|
|
156
|
+
# Header
|
|
157
|
+
header = f" File Edit: {self.state.file_path} "
|
|
158
|
+
if len(header) > width - 4:
|
|
159
|
+
header = f" ...{self.state.file_path[-width+15:]} "
|
|
160
|
+
out.append(f"\033[1;1H\033[44;37;1m{'=' * width}\033[0m")
|
|
161
|
+
out.append(f"\033[1;2H\033[44;37;1m{header}\033[0m")
|
|
162
|
+
|
|
163
|
+
# Stats line
|
|
164
|
+
approved = sum(1 for d in self.state.decisions.values() if d == HunkDecision.APPROVED)
|
|
165
|
+
rejected = sum(1 for d in self.state.decisions.values() if d == HunkDecision.REJECTED)
|
|
166
|
+
pending = sum(1 for d in self.state.decisions.values() if d == HunkDecision.PENDING)
|
|
167
|
+
stats = f"Hunks: {len(self.state.hunks)} | Approved: {approved} | Rejected: {rejected} | Pending: {pending}"
|
|
168
|
+
out.append(f"\033[2;1H\033[90m{stats.center(width)}\033[0m")
|
|
169
|
+
|
|
170
|
+
# Diff content area
|
|
171
|
+
content_start = 4
|
|
172
|
+
content_height = height - 6
|
|
173
|
+
|
|
174
|
+
if not self.state.hunks:
|
|
175
|
+
out.append(f"\033[{content_start};2H\033[33mNo differences found.\033[0m")
|
|
176
|
+
else:
|
|
177
|
+
# Render current hunk
|
|
178
|
+
hunk = self.state.hunks[self.state.selected_hunk]
|
|
179
|
+
decision = self.state.decisions[self.state.selected_hunk]
|
|
180
|
+
|
|
181
|
+
# Hunk header with decision indicator
|
|
182
|
+
decision_indicator = {
|
|
183
|
+
HunkDecision.PENDING: "\033[33m[?]\033[0m",
|
|
184
|
+
HunkDecision.APPROVED: "\033[32m[+]\033[0m",
|
|
185
|
+
HunkDecision.REJECTED: "\033[31m[-]\033[0m"
|
|
186
|
+
}[decision]
|
|
187
|
+
|
|
188
|
+
hunk_header = f"{decision_indicator} Hunk {self.state.selected_hunk + 1}/{len(self.state.hunks)}: {hunk.header}"
|
|
189
|
+
out.append(f"\033[3;1H\033[90m{'-' * width}\033[0m")
|
|
190
|
+
out.append(f"\033[3;2H{hunk_header[:width-4]}")
|
|
191
|
+
|
|
192
|
+
# Render diff lines
|
|
193
|
+
visible_lines = hunk.lines[self.state.scroll_offset:self.state.scroll_offset + content_height]
|
|
194
|
+
|
|
195
|
+
for i, line in enumerate(visible_lines):
|
|
196
|
+
row = content_start + i
|
|
197
|
+
if row >= height - 2:
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
# Color based on line type
|
|
201
|
+
if line.startswith('+'):
|
|
202
|
+
color = "\033[32m" # Green for additions
|
|
203
|
+
elif line.startswith('-'):
|
|
204
|
+
color = "\033[31m" # Red for deletions
|
|
205
|
+
else:
|
|
206
|
+
color = "\033[0m" # Default for context
|
|
207
|
+
|
|
208
|
+
# Truncate long lines
|
|
209
|
+
display_line = line.rstrip()[:width-2]
|
|
210
|
+
out.append(f"\033[{row};1H{color}{display_line}\033[0m")
|
|
211
|
+
|
|
212
|
+
# Scroll indicator
|
|
213
|
+
if len(hunk.lines) > content_height:
|
|
214
|
+
scroll_pct = (self.state.scroll_offset / (len(hunk.lines) - content_height)) * 100
|
|
215
|
+
scroll_info = f"[{int(scroll_pct)}%]"
|
|
216
|
+
out.append(f"\033[{content_start};{width-len(scroll_info)-1}H\033[90m{scroll_info}\033[0m")
|
|
217
|
+
|
|
218
|
+
# Footer with keybindings
|
|
219
|
+
footer_y = height - 1
|
|
220
|
+
out.append(f"\033[{footer_y};1H\033[90m{'-' * width}\033[0m")
|
|
221
|
+
|
|
222
|
+
keys = "[a] Approve [r] Reject [A] Approve All [R] Reject All [j/k] Hunks [q] Done [?] Help"
|
|
223
|
+
out.append(f"\033[{height};1H\033[90m{keys[:width]}\033[0m")
|
|
224
|
+
|
|
225
|
+
sys.stdout.write(''.join(out))
|
|
226
|
+
sys.stdout.flush()
|
|
227
|
+
|
|
228
|
+
def handle_input(self, c: str) -> bool:
|
|
229
|
+
"""Handle input character. Returns False to exit."""
|
|
230
|
+
if c == 'q':
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
elif c == 'a': # Approve current hunk
|
|
234
|
+
self.state.decisions[self.state.selected_hunk] = HunkDecision.APPROVED
|
|
235
|
+
if self.state.selected_hunk < len(self.state.hunks) - 1:
|
|
236
|
+
self.state.selected_hunk += 1
|
|
237
|
+
self.state.scroll_offset = 0
|
|
238
|
+
|
|
239
|
+
elif c == 'r': # Reject current hunk
|
|
240
|
+
self.state.decisions[self.state.selected_hunk] = HunkDecision.REJECTED
|
|
241
|
+
if self.state.selected_hunk < len(self.state.hunks) - 1:
|
|
242
|
+
self.state.selected_hunk += 1
|
|
243
|
+
self.state.scroll_offset = 0
|
|
244
|
+
|
|
245
|
+
elif c == 'A': # Approve all
|
|
246
|
+
for i in range(len(self.state.hunks)):
|
|
247
|
+
self.state.decisions[i] = HunkDecision.APPROVED
|
|
248
|
+
|
|
249
|
+
elif c == 'R': # Reject all
|
|
250
|
+
for i in range(len(self.state.hunks)):
|
|
251
|
+
self.state.decisions[i] = HunkDecision.REJECTED
|
|
252
|
+
|
|
253
|
+
elif c == 'j' or c == '\x1b': # Down/next hunk (or escape sequence)
|
|
254
|
+
if c == '\x1b':
|
|
255
|
+
# Handle escape sequences
|
|
256
|
+
if HAS_TTY and select.select([sys.stdin], [], [], 0.05)[0]:
|
|
257
|
+
c2 = sys.stdin.read(1)
|
|
258
|
+
if c2 == '[':
|
|
259
|
+
c3 = sys.stdin.read(1)
|
|
260
|
+
if c3 == 'B': # Down arrow
|
|
261
|
+
if self.state.selected_hunk < len(self.state.hunks) - 1:
|
|
262
|
+
self.state.selected_hunk += 1
|
|
263
|
+
self.state.scroll_offset = 0
|
|
264
|
+
elif c3 == 'A': # Up arrow
|
|
265
|
+
if self.state.selected_hunk > 0:
|
|
266
|
+
self.state.selected_hunk -= 1
|
|
267
|
+
self.state.scroll_offset = 0
|
|
268
|
+
else:
|
|
269
|
+
if self.state.selected_hunk < len(self.state.hunks) - 1:
|
|
270
|
+
self.state.selected_hunk += 1
|
|
271
|
+
self.state.scroll_offset = 0
|
|
272
|
+
|
|
273
|
+
elif c == 'k': # Up/previous hunk
|
|
274
|
+
if self.state.selected_hunk > 0:
|
|
275
|
+
self.state.selected_hunk -= 1
|
|
276
|
+
self.state.scroll_offset = 0
|
|
277
|
+
|
|
278
|
+
elif c == 'n': # Next hunk (same as j)
|
|
279
|
+
if self.state.selected_hunk < len(self.state.hunks) - 1:
|
|
280
|
+
self.state.selected_hunk += 1
|
|
281
|
+
self.state.scroll_offset = 0
|
|
282
|
+
|
|
283
|
+
elif c == 'p': # Previous hunk (same as k)
|
|
284
|
+
if self.state.selected_hunk > 0:
|
|
285
|
+
self.state.selected_hunk -= 1
|
|
286
|
+
self.state.scroll_offset = 0
|
|
287
|
+
|
|
288
|
+
elif c == ' ': # Scroll down within hunk
|
|
289
|
+
if self.state.hunks:
|
|
290
|
+
hunk = self.state.hunks[self.state.selected_hunk]
|
|
291
|
+
_, height = get_terminal_size()
|
|
292
|
+
content_height = height - 6
|
|
293
|
+
max_scroll = max(0, len(hunk.lines) - content_height)
|
|
294
|
+
self.state.scroll_offset = min(self.state.scroll_offset + 5, max_scroll)
|
|
295
|
+
|
|
296
|
+
elif c == 'b': # Scroll up within hunk
|
|
297
|
+
self.state.scroll_offset = max(0, self.state.scroll_offset - 5)
|
|
298
|
+
|
|
299
|
+
return True
|
|
300
|
+
|
|
301
|
+
def apply_decisions(self) -> str:
|
|
302
|
+
"""Apply decisions and return the resulting content."""
|
|
303
|
+
if not self.state.hunks:
|
|
304
|
+
return self.state.modified
|
|
305
|
+
|
|
306
|
+
# If all hunks approved, return modified
|
|
307
|
+
if all(d == HunkDecision.APPROVED for d in self.state.decisions.values()):
|
|
308
|
+
return self.state.modified
|
|
309
|
+
|
|
310
|
+
# If all hunks rejected, return original
|
|
311
|
+
if all(d == HunkDecision.REJECTED for d in self.state.decisions.values()):
|
|
312
|
+
return self.state.original
|
|
313
|
+
|
|
314
|
+
# Partial application - reconstruct from decisions
|
|
315
|
+
# This is complex - for now, we'll use a simple approach:
|
|
316
|
+
# If any hunk is rejected, we need to carefully reconstruct
|
|
317
|
+
|
|
318
|
+
result_lines = self.state.original.splitlines(keepends=True)
|
|
319
|
+
offset = 0 # Track line number offset from applied changes
|
|
320
|
+
|
|
321
|
+
for i, hunk in enumerate(self.state.hunks):
|
|
322
|
+
if self.state.decisions[i] == HunkDecision.APPROVED:
|
|
323
|
+
# Apply this hunk
|
|
324
|
+
start = hunk.start_original - 1 + offset
|
|
325
|
+
|
|
326
|
+
# Count removals and additions in this hunk
|
|
327
|
+
removals = [l[1:] for l in hunk.lines if l.startswith('-')]
|
|
328
|
+
additions = [l[1:] for l in hunk.lines if l.startswith('+')]
|
|
329
|
+
|
|
330
|
+
# Remove old lines
|
|
331
|
+
del result_lines[start:start + len(removals)]
|
|
332
|
+
|
|
333
|
+
# Insert new lines
|
|
334
|
+
for j, line in enumerate(additions):
|
|
335
|
+
if not line.endswith('\n'):
|
|
336
|
+
line += '\n'
|
|
337
|
+
result_lines.insert(start + j, line)
|
|
338
|
+
|
|
339
|
+
# Update offset
|
|
340
|
+
offset += len(additions) - len(removals)
|
|
341
|
+
|
|
342
|
+
return ''.join(result_lines)
|
|
343
|
+
|
|
344
|
+
def run(self) -> Dict[str, any]:
|
|
345
|
+
"""Run the interactive diff viewer. Returns approval decisions."""
|
|
346
|
+
if not HAS_TTY:
|
|
347
|
+
print("TTY not available - cannot run interactive diff viewer")
|
|
348
|
+
return {
|
|
349
|
+
"approved": False,
|
|
350
|
+
"decisions": {},
|
|
351
|
+
"content": self.state.original
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if not self.state.hunks:
|
|
355
|
+
return {
|
|
356
|
+
"approved": True,
|
|
357
|
+
"decisions": {},
|
|
358
|
+
"content": self.state.modified
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
fd = sys.stdin.fileno()
|
|
362
|
+
old_settings = termios.tcgetattr(fd)
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
tty.setcbreak(fd)
|
|
366
|
+
sys.stdout.write('\033[?25l') # Hide cursor
|
|
367
|
+
|
|
368
|
+
self.render_screen()
|
|
369
|
+
|
|
370
|
+
while True:
|
|
371
|
+
c = sys.stdin.read(1)
|
|
372
|
+
if not self.handle_input(c):
|
|
373
|
+
break
|
|
374
|
+
self.render_screen()
|
|
375
|
+
|
|
376
|
+
finally:
|
|
377
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
378
|
+
sys.stdout.write('\033[?25h') # Show cursor
|
|
379
|
+
sys.stdout.write('\033[2J\033[H') # Clear screen
|
|
380
|
+
sys.stdout.flush()
|
|
381
|
+
|
|
382
|
+
# Determine if approved
|
|
383
|
+
all_approved = all(d == HunkDecision.APPROVED for d in self.state.decisions.values())
|
|
384
|
+
any_approved = any(d == HunkDecision.APPROVED for d in self.state.decisions.values())
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
"approved": any_approved,
|
|
388
|
+
"all_approved": all_approved,
|
|
389
|
+
"decisions": {i: d.value for i, d in self.state.decisions.items()},
|
|
390
|
+
"content": self.apply_decisions()
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def show_diff_approval(file_path: str, original: str, modified: str) -> Dict[str, any]:
|
|
395
|
+
"""
|
|
396
|
+
Show an interactive diff approval dialog.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
file_path: Path to the file being edited
|
|
400
|
+
original: Original file content
|
|
401
|
+
modified: Modified file content
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Dict with:
|
|
405
|
+
- approved: bool - whether any changes were approved
|
|
406
|
+
- all_approved: bool - whether all changes were approved
|
|
407
|
+
- content: str - the resulting content after applying decisions
|
|
408
|
+
- decisions: dict - per-hunk decisions
|
|
409
|
+
"""
|
|
410
|
+
viewer = DiffViewer(file_path, original, modified)
|
|
411
|
+
return viewer.run()
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def quick_diff_preview(original: str, modified: str, max_lines: int = 20) -> str:
|
|
415
|
+
"""
|
|
416
|
+
Generate a quick text-based diff preview (non-interactive).
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
original: Original content
|
|
420
|
+
modified: Modified content
|
|
421
|
+
max_lines: Maximum lines to show
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
Colored diff string
|
|
425
|
+
"""
|
|
426
|
+
original_lines = original.splitlines(keepends=True)
|
|
427
|
+
modified_lines = modified.splitlines(keepends=True)
|
|
428
|
+
|
|
429
|
+
diff = list(difflib.unified_diff(
|
|
430
|
+
original_lines,
|
|
431
|
+
modified_lines,
|
|
432
|
+
lineterm=''
|
|
433
|
+
))
|
|
434
|
+
|
|
435
|
+
if not diff:
|
|
436
|
+
return "No changes"
|
|
437
|
+
|
|
438
|
+
result = []
|
|
439
|
+
for line in diff[:max_lines]:
|
|
440
|
+
if line.startswith('+') and not line.startswith('+++'):
|
|
441
|
+
result.append(f"\033[32m{line.rstrip()}\033[0m")
|
|
442
|
+
elif line.startswith('-') and not line.startswith('---'):
|
|
443
|
+
result.append(f"\033[31m{line.rstrip()}\033[0m")
|
|
444
|
+
elif line.startswith('@@'):
|
|
445
|
+
result.append(f"\033[36m{line.rstrip()}\033[0m")
|
|
446
|
+
else:
|
|
447
|
+
result.append(line.rstrip())
|
|
448
|
+
|
|
449
|
+
if len(diff) > max_lines:
|
|
450
|
+
result.append(f"\033[90m... ({len(diff) - max_lines} more lines)\033[0m")
|
|
451
|
+
|
|
452
|
+
return '\n'.join(result)
|
npcsh/execution.py
CHANGED
npcsh/guac.py
CHANGED
npcsh/mcp_helpers.py
CHANGED
|
@@ -4,7 +4,6 @@ Raw MCP client with no exception handling and full visibility.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
|
-
import os
|
|
8
7
|
import sys
|
|
9
8
|
import json
|
|
10
9
|
try:
|
|
@@ -245,7 +244,7 @@ class MCPClient:
|
|
|
245
244
|
|
|
246
245
|
|
|
247
246
|
self._log(f"Executing tool: {tool_name} with args: {tool_args}")
|
|
248
|
-
print(
|
|
247
|
+
print("\nExecuting tool call:")
|
|
249
248
|
print(f" Jinx name: {tool_name}")
|
|
250
249
|
print(f" Jinx args: {tool_args}")
|
|
251
250
|
print(f" Jinx args type: {type(tool_args)}")
|
|
@@ -268,7 +267,7 @@ class MCPClient:
|
|
|
268
267
|
print(f" TextContent detected, text: {tool_result.text}")
|
|
269
268
|
tool_result = tool_result.text
|
|
270
269
|
elif isinstance(tool_result, list) and all(hasattr(item, 'text') for item in tool_result):
|
|
271
|
-
print(
|
|
270
|
+
print(" List of TextContent detected")
|
|
272
271
|
tool_result = [item.text for item in tool_result]
|
|
273
272
|
|
|
274
273
|
|
npcsh/mcp_server.py
CHANGED
|
@@ -7,9 +7,8 @@ npcpy.llm_funcs, and npcpy.npc_compiler as tools.
|
|
|
7
7
|
import os
|
|
8
8
|
import subprocess
|
|
9
9
|
import json
|
|
10
|
-
import asyncio
|
|
11
10
|
|
|
12
|
-
from typing import
|
|
11
|
+
from typing import List, Callable
|
|
13
12
|
|
|
14
13
|
from mcp.server.fastmcp import FastMCP
|
|
15
14
|
import importlib
|
|
@@ -20,19 +19,15 @@ from sqlalchemy import text
|
|
|
20
19
|
import os
|
|
21
20
|
import subprocess
|
|
22
21
|
import json
|
|
23
|
-
import asyncio
|
|
24
22
|
try:
|
|
25
23
|
import inspect
|
|
26
24
|
except:
|
|
27
25
|
pass
|
|
28
|
-
from typing import
|
|
26
|
+
from typing import List, Callable
|
|
29
27
|
|
|
30
28
|
from functools import wraps
|
|
31
|
-
import sys
|
|
32
29
|
|
|
33
|
-
from npcpy.llm_funcs import
|
|
34
|
-
zoom_in, execute_llm_command, gen_image
|
|
35
|
-
from npcpy.memory.search import search_similar_texts, execute_search_command, execute_rag_command, answer_with_rag, execute_brainblast_command
|
|
30
|
+
from npcpy.llm_funcs import (gen_image)
|
|
36
31
|
from npcpy.data.load import load_file_contents
|
|
37
32
|
from npcpy.memory.command_history import CommandHistory
|
|
38
33
|
from npcpy.data.image import capture_screenshot
|
|
@@ -268,7 +263,7 @@ def register_selected_npcpy_tools():
|
|
|
268
263
|
gen_image,
|
|
269
264
|
load_file_contents,
|
|
270
265
|
capture_screenshot,
|
|
271
|
-
search_web
|
|
266
|
+
search_web ]
|
|
272
267
|
|
|
273
268
|
for func in tools:
|
|
274
269
|
|
|
@@ -293,7 +288,7 @@ register_selected_npcpy_tools()
|
|
|
293
288
|
|
|
294
289
|
|
|
295
290
|
if __name__ == "__main__":
|
|
296
|
-
print(
|
|
291
|
+
print("Starting enhanced NPCPY MCP server...")
|
|
297
292
|
print(f"Workspace: {DEFAULT_WORKSPACE}")
|
|
298
293
|
|
|
299
294
|
|
npcsh/npc.py
CHANGED
|
@@ -7,7 +7,6 @@ from typing import Optional
|
|
|
7
7
|
from npcsh._state import (
|
|
8
8
|
NPCSH_CHAT_MODEL,
|
|
9
9
|
NPCSH_CHAT_PROVIDER,
|
|
10
|
-
NPCSH_API_URL,
|
|
11
10
|
NPCSH_DB_PATH,
|
|
12
11
|
NPCSH_STREAM_OUTPUT,
|
|
13
12
|
initial_state,
|
|
@@ -16,9 +15,8 @@ from npcpy.npc_sysenv import (
|
|
|
16
15
|
print_and_process_stream_with_markdown,
|
|
17
16
|
render_markdown,
|
|
18
17
|
)
|
|
19
|
-
from npcpy.npc_compiler import NPC
|
|
18
|
+
from npcpy.npc_compiler import NPC
|
|
20
19
|
from npcsh.routes import router
|
|
21
|
-
from npcpy.llm_funcs import check_llm_command
|
|
22
20
|
from sqlalchemy import create_engine
|
|
23
21
|
|
|
24
22
|
from npcsh._state import (
|
|
@@ -256,8 +254,9 @@ def main():
|
|
|
256
254
|
print(
|
|
257
255
|
f"Processing prompt: '{prompt}' with NPC: '{args.npc}'..."
|
|
258
256
|
)
|
|
259
|
-
|
|
260
|
-
|
|
257
|
+
|
|
258
|
+
# Use NPCSH_DEFAULT_MODE environment variable, default to 'agent' for tool execution
|
|
259
|
+
shell_state.current_mode = os.environ.get('NPCSH_DEFAULT_MODE', 'agent')
|
|
261
260
|
updated_state, result = execute_command(
|
|
262
261
|
prompt,
|
|
263
262
|
shell_state,
|
|
@@ -274,12 +273,12 @@ def main():
|
|
|
274
273
|
)
|
|
275
274
|
|
|
276
275
|
if (
|
|
277
|
-
hasattr(output, '__iter__')
|
|
276
|
+
hasattr(output, '__iter__')
|
|
278
277
|
and not isinstance(output, (str, bytes, dict, list))
|
|
279
278
|
):
|
|
280
|
-
|
|
281
|
-
output,
|
|
282
|
-
model_for_stream,
|
|
279
|
+
print_and_process_stream_with_markdown(
|
|
280
|
+
output,
|
|
281
|
+
model_for_stream,
|
|
283
282
|
provider_for_stream,
|
|
284
283
|
show=True
|
|
285
284
|
)
|
|
@@ -289,7 +288,7 @@ def main():
|
|
|
289
288
|
hasattr(result, '__iter__')
|
|
290
289
|
and not isinstance(result, (str, bytes, dict, list))
|
|
291
290
|
):
|
|
292
|
-
|
|
291
|
+
print_and_process_stream_with_markdown(
|
|
293
292
|
result,
|
|
294
293
|
effective_model,
|
|
295
294
|
effective_provider,
|
|
@@ -324,7 +323,7 @@ def jinx_main():
|
|
|
324
323
|
if arg in ['-h', '--help']:
|
|
325
324
|
print(f"Usage: {jinx_name} [key=value ...]")
|
|
326
325
|
print(f"\nRun the '{jinx_name}' jinx with specified parameters.")
|
|
327
|
-
print(
|
|
326
|
+
print("\nExamples:")
|
|
328
327
|
print(f" {jinx_name} show=1")
|
|
329
328
|
print(f" {jinx_name} model=my_model db=~/mydb.db")
|
|
330
329
|
print(f"\nOr use: npc {jinx_name} [key=value ...]")
|
|
@@ -21,7 +21,7 @@ steps:
|
|
|
21
21
|
npc_name_input = {{ npc_name | default("") | tojson }}.strip() or None
|
|
22
22
|
|
|
23
23
|
if not model:
|
|
24
|
-
model = npc.model if npc and npc.model
|
|
24
|
+
model = npc.model if npc and npc.model else ""
|
|
25
25
|
if not provider:
|
|
26
26
|
provider = npc.provider if npc and npc.provider else "anthropic"
|
|
27
27
|
|