npcsh 1.1.19__py3-none-any.whl → 1.1.20__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.
Files changed (144) hide show
  1. npcsh/_state.py +11 -7
  2. npcsh/npc_team/jinxs/bin/config_tui.jinx +3 -2
  3. npcsh/npc_team/jinxs/bin/jinxs.jinx +407 -0
  4. npcsh/npc_team/jinxs/bin/kg.jinx +941 -0
  5. npcsh/npc_team/jinxs/bin/memories.jinx +3 -2
  6. npcsh/npc_team/jinxs/bin/models.jinx +343 -0
  7. npcsh/npc_team/jinxs/bin/nql.jinx +380 -50
  8. npcsh/npc_team/jinxs/bin/setup.jinx +2 -1
  9. npcsh/npc_team/jinxs/bin/team.jinx +504 -0
  10. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +1 -1
  11. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +1 -1
  12. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +1 -1
  13. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +1 -1
  14. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +1 -1
  15. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +1 -1
  16. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +1 -1
  17. npcsh/npc_team/jinxs/modes/alicanto.jinx +1 -1
  18. npcsh/npc_team/jinxs/modes/arxiv.jinx +1 -1
  19. npcsh/npc_team/jinxs/modes/corca.jinx +1 -1
  20. npcsh/npc_team/jinxs/modes/guac.jinx +4 -4
  21. npcsh/npc_team/jinxs/modes/plonk.jinx +1 -1
  22. npcsh/npc_team/jinxs/modes/pti.jinx +1 -1
  23. npcsh/npc_team/jinxs/modes/reattach.jinx +1 -1
  24. npcsh/npc_team/jinxs/modes/spool.jinx +1 -1
  25. npcsh/npc_team/jinxs/modes/wander.jinx +1 -1
  26. npcsh/routes.py +8 -2
  27. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/alicanto.jinx +1 -1
  28. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/arxiv.jinx +1 -1
  29. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/config_tui.jinx +3 -2
  30. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca.jinx +1 -1
  31. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/db_search.jinx +1 -1
  32. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/file_search.jinx +1 -1
  33. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/guac.jinx +4 -4
  34. npcsh-1.1.20.data/data/npcsh/npc_team/jinxs.jinx +407 -0
  35. npcsh-1.1.20.data/data/npcsh/npc_team/kg.jinx +941 -0
  36. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/kg_search.jinx +1 -1
  37. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/mem_search.jinx +1 -1
  38. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/memories.jinx +3 -2
  39. npcsh-1.1.20.data/data/npcsh/npc_team/models.jinx +343 -0
  40. npcsh-1.1.20.data/data/npcsh/npc_team/nql.jinx +471 -0
  41. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/paper_search.jinx +1 -1
  42. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonk.jinx +1 -1
  43. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/pti.jinx +1 -1
  44. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/reattach.jinx +1 -1
  45. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/semantic_scholar.jinx +1 -1
  46. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/setup.jinx +2 -1
  47. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/spool.jinx +1 -1
  48. npcsh-1.1.20.data/data/npcsh/npc_team/team.jinx +504 -0
  49. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/wander.jinx +1 -1
  50. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/web_search.jinx +1 -1
  51. {npcsh-1.1.19.dist-info → npcsh-1.1.20.dist-info}/METADATA +1 -1
  52. {npcsh-1.1.19.dist-info → npcsh-1.1.20.dist-info}/RECORD +139 -135
  53. {npcsh-1.1.19.dist-info → npcsh-1.1.20.dist-info}/entry_points.txt +4 -1
  54. npcsh/npc_team/jinxs/bin/team_tui.jinx +0 -327
  55. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +0 -331
  56. npcsh-1.1.19.data/data/npcsh/npc_team/jinxs.jinx +0 -331
  57. npcsh-1.1.19.data/data/npcsh/npc_team/nql.jinx +0 -141
  58. npcsh-1.1.19.data/data/npcsh/npc_team/team_tui.jinx +0 -327
  59. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  60. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  61. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/alicanto.png +0 -0
  62. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/benchmark.jinx +0 -0
  63. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  64. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  65. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/build.jinx +0 -0
  66. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/chat.jinx +0 -0
  67. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/click.jinx +0 -0
  68. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  69. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  70. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  71. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  72. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/compile.jinx +0 -0
  73. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/compress.jinx +0 -0
  74. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  75. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/convene.jinx +0 -0
  76. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca.npc +0 -0
  77. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca.png +0 -0
  78. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca_example.png +0 -0
  79. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  80. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  81. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  82. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/frederic.npc +0 -0
  83. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/frederic4.png +0 -0
  84. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/guac.npc +0 -0
  85. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/guac.png +0 -0
  86. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/help.jinx +0 -0
  87. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  88. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/init.jinx +0 -0
  89. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  90. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  91. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  92. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  93. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  94. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  95. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/mem_review.jinx +0 -0
  96. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  97. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/notify.jinx +0 -0
  98. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  99. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  100. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  101. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  102. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/ots.jinx +0 -0
  103. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/paste.jinx +0 -0
  104. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonk.npc +0 -0
  105. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonk.png +0 -0
  106. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  107. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  108. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/python.jinx +0 -0
  109. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  110. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/roll.jinx +0 -0
  111. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  112. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sample.jinx +0 -0
  113. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  114. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/search.jinx +0 -0
  115. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  116. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/serve.jinx +0 -0
  117. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/set.jinx +0 -0
  118. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sh.jinx +0 -0
  119. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/shh.jinx +0 -0
  120. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  121. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sibiji.png +0 -0
  122. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  123. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  124. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/spool.png +0 -0
  125. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sql.jinx +0 -0
  126. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switch.jinx +0 -0
  127. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  128. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  129. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switches.jinx +0 -0
  130. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sync.jinx +0 -0
  131. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  132. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  133. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  134. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/usage.jinx +0 -0
  135. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  136. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  137. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/wait.jinx +0 -0
  138. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  139. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/yap.jinx +0 -0
  140. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/yap.png +0 -0
  141. {npcsh-1.1.19.data → npcsh-1.1.20.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  142. {npcsh-1.1.19.dist-info → npcsh-1.1.20.dist-info}/WHEEL +0 -0
  143. {npcsh-1.1.19.dist-info → npcsh-1.1.20.dist-info}/licenses/LICENSE +0 -0
  144. {npcsh-1.1.19.dist-info → npcsh-1.1.20.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,504 @@
1
+ jinx_name: team
2
+ description: Interactive TUI for managing team context, NPCs, and jinxs
3
+ interactive: true
4
+ inputs: []
5
+ steps:
6
+ - name: team_manager
7
+ engine: python
8
+ code: |
9
+ import os
10
+ import sys
11
+ import tty
12
+ import termios
13
+ import select
14
+ import yaml
15
+ from pathlib import Path
16
+
17
+ if not sys.stdin.isatty():
18
+ context['output'] = "Team TUI requires an interactive terminal."
19
+
20
+ elif not state or not state.team:
21
+ context['output'] = "No team loaded."
22
+
23
+ else:
24
+ TEAM_DIR = Path(state.team.team_path)
25
+
26
+ # Find the actual .ctx file (first *.ctx found, same as Team class)
27
+ CTX_PATH = None
28
+ for _f in sorted(TEAM_DIR.glob('*.ctx')):
29
+ CTX_PATH = _f
30
+ break
31
+
32
+ class TUIState:
33
+ def __init__(self):
34
+ self.tab = 0
35
+ self.tabs = ['Context', 'NPCs', 'Jinxs']
36
+ self.sel = 0
37
+ self.scroll = 0
38
+ self.editing = False
39
+ self.edit_field = None
40
+ self.edit_buf = ""
41
+ self.edit_cursor = 0
42
+ self.status = ""
43
+ self.team_ctx = {}
44
+ self.npcs = []
45
+ self.npc_data = {}
46
+ self.jinxs = []
47
+ self.jinx_data = {}
48
+ self.detail = False
49
+ self.detail_idx = 0
50
+ self.detail_scroll = 0
51
+
52
+ ui = TUIState()
53
+
54
+ def term_size():
55
+ try:
56
+ s = os.get_terminal_size()
57
+ return s.columns, s.lines
58
+ except:
59
+ return 80, 24
60
+
61
+ def load_data():
62
+ if CTX_PATH and CTX_PATH.exists():
63
+ with open(CTX_PATH) as f:
64
+ ui.team_ctx = yaml.safe_load(f) or {}
65
+ ui.npcs = sorted([f.stem for f in TEAM_DIR.glob('*.npc')])
66
+ ui.npc_data = {}
67
+ for name in ui.npcs:
68
+ try:
69
+ with open(TEAM_DIR / f"{name}.npc") as f:
70
+ ui.npc_data[name] = yaml.safe_load(f) or {}
71
+ except:
72
+ ui.npc_data[name] = {}
73
+ ui.jinxs = []
74
+ ui.jinx_data = {}
75
+ jdir = TEAM_DIR / 'jinxs'
76
+ if jdir.exists():
77
+ for sub in sorted(jdir.iterdir()):
78
+ if sub.is_dir():
79
+ for jf in sorted(sub.glob('*.jinx')):
80
+ ui.jinxs.append((sub.name, jf.stem))
81
+ try:
82
+ with open(jf) as f:
83
+ ui.jinx_data[f"{sub.name}/{jf.stem}"] = yaml.safe_load(f) or {}
84
+ except:
85
+ ui.jinx_data[f"{sub.name}/{jf.stem}"] = {}
86
+
87
+ def ctx_fields():
88
+ return list(ui.team_ctx.keys()) if ui.team_ctx else []
89
+
90
+ def fmt_val(raw, maxw):
91
+ if isinstance(raw, list):
92
+ v = ', '.join(str(x) for x in raw)
93
+ elif isinstance(raw, bool):
94
+ v = 'true' if raw else 'false'
95
+ else:
96
+ v = str(raw) if raw else ''
97
+ if len(v) > maxw:
98
+ v = v[:maxw-3] + '...'
99
+ return v
100
+
101
+ def save_ctx():
102
+ p = CTX_PATH or (TEAM_DIR / 'team.ctx')
103
+ with open(p, 'w') as f:
104
+ yaml.dump(ui.team_ctx, f, default_flow_style=False)
105
+ ui.status = f"Saved {p.name}"
106
+
107
+ def save_npc(name):
108
+ with open(TEAM_DIR / f"{name}.npc", 'w') as f:
109
+ yaml.dump(ui.npc_data[name], f, default_flow_style=False)
110
+ ui.status = f"Saved {name}.npc"
111
+
112
+ def save_jinx(key):
113
+ folder, name = key.split('/', 1)
114
+ p = TEAM_DIR / 'jinxs' / folder / f"{name}.jinx"
115
+ with open(p, 'w') as f:
116
+ yaml.dump(ui.jinx_data[key], f, default_flow_style=False)
117
+ ui.status = f"Saved {key}.jinx"
118
+
119
+ # ── rendering ──────────────────────────────────────────
120
+ def w(row, col, text):
121
+ """Write text at position, clearing the line first."""
122
+ return f"\033[{row};1H\033[K\033[{row};{col}H{text}"
123
+
124
+ def wline(row, text):
125
+ """Write full line at row, clear to EOL."""
126
+ return f"\033[{row};1H\033[K{text}"
127
+
128
+ def render():
129
+ W, H = term_size()
130
+ out = []
131
+
132
+ # Home cursor (no full-screen clear)
133
+ out.append("\033[H")
134
+
135
+ # ── header ──
136
+ team_name = state.team.name or 'team'
137
+ hdr = f" {team_name} "
138
+ pad = '=' * W
139
+ out.append(wline(1, f"\033[44;37;1m{pad}\033[0m"))
140
+ out.append(f"\033[1;{max(1,(W - len(hdr)) // 2)}H\033[44;37;1m{hdr}\033[0m")
141
+
142
+ # ── tabs ──
143
+ tb = ""
144
+ for i, t in enumerate(ui.tabs):
145
+ if i == ui.tab:
146
+ tb += f"\033[7;1m [{t}] \033[0m"
147
+ else:
148
+ tb += f" {t} "
149
+ out.append(wline(2, f" {tb}"))
150
+ out.append(wline(3, f"\033[90m{'─' * W}\033[0m"))
151
+
152
+ # ── body ──
153
+ body_start = 4
154
+ body_end = H - 3 # leave room for separator, status, footer
155
+ body_h = body_end - body_start + 1
156
+
157
+ if ui.detail:
158
+ render_detail(out, W, body_start, body_h)
159
+ elif ui.tab == 0:
160
+ render_ctx(out, W, body_start, body_h)
161
+ elif ui.tab == 1:
162
+ render_npcs(out, W, body_start, body_h)
163
+ else:
164
+ render_jinxs(out, W, body_start, body_h)
165
+
166
+ # clear any leftover body lines
167
+ # (handled per-line in each render_ function)
168
+
169
+ # ── separator ──
170
+ out.append(wline(H - 2, f"\033[90m{'─' * W}\033[0m"))
171
+
172
+ # ── status ──
173
+ if ui.status:
174
+ out.append(wline(H - 1, f" \033[33m{ui.status[:W-2]}\033[0m"))
175
+ else:
176
+ out.append(wline(H - 1, ""))
177
+
178
+ # ── footer ──
179
+ if ui.editing:
180
+ foot = " [Enter] Save [Esc] Cancel [Backspace] Delete"
181
+ elif ui.detail:
182
+ foot = " [j/k] Navigate [e/Enter] Edit [s] Save [q/Esc] Back"
183
+ else:
184
+ foot = " [Tab] Switch [j/k] Nav [Enter] Open [s] Save [q] Quit"
185
+ out.append(wline(H, f"\033[44;37m{foot[:W].ljust(W)}\033[0m"))
186
+
187
+ sys.stdout.write(''.join(out))
188
+ sys.stdout.flush()
189
+
190
+ def render_ctx(out, W, start, body_h):
191
+ fields = ctx_fields()
192
+ for r in range(body_h):
193
+ row = start + r
194
+ i = r
195
+ if i >= len(fields):
196
+ out.append(wline(row, ""))
197
+ continue
198
+ key = fields[i]
199
+ raw = ui.team_ctx.get(key, '')
200
+ val = fmt_val(raw, W - 20)
201
+
202
+ if i == ui.sel:
203
+ if ui.editing:
204
+ out.append(wline(row, f" \033[1m{key}:\033[0m \033[44;37m{ui.edit_buf}\033[0m\033[K"))
205
+ else:
206
+ line = f" {key}: {val}"
207
+ out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
208
+ else:
209
+ if val:
210
+ out.append(wline(row, f" {key}: \033[32m{val}\033[0m"))
211
+ else:
212
+ out.append(wline(row, f" {key}: \033[90m(empty)\033[0m"))
213
+ if not fields:
214
+ out.append(wline(start, " \033[90mNo context file found.\033[0m"))
215
+
216
+ def render_npcs(out, W, start, body_h):
217
+ vis = ui.npcs[ui.scroll:ui.scroll + body_h]
218
+ for r in range(body_h):
219
+ row = start + r
220
+ i = r + ui.scroll
221
+ if r >= len(vis):
222
+ out.append(wline(row, ""))
223
+ continue
224
+ name = vis[r]
225
+ ndata = ui.npc_data.get(name, {})
226
+ directive = str(ndata.get('primary_directive', ''))[:W-30]
227
+ directive = directive.replace('\n', ' ')
228
+ if i == ui.sel:
229
+ line = f" > {name:<16} {directive}"
230
+ out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
231
+ else:
232
+ out.append(wline(row, f" {name:<16} \033[90m{directive}\033[0m"))
233
+ if not ui.npcs:
234
+ out.append(wline(start, " \033[90mNo NPCs found.\033[0m"))
235
+
236
+ def render_jinxs(out, W, start, body_h):
237
+ vis = ui.jinxs[ui.scroll:ui.scroll + body_h]
238
+ for r in range(body_h):
239
+ row = start + r
240
+ i = r + ui.scroll
241
+ if r >= len(vis):
242
+ out.append(wline(row, ""))
243
+ continue
244
+ folder, name = vis[r]
245
+ label = f"{folder}/{name}"
246
+ jdata = ui.jinx_data.get(label, {})
247
+ desc = str(jdata.get('description', ''))[:W-30]
248
+ if i == ui.sel:
249
+ line = f" > {label:<24} {desc}"
250
+ out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
251
+ else:
252
+ out.append(wline(row, f" {label:<24} \033[90m{desc}\033[0m"))
253
+ if not ui.jinxs:
254
+ out.append(wline(start, " \033[90mNo jinxs found.\033[0m"))
255
+
256
+ def render_detail(out, W, start, body_h):
257
+ if ui.tab == 1 and ui.npcs:
258
+ name = ui.npcs[ui.sel]
259
+ data = ui.npc_data.get(name, {})
260
+ title = f"NPC: {name}"
261
+ elif ui.tab == 2 and ui.jinxs:
262
+ folder, name = ui.jinxs[ui.sel]
263
+ key = f"{folder}/{name}"
264
+ data = ui.jinx_data.get(key, {})
265
+ title = f"Jinx: {key}"
266
+ else:
267
+ for r in range(body_h):
268
+ out.append(wline(start + r, ""))
269
+ return
270
+
271
+ out.append(wline(start, f" \033[1m{title}\033[0m"))
272
+ out.append(wline(start + 1, f" \033[90m{'─' * (W - 4)}\033[0m"))
273
+
274
+ fields = list(data.keys()) if data else []
275
+ vis = fields[ui.detail_scroll:ui.detail_scroll + body_h - 2]
276
+ for r in range(body_h - 2):
277
+ row = start + 2 + r
278
+ fi = r + ui.detail_scroll
279
+ if r >= len(vis):
280
+ out.append(wline(row, ""))
281
+ continue
282
+ fkey = vis[r]
283
+ fval = fmt_val(data.get(fkey, ''), W - 25)
284
+ if fi == ui.detail_idx:
285
+ if ui.editing:
286
+ out.append(wline(row, f" \033[1m{fkey}:\033[0m \033[44;37m{ui.edit_buf}\033[0m"))
287
+ else:
288
+ line = f" {fkey}: {fval}"
289
+ out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
290
+ else:
291
+ out.append(wline(row, f" {fkey}: \033[36m{fval}\033[0m"))
292
+
293
+ # ── input handling ─────────────────────────────────────
294
+ def handle(c):
295
+ if ui.editing:
296
+ return handle_edit(c)
297
+ if c == '\x1b':
298
+ return handle_esc()
299
+ if c == 'q':
300
+ if ui.detail:
301
+ ui.detail = False
302
+ ui.status = ""
303
+ else:
304
+ return False
305
+ elif c == '\t':
306
+ ui.tab = (ui.tab + 1) % 3
307
+ ui.sel = 0
308
+ ui.scroll = 0
309
+ ui.detail = False
310
+ ui.status = ""
311
+ elif c == 'k':
312
+ nav_up()
313
+ elif c == 'j':
314
+ nav_down()
315
+ elif c in ('\r', '\n', 'e'):
316
+ do_enter()
317
+ elif c == 's':
318
+ do_save()
319
+ return True
320
+
321
+ def handle_esc():
322
+ if select.select([sys.stdin], [], [], 0.05)[0]:
323
+ c2 = sys.stdin.read(1)
324
+ if c2 == '[':
325
+ c3 = sys.stdin.read(1)
326
+ if c3 == 'A':
327
+ nav_up()
328
+ elif c3 == 'B':
329
+ nav_down()
330
+ # consume any other escape sequence
331
+ else:
332
+ # bare Esc
333
+ if ui.detail:
334
+ ui.detail = False
335
+ ui.status = ""
336
+ return True
337
+
338
+ def handle_edit(c):
339
+ if c == '\x1b':
340
+ # Check if arrow key or bare esc
341
+ if select.select([sys.stdin], [], [], 0.05)[0]:
342
+ c2 = sys.stdin.read(1)
343
+ if c2 == '[':
344
+ sys.stdin.read(1) # consume arrow char
345
+ else:
346
+ ui.editing = False
347
+ ui.edit_buf = ""
348
+ ui.status = "Cancelled"
349
+ elif c in ('\r', '\n'):
350
+ finish_edit()
351
+ elif c in ('\x7f', '\x08'):
352
+ ui.edit_buf = ui.edit_buf[:-1]
353
+ elif c == '\x15': # Ctrl+U clear line
354
+ ui.edit_buf = ""
355
+ elif 32 <= ord(c) <= 126:
356
+ ui.edit_buf += c
357
+ return True
358
+
359
+ def nav_up():
360
+ if ui.detail:
361
+ ui.detail_idx = max(0, ui.detail_idx - 1)
362
+ if ui.detail_idx < ui.detail_scroll:
363
+ ui.detail_scroll = ui.detail_idx
364
+ else:
365
+ ui.sel = max(0, ui.sel - 1)
366
+ if ui.sel < ui.scroll:
367
+ ui.scroll = ui.sel
368
+ ui.status = ""
369
+
370
+ def nav_down():
371
+ _, H = term_size()
372
+ body_h = H - 6
373
+ if ui.detail:
374
+ if ui.tab == 1 and ui.npcs:
375
+ n = ui.npcs[ui.sel]
376
+ mx = max(0, len(ui.npc_data.get(n, {})) - 1)
377
+ elif ui.tab == 2 and ui.jinxs:
378
+ f, n = ui.jinxs[ui.sel]
379
+ mx = max(0, len(ui.jinx_data.get(f"{f}/{n}", {})) - 1)
380
+ else:
381
+ mx = 0
382
+ ui.detail_idx = min(mx, ui.detail_idx + 1)
383
+ if ui.detail_idx >= ui.detail_scroll + body_h - 2:
384
+ ui.detail_scroll = ui.detail_idx - body_h + 3
385
+ else:
386
+ if ui.tab == 0:
387
+ mx = max(0, len(ctx_fields()) - 1)
388
+ elif ui.tab == 1:
389
+ mx = max(0, len(ui.npcs) - 1)
390
+ else:
391
+ mx = max(0, len(ui.jinxs) - 1)
392
+ ui.sel = min(mx, ui.sel + 1)
393
+ if ui.sel >= ui.scroll + body_h:
394
+ ui.scroll = ui.sel - body_h + 1
395
+ ui.status = ""
396
+
397
+ def do_enter():
398
+ if ui.tab == 0:
399
+ start_ctx_edit()
400
+ elif ui.tab == 1 and ui.npcs:
401
+ if not ui.detail:
402
+ ui.detail = True
403
+ ui.detail_idx = 0
404
+ ui.detail_scroll = 0
405
+ else:
406
+ start_detail_edit()
407
+ elif ui.tab == 2 and ui.jinxs:
408
+ if not ui.detail:
409
+ ui.detail = True
410
+ ui.detail_idx = 0
411
+ ui.detail_scroll = 0
412
+ else:
413
+ start_detail_edit()
414
+
415
+ def start_ctx_edit():
416
+ fields = ctx_fields()
417
+ if ui.sel < len(fields):
418
+ ui.edit_field = fields[ui.sel]
419
+ raw = ui.team_ctx.get(ui.edit_field, '')
420
+ ui.edit_buf = fmt_val(raw, 9999) # full value, no truncation
421
+ ui.editing = True
422
+
423
+ def start_detail_edit():
424
+ if ui.tab == 1:
425
+ name = ui.npcs[ui.sel]
426
+ data = ui.npc_data.get(name, {})
427
+ elif ui.tab == 2:
428
+ f, n = ui.jinxs[ui.sel]
429
+ data = ui.jinx_data.get(f"{f}/{n}", {})
430
+ else:
431
+ return
432
+ fields = list(data.keys())
433
+ if ui.detail_idx < len(fields):
434
+ ui.edit_field = fields[ui.detail_idx]
435
+ raw = data.get(ui.edit_field, '')
436
+ if isinstance(raw, list):
437
+ ui.edit_buf = ', '.join(str(x) for x in raw)
438
+ else:
439
+ ui.edit_buf = str(raw) if raw else ''
440
+ ui.editing = True
441
+
442
+ def finish_edit():
443
+ if ui.tab == 0:
444
+ old = ui.team_ctx.get(ui.edit_field)
445
+ if isinstance(old, list):
446
+ ui.team_ctx[ui.edit_field] = [x.strip() for x in ui.edit_buf.split(',') if x.strip()]
447
+ elif isinstance(old, bool):
448
+ ui.team_ctx[ui.edit_field] = ui.edit_buf.lower() in ('true', '1', 'yes')
449
+ else:
450
+ ui.team_ctx[ui.edit_field] = ui.edit_buf
451
+ elif ui.tab == 1 and ui.detail:
452
+ name = ui.npcs[ui.sel]
453
+ old = ui.npc_data[name].get(ui.edit_field)
454
+ if isinstance(old, list):
455
+ ui.npc_data[name][ui.edit_field] = [x.strip() for x in ui.edit_buf.split(',') if x.strip()]
456
+ elif isinstance(old, bool):
457
+ ui.npc_data[name][ui.edit_field] = ui.edit_buf.lower() in ('true', '1', 'yes')
458
+ else:
459
+ ui.npc_data[name][ui.edit_field] = ui.edit_buf
460
+ elif ui.tab == 2 and ui.detail:
461
+ f, n = ui.jinxs[ui.sel]
462
+ key = f"{f}/{n}"
463
+ old = ui.jinx_data[key].get(ui.edit_field)
464
+ if isinstance(old, list):
465
+ ui.jinx_data[key][ui.edit_field] = [x.strip() for x in ui.edit_buf.split(',') if x.strip()]
466
+ elif isinstance(old, bool):
467
+ ui.jinx_data[key][ui.edit_field] = ui.edit_buf.lower() in ('true', '1', 'yes')
468
+ else:
469
+ ui.jinx_data[key][ui.edit_field] = ui.edit_buf
470
+ ui.editing = False
471
+ ui.edit_buf = ""
472
+ ui.status = f"Set {ui.edit_field}"
473
+
474
+ def do_save():
475
+ if ui.tab == 0:
476
+ save_ctx()
477
+ elif ui.tab == 1 and ui.detail and ui.npcs:
478
+ save_npc(ui.npcs[ui.sel])
479
+ elif ui.tab == 2 and ui.detail and ui.jinxs:
480
+ f, n = ui.jinxs[ui.sel]
481
+ save_jinx(f"{f}/{n}")
482
+
483
+ # ── main loop ──────────────────────────────────────────
484
+ load_data()
485
+ fd = sys.stdin.fileno()
486
+ old_attrs = termios.tcgetattr(fd)
487
+
488
+ try:
489
+ tty.setcbreak(fd)
490
+ sys.stdout.write('\033[?25l') # hide cursor
491
+ sys.stdout.write('\033[2J\033[H') # initial full clear
492
+ sys.stdout.flush()
493
+ render()
494
+ while True:
495
+ c = sys.stdin.read(1)
496
+ if not handle(c):
497
+ break
498
+ render()
499
+ finally:
500
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_attrs)
501
+ sys.stdout.write('\033[?25h\033[2J\033[H')
502
+ sys.stdout.flush()
503
+
504
+ context['output'] = "Team TUI closed."
@@ -213,7 +213,7 @@ steps:
213
213
  line = line[:width+20] # allow for color codes
214
214
 
215
215
  if idx == selected:
216
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
216
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
217
217
  else:
218
218
  sys.stdout.write(f' {line}')
219
219
 
@@ -207,7 +207,7 @@ steps:
207
207
  line = line[:width-1]
208
208
 
209
209
  if idx == selected:
210
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
210
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
211
211
  else:
212
212
  sys.stdout.write(f' {line}')
213
213
 
@@ -277,7 +277,7 @@ steps:
277
277
  line = line[:width+15]
278
278
 
279
279
  if idx == selected:
280
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
280
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
281
281
  else:
282
282
  sys.stdout.write(f' {line}')
283
283
 
@@ -226,7 +226,7 @@ steps:
226
226
  line = line[:width+15]
227
227
 
228
228
  if idx == selected:
229
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
229
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
230
230
  else:
231
231
  sys.stdout.write(f' {line}')
232
232
 
@@ -153,7 +153,7 @@ steps:
153
153
  line = line[:width-1]
154
154
 
155
155
  if idx == selected:
156
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
156
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
157
157
  else:
158
158
  sys.stdout.write(f' {line}')
159
159
 
@@ -225,7 +225,7 @@ steps:
225
225
  line = line[:width+10]
226
226
 
227
227
  if idx == selected:
228
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
228
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
229
229
  else:
230
230
  sys.stdout.write(f' {line}')
231
231
 
@@ -181,7 +181,7 @@ steps:
181
181
  line = line[:width-1]
182
182
 
183
183
  if idx == selected:
184
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
184
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
185
185
  else:
186
186
  sys.stdout.write(f' {line}')
187
187
 
@@ -114,7 +114,7 @@ steps:
114
114
 
115
115
  line = str(items[idx])[:width-2]
116
116
  if current_tab in [0, 1, 2] and idx == selected:
117
- sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
117
+ sys.stdout.write(f'\033[7;1m>{line.ljust(width-2)}\033[0m')
118
118
  else:
119
119
  # Color gold/cliff markers
120
120
  if '[GOLD]' in line:
@@ -451,7 +451,7 @@ steps:
451
451
  line = line[:width-1]
452
452
 
453
453
  if idx == selected:
454
- sys.stdout.write('\033[47;30;1m>' + line.ljust(width-1) + '\033[0m')
454
+ sys.stdout.write('\033[7;1m>' + line.ljust(width-1) + '\033[0m')
455
455
  else:
456
456
  sys.stdout.write(' ' + line)
457
457
 
@@ -129,7 +129,7 @@ steps:
129
129
  line = line[:width-1]
130
130
 
131
131
  if idx == selected:
132
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
132
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
133
133
  else:
134
134
  sys.stdout.write(f' {line}')
135
135
 
@@ -1,5 +1,5 @@
1
1
  jinx_name: guac
2
- description: Interactive Python data analysis TUI - live variable inspector, DataFrame viewer, code execution
2
+ description: Interactive Python TUI - live variable inspector, code execution, DataFrame viewer
3
3
  inputs:
4
4
  - model: null
5
5
  - provider: null
@@ -202,7 +202,7 @@ steps:
202
202
  # ===== HEADER =====
203
203
  mode_colors = {"code": "\033[32m", "natural": "\033[35m", "inspect": "\033[33m"}
204
204
  mode_str = f"{mode_colors[state.mode]}[{state.mode}]\033[0m"
205
- header = f" GUAC - Python Data Analysis {mode_str} "
205
+ header = f" GUAC - Interactive Python {mode_str} "
206
206
  status_color = "\033[33m" if "..." in state.status else "\033[32m"
207
207
  out.append(f"\033[1;1H\033[42;30;1m{header.ljust(width)}\033[0m")
208
208
  out.append(f"\033[1;{width-len(state.status)-3}H{status_color}[{state.status}]\033[0m")
@@ -275,7 +275,7 @@ steps:
275
275
  tabs = ""
276
276
  for i, name in enumerate(panel_names):
277
277
  if i == state.panel:
278
- tabs += f"\033[47;30m {name} \033[0m"
278
+ tabs += f"\033[7m {name} \033[0m"
279
279
  else:
280
280
  tabs += f"\033[90m {name} \033[0m"
281
281
  out.append(f"\033[3;{right_x}H{tabs}")
@@ -291,7 +291,7 @@ steps:
291
291
  info = var_info(name, value)
292
292
  display = f"{name[:10]:<10} {info[:rpanel_w-12]}"
293
293
  if idx == state.selected_var:
294
- out.append(f"\033[{4+i};{right_x}H\033[47;30m>{display[:rpanel_w]}\033[0m")
294
+ out.append(f"\033[{4+i};{right_x}H\033[7m>{display[:rpanel_w]}\033[0m")
295
295
  elif isinstance(value, pd.DataFrame):
296
296
  out.append(f"\033[{4+i};{right_x}H\033[34m {display[:rpanel_w]}\033[0m")
297
297
  elif isinstance(value, np.ndarray):
@@ -130,7 +130,7 @@ steps:
130
130
  color = ''
131
131
 
132
132
  if idx == selected:
133
- sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
133
+ sys.stdout.write(f'\033[7;1m>{line.ljust(width-2)}\033[0m')
134
134
  elif color:
135
135
  sys.stdout.write(f'{color}{line}\033[0m')
136
136
  else:
@@ -123,7 +123,7 @@ steps:
123
123
  line = line[:width-1]
124
124
 
125
125
  if idx == selected:
126
- sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
126
+ sys.stdout.write(f'\033[7;1m>{line.ljust(width-2)}\033[0m')
127
127
  else:
128
128
  sys.stdout.write(f'\033[33m{line}\033[0m')
129
129
 
@@ -160,7 +160,7 @@ steps:
160
160
  line = line[:width-2].ljust(width-1)
161
161
 
162
162
  if idx == selected:
163
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
163
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
164
164
  else:
165
165
  sys.stdout.write(f' {line}')
166
166