npcsh 1.1.21__py3-none-any.whl → 1.1.22__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 (136) hide show
  1. npcsh/_state.py +10 -5
  2. npcsh/benchmark/npcsh_agent.py +22 -14
  3. npcsh/benchmark/templates/install-npcsh.sh.j2 +2 -2
  4. npcsh/mcp_server.py +9 -1
  5. npcsh/npc_team/alicanto.npc +12 -6
  6. npcsh/npc_team/corca.npc +0 -1
  7. npcsh/npc_team/frederic.npc +2 -3
  8. npcsh/npc_team/jinxs/lib/core/edit_file.jinx +83 -61
  9. npcsh/npc_team/jinxs/modes/alicanto.jinx +102 -41
  10. npcsh/npc_team/jinxs/modes/build.jinx +378 -0
  11. npcsh/npc_team/jinxs/modes/convene.jinx +597 -0
  12. npcsh/npc_team/jinxs/modes/corca.jinx +777 -387
  13. npcsh/npc_team/jinxs/modes/kg.jinx +69 -2
  14. npcsh/npc_team/jinxs/modes/plonk.jinx +16 -7
  15. npcsh/npc_team/jinxs/modes/yap.jinx +628 -187
  16. npcsh/npc_team/kadiefa.npc +2 -1
  17. npcsh/npc_team/sibiji.npc +3 -3
  18. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.jinx +102 -41
  19. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.npc +12 -6
  20. npcsh-1.1.22.data/data/npcsh/npc_team/build.jinx +378 -0
  21. npcsh-1.1.22.data/data/npcsh/npc_team/corca.jinx +820 -0
  22. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.npc +0 -1
  23. npcsh-1.1.22.data/data/npcsh/npc_team/edit_file.jinx +119 -0
  24. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic.npc +2 -3
  25. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.npc +2 -1
  26. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kg.jinx +69 -2
  27. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.jinx +16 -7
  28. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.npc +3 -3
  29. npcsh-1.1.22.data/data/npcsh/npc_team/yap.jinx +716 -0
  30. {npcsh-1.1.21.dist-info → npcsh-1.1.22.dist-info}/METADATA +246 -281
  31. {npcsh-1.1.21.dist-info → npcsh-1.1.22.dist-info}/RECORD +127 -130
  32. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +0 -429
  33. npcsh/npc_team/jinxs/lib/core/search.jinx +0 -54
  34. npcsh/npc_team/jinxs/lib/utils/build.jinx +0 -65
  35. npcsh-1.1.21.data/data/npcsh/npc_team/build.jinx +0 -65
  36. npcsh-1.1.21.data/data/npcsh/npc_team/corca.jinx +0 -430
  37. npcsh-1.1.21.data/data/npcsh/npc_team/edit_file.jinx +0 -97
  38. npcsh-1.1.21.data/data/npcsh/npc_team/kg_search.jinx +0 -429
  39. npcsh-1.1.21.data/data/npcsh/npc_team/search.jinx +0 -54
  40. npcsh-1.1.21.data/data/npcsh/npc_team/yap.jinx +0 -275
  41. /npcsh/npc_team/jinxs/lib/{core → utils}/chat.jinx +0 -0
  42. /npcsh/npc_team/jinxs/lib/{core → utils}/cmd.jinx +0 -0
  43. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  44. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.png +0 -0
  45. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/arxiv.jinx +0 -0
  46. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/benchmark.jinx +0 -0
  47. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  48. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  49. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/chat.jinx +0 -0
  50. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/click.jinx +0 -0
  51. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  52. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  53. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  54. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  55. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/compile.jinx +0 -0
  56. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/compress.jinx +0 -0
  57. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/config_tui.jinx +0 -0
  58. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  59. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/convene.jinx +0 -0
  60. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.png +0 -0
  61. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca_example.png +0 -0
  62. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/db_search.jinx +0 -0
  63. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  64. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/file_search.jinx +0 -0
  65. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  66. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic4.png +0 -0
  67. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/git.jinx +0 -0
  68. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.jinx +0 -0
  69. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.npc +0 -0
  70. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.png +0 -0
  71. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/help.jinx +0 -0
  72. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  73. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/init.jinx +0 -0
  74. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
  75. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  76. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  77. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  78. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  79. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  80. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/memories.jinx +0 -0
  81. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/models.jinx +0 -0
  82. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  83. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/notify.jinx +0 -0
  84. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  85. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  86. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/nql.jinx +0 -0
  87. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  88. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  89. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/ots.jinx +0 -0
  90. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/papers.jinx +0 -0
  91. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/paste.jinx +0 -0
  92. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.npc +0 -0
  93. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.png +0 -0
  94. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  95. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/pti.jinx +0 -0
  96. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/python.jinx +0 -0
  97. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  98. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/reattach.jinx +0 -0
  99. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/roll.jinx +0 -0
  100. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  101. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sample.jinx +0 -0
  102. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  103. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  104. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/serve.jinx +0 -0
  105. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/set.jinx +0 -0
  106. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/setup.jinx +0 -0
  107. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sh.jinx +0 -0
  108. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/shh.jinx +0 -0
  109. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.png +0 -0
  110. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  111. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  112. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.jinx +0 -0
  113. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.png +0 -0
  114. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sql.jinx +0 -0
  115. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch.jinx +0 -0
  116. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  117. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  118. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switches.jinx +0 -0
  119. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sync.jinx +0 -0
  120. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/team.jinx +0 -0
  121. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  122. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  123. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  124. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/usage.jinx +0 -0
  125. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  126. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  127. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/wait.jinx +0 -0
  128. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/wander.jinx +0 -0
  129. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/web_search.jinx +0 -0
  130. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  131. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/yap.png +0 -0
  132. {npcsh-1.1.21.data → npcsh-1.1.22.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  133. {npcsh-1.1.21.dist-info → npcsh-1.1.22.dist-info}/WHEEL +0 -0
  134. {npcsh-1.1.21.dist-info → npcsh-1.1.22.dist-info}/entry_points.txt +0 -0
  135. {npcsh-1.1.21.dist-info → npcsh-1.1.22.dist-info}/licenses/LICENSE +0 -0
  136. {npcsh-1.1.21.dist-info → npcsh-1.1.22.dist-info}/top_level.txt +0 -0
@@ -1,429 +0,0 @@
1
- jinx_name: kg_search
2
- description: Search the knowledge graph with interactive TUI
3
- interactive: true
4
- inputs:
5
- - query: ""
6
- - type: "facts"
7
- - mode: "keyword"
8
- - concept: ""
9
- - depth: "2"
10
- - breadth: "5"
11
- - max_results: "20"
12
- - threshold: "0.6"
13
- - npc_name: ""
14
- - team_name: ""
15
- - db_path: ""
16
- - text: "false"
17
-
18
- steps:
19
- - name: search_kg
20
- engine: python
21
- code: |
22
- import os
23
- import sys
24
- import tty
25
- import termios
26
- from npcpy.memory.command_history import CommandHistory
27
- from npcpy.memory.knowledge_graph import (
28
- kg_search_facts, kg_list_concepts, kg_get_facts_for_concept,
29
- kg_get_all_facts, kg_link_search, kg_embedding_search,
30
- kg_hybrid_search, kg_explore_concept
31
- )
32
-
33
- query = context.get('query', '').strip()
34
- search_type = context.get('type', 'facts').lower()
35
- search_mode = context.get('mode', 'keyword').lower()
36
- concept = context.get('concept', '').strip()
37
- max_depth = int(context.get('depth') or 2)
38
- breadth = int(context.get('breadth') or 5)
39
- max_results = int(context.get('max_results') or 20)
40
- threshold = float(context.get('threshold') or 0.6)
41
- text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
42
-
43
- if not query and search_type == 'facts' and not concept:
44
- lines = [
45
- "Usage: /kg_search <query> [mode=keyword|embedding|link|hybrid|all]",
46
- "",
47
- "Search Modes:",
48
- " keyword - Simple keyword matching (default, fast)",
49
- " embedding - Semantic similarity using embeddings",
50
- " link - Traverse graph links from keyword matches",
51
- " hybrid - Combine keyword + link traversal",
52
- " all - Combine all methods (slowest, most thorough)",
53
- "",
54
- "Options:",
55
- " type - Result type: facts, concepts, all",
56
- " concept - Explore a specific concept",
57
- " depth - Link traversal depth (default: 2)",
58
- " breadth - Results per traversal hop (default: 5)",
59
- " max_results - Max total results (default: 20)",
60
- " threshold - Embedding similarity threshold (default: 0.6)",
61
- " text - Text-only output, no TUI (true/false)",
62
- "",
63
- "TUI Controls:",
64
- " j/k or arrows - Navigate",
65
- " 1/2/3 - Sort by score/concept/type",
66
- " c - Toggle concepts view",
67
- " e - Explore selected concept",
68
- " p - Preview full fact/concept",
69
- " q/ESC - Quit",
70
- "",
71
- "Examples:",
72
- " /kg_search python",
73
- " /kg_search python mode=embedding",
74
- " /kg_search python mode=link depth=3",
75
- " /kg_search type=concepts",
76
- " /kg_search concept=coding",
77
- ]
78
- context['output'] = "\n".join(lines)
79
- else:
80
- db_path = context.get('db_path') or os.path.expanduser("~/npcsh_history.db")
81
-
82
- try:
83
- cmd_history = CommandHistory(db_path)
84
- engine = cmd_history.engine
85
-
86
- team_obj = None
87
- try:
88
- team_obj = state.team if 'state' in dir() and state else None
89
- except:
90
- pass
91
- npc_obj = npc if 'npc' in dir() else None
92
-
93
- emodel = None
94
- eprovider = None
95
- try:
96
- if 'state' in dir() and state:
97
- emodel = getattr(state, 'embedding_model', None)
98
- eprovider = getattr(state, 'embedding_provider', None)
99
- except:
100
- pass
101
-
102
- # Collect results
103
- results = []
104
-
105
- if concept:
106
- result = kg_explore_concept(engine, concept, max_depth=max_depth, breadth_per_step=breadth)
107
- for i, f in enumerate(result.get('direct_facts', [])):
108
- results.append({'type': 'fact', 'content': str(f), 'score': 1.0, 'concept': concept, 'source': 'direct'})
109
- for i, f in enumerate(result.get('extended_facts', [])):
110
- results.append({'type': 'fact', 'content': str(f), 'score': 0.5, 'concept': concept, 'source': 'extended'})
111
- for c in result.get('related_concepts', []):
112
- results.append({'type': 'concept', 'content': str(c), 'score': 0.8, 'concept': concept, 'source': 'related'})
113
-
114
- elif search_type == 'concepts':
115
- concepts = kg_list_concepts(engine, search_all_scopes=True)
116
- for c in concepts:
117
- results.append({'type': 'concept', 'content': str(c), 'score': 1.0, 'concept': str(c), 'source': 'list'})
118
-
119
- elif search_type == 'all' and not query:
120
- facts = kg_get_all_facts(engine, search_all_scopes=True)
121
- for f in facts[:max_results]:
122
- results.append({'type': 'fact', 'content': str(f), 'score': 1.0, 'concept': '', 'source': 'all'})
123
-
124
- elif search_mode == 'embedding':
125
- raw_results = kg_embedding_search(
126
- engine, query,
127
- embedding_model=emodel, embedding_provider=eprovider,
128
- similarity_threshold=threshold, max_results=max_results,
129
- search_all_scopes=True
130
- )
131
- for r in raw_results:
132
- results.append({
133
- 'type': r.get('type', 'fact'),
134
- 'content': str(r.get('content', '')),
135
- 'score': r.get('score', 0),
136
- 'concept': r.get('concept', ''),
137
- 'source': 'embedding'
138
- })
139
-
140
- elif search_mode == 'link':
141
- raw_results = kg_link_search(
142
- engine, query,
143
- max_depth=max_depth, breadth_per_step=breadth, max_results=max_results,
144
- search_all_scopes=True
145
- )
146
- for r in raw_results:
147
- results.append({
148
- 'type': r.get('type', 'fact'),
149
- 'content': str(r.get('content', '')),
150
- 'score': r.get('score', 0),
151
- 'concept': r.get('concept', ''),
152
- 'source': f"link-d{r.get('depth', 0)}"
153
- })
154
-
155
- elif search_mode in ['hybrid', 'all', 'keyword+link', 'keyword+embedding']:
156
- raw_results = kg_hybrid_search(
157
- engine, query,
158
- mode=search_mode if search_mode != 'hybrid' else 'keyword+link',
159
- max_depth=max_depth, breadth_per_step=breadth, max_results=max_results,
160
- embedding_model=emodel, embedding_provider=eprovider,
161
- similarity_threshold=threshold,
162
- search_all_scopes=True
163
- )
164
- for r in raw_results:
165
- results.append({
166
- 'type': r.get('type', 'fact'),
167
- 'content': str(r.get('content', '')),
168
- 'score': r.get('score', 0),
169
- 'concept': r.get('concept', ''),
170
- 'source': r.get('source', 'hybrid')
171
- })
172
-
173
- else:
174
- # Default keyword search
175
- facts = kg_search_facts(engine, query, search_all_scopes=True)
176
- for f in facts[:max_results]:
177
- results.append({'type': 'fact', 'content': str(f), 'score': 1.0, 'concept': '', 'source': 'keyword'})
178
-
179
- if not results:
180
- context['output'] = f"No KG results found for '{query}'"
181
- elif text_mode:
182
- # Text-only output
183
- lines = [f"Found {len(results)} KG results:", ""]
184
- for i, r in enumerate(results, 1):
185
- score = f"{r['score']:.2f}" if isinstance(r['score'], float) else str(r['score'])
186
- lines.append(f"{i}. [{r['type']} {score}] {r['content'][:80]}")
187
- context['output'] = "\n".join(lines)
188
- else:
189
- # Interactive TUI mode
190
- def get_terminal_size():
191
- try:
192
- size = os.get_terminal_size()
193
- return size.columns, size.lines
194
- except:
195
- return 80, 24
196
-
197
- width, height = get_terminal_size()
198
- selected = 0
199
- scroll = 0
200
- list_height = height - 5
201
- mode = 'list'
202
- preview_scroll = 0
203
- sort_mode = 'score' # score, concept, type
204
- type_filter = 'all' # all, fact, concept
205
-
206
- def sort_results(results, sort_mode):
207
- if sort_mode == 'score':
208
- return sorted(results, key=lambda x: x.get('score', 0), reverse=True)
209
- elif sort_mode == 'concept':
210
- return sorted(results, key=lambda x: (x.get('concept', ''), -x.get('score', 0)))
211
- elif sort_mode == 'type':
212
- return sorted(results, key=lambda x: (x.get('type', ''), -x.get('score', 0)))
213
- return results
214
-
215
- def filter_results(results, type_filter):
216
- if type_filter == 'all':
217
- return results
218
- return [r for r in results if r.get('type') == type_filter]
219
-
220
- display_results = filter_results(sort_results(results, sort_mode), type_filter)
221
-
222
- fd = sys.stdin.fileno()
223
- old_settings = termios.tcgetattr(fd)
224
-
225
- try:
226
- tty.setcbreak(fd)
227
- sys.stdout.write('\033[?25l')
228
- sys.stdout.write('\033[2J\033[H')
229
-
230
- while True:
231
- width, height = get_terminal_size()
232
- list_height = height - 5
233
-
234
- if mode == 'list':
235
- if selected < scroll:
236
- scroll = selected
237
- elif selected >= scroll + list_height:
238
- scroll = selected - list_height + 1
239
-
240
- sys.stdout.write('\033[H')
241
-
242
- # Header
243
- if mode == 'list':
244
- sort_ind = {'score': '1', 'concept': '2', 'type': '3'}[sort_mode]
245
- label = query or concept or search_type
246
- header = f" KG SEARCH ({len(display_results)} results): '{label}' [sort:{sort_mode}({sort_ind}) filter:{type_filter}] "
247
- else:
248
- header = f" PREVIEW: {display_results[selected]['type']} "
249
- sys.stdout.write(f'\033[7;1m{header.ljust(width)}\033[0m\n')
250
-
251
- # Column headers
252
- if mode == 'list':
253
- col_header = f' {"TYPE":<8} {"SCORE":<6} {"CONCEPT":<15} {"CONTENT":<50}'
254
- sys.stdout.write(f'\033[90m{col_header[:width]}\033[0m\n')
255
- else:
256
- sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
257
-
258
- if mode == 'list':
259
- for i in range(list_height):
260
- idx = scroll + i
261
- sys.stdout.write(f'\033[{3+i};1H\033[K')
262
- if idx >= len(display_results):
263
- continue
264
-
265
- r = display_results[idx]
266
- rtype = r.get('type', '?')[:8]
267
- score = f"{r.get('score', 0):.2f}" if isinstance(r.get('score'), float) else str(r.get('score', ''))[:6]
268
- concept_str = (r.get('concept', '') or '')[:15]
269
- content = (r.get('content', '') or '')[:60].replace('\n', ' ')
270
-
271
- # Color by type
272
- if r.get('type') == 'concept':
273
- type_color = '\033[35m' # magenta
274
- else:
275
- type_color = '\033[36m' # cyan
276
-
277
- line = f" {type_color}{rtype:<8}\033[0m {score:<6} {concept_str:<15} {content}"
278
- line = line[:width+15]
279
-
280
- if idx == selected:
281
- sys.stdout.write(f'\033[7;1m>{line}\033[0m')
282
- else:
283
- sys.stdout.write(f' {line}')
284
-
285
- # Status bar
286
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
287
- sel = display_results[selected] if display_results else {}
288
- source = sel.get('source', '')
289
- sys.stdout.write(f'\033[{height-1};1H\033[K Source: {source}'.ljust(width))
290
- sys.stdout.write(f'\033[{height};1H\033[K\033[7m j/k:Nav 1/2/3:Sort c:Concepts e:Explore p:Preview q:Quit [{selected+1}/{len(display_results)}] \033[0m')
291
-
292
- else: # preview mode
293
- sel = display_results[selected]
294
- content = sel.get('content', '')
295
- lines = content.split('\n')
296
-
297
- # Add metadata at top
298
- meta_lines = [
299
- f"Type: {sel.get('type', '')}",
300
- f"Score: {sel.get('score', '')}",
301
- f"Concept: {sel.get('concept', '')}",
302
- f"Source: {sel.get('source', '')}",
303
- "─" * 40,
304
- ]
305
- all_lines = meta_lines + lines
306
-
307
- for i in range(list_height):
308
- idx = preview_scroll + i
309
- sys.stdout.write(f'\033[{3+i};1H\033[K')
310
- if idx < len(all_lines):
311
- sys.stdout.write(all_lines[idx][:width-1])
312
-
313
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
314
- sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(all_lines)} lines]')
315
- sys.stdout.write(f'\033[{height};1H\033[K\033[7m j/k:Scroll b:Back e:Explore q:Quit \033[0m')
316
-
317
- sys.stdout.flush()
318
-
319
- c = os.read(fd, 1).decode('latin-1')
320
-
321
- if c == '\x1b':
322
- import select as _sel
323
- if _sel.select([fd], [], [], 0.05)[0]:
324
- c2 = os.read(fd, 1).decode('latin-1')
325
- else:
326
- if mode == 'preview':
327
- mode = 'list'
328
- sys.stdout.write('\033[2J\033[H')
329
- else:
330
- context['output'] = "Cancelled."
331
- break
332
- continue
333
- if c2 == '[':
334
- c3 = os.read(fd, 1).decode('latin-1')
335
- if c3 == 'A': # Up
336
- if mode == 'list' and selected > 0:
337
- selected -= 1
338
- elif mode == 'preview' and preview_scroll > 0:
339
- preview_scroll -= 1
340
- elif c3 == 'B': # Down
341
- if mode == 'list' and selected < len(display_results) - 1:
342
- selected += 1
343
- elif mode == 'preview':
344
- sel = display_results[selected]
345
- content = sel.get('content', '')
346
- all_lines = content.split('\n')
347
- if preview_scroll < max(0, len(all_lines) + 5 - list_height):
348
- preview_scroll += 1
349
- else:
350
- if mode == 'preview':
351
- mode = 'list'
352
- sys.stdout.write('\033[2J\033[H')
353
- else:
354
- context['output'] = "Cancelled."
355
- break
356
- continue
357
-
358
- if c == 'q' or c == '\x03':
359
- context['output'] = "Cancelled."
360
- break
361
- elif c == 'k':
362
- if mode == 'list' and selected > 0:
363
- selected -= 1
364
- elif mode == 'preview' and preview_scroll > 0:
365
- preview_scroll -= 1
366
- elif c == 'j':
367
- if mode == 'list' and selected < len(display_results) - 1:
368
- selected += 1
369
- elif mode == 'preview':
370
- sel = display_results[selected]
371
- content = sel.get('content', '')
372
- all_lines = content.split('\n')
373
- if preview_scroll < max(0, len(all_lines) + 5 - list_height):
374
- preview_scroll += 1
375
- elif c == '1':
376
- sort_mode = 'score'
377
- display_results = filter_results(sort_results(results, sort_mode), type_filter)
378
- selected = 0
379
- scroll = 0
380
- elif c == '2':
381
- sort_mode = 'concept'
382
- display_results = filter_results(sort_results(results, sort_mode), type_filter)
383
- selected = 0
384
- scroll = 0
385
- elif c == '3':
386
- sort_mode = 'type'
387
- display_results = filter_results(sort_results(results, sort_mode), type_filter)
388
- selected = 0
389
- scroll = 0
390
- elif c == 'c' and mode == 'list':
391
- # Toggle type filter
392
- if type_filter == 'all':
393
- type_filter = 'concept'
394
- elif type_filter == 'concept':
395
- type_filter = 'fact'
396
- else:
397
- type_filter = 'all'
398
- display_results = filter_results(sort_results(results, sort_mode), type_filter)
399
- selected = 0
400
- scroll = 0
401
- elif c == 'e' and display_results:
402
- # Explore the selected concept
403
- sel = display_results[selected]
404
- explore_concept = sel.get('concept') or sel.get('content', '')
405
- if sel.get('type') == 'concept':
406
- explore_concept = sel.get('content', '')
407
- context['output'] = f"Explore concept: {explore_concept}\n\nRun: /kg_search concept={explore_concept}"
408
- break
409
- elif c == 'p' and mode == 'list' and display_results:
410
- mode = 'preview'
411
- preview_scroll = 0
412
- sys.stdout.write('\033[2J\033[H')
413
- elif c == 'b' and mode == 'preview':
414
- mode = 'list'
415
- sys.stdout.write('\033[2J\033[H')
416
- elif c in ('\r', '\n') and display_results:
417
- sel = display_results[selected]
418
- context['output'] = f"Selected: {sel.get('content', '')[:100]}"
419
- break
420
-
421
- finally:
422
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
423
- sys.stdout.write('\033[?25h')
424
- sys.stdout.write('\033[2J\033[H')
425
- sys.stdout.flush()
426
-
427
- except Exception as e:
428
- import traceback
429
- context['output'] = "KG search error: " + str(e) + "\n" + traceback.format_exc()
@@ -1,54 +0,0 @@
1
- jinx_name: search
2
- description: Unified search - routes to web_search, mem_search, kg_search, file_search, db_search
3
- inputs:
4
- - query: ""
5
- - type: "web"
6
- - provider: ""
7
- - num_results: ""
8
- - status: ""
9
- - npc_name: ""
10
- - team_name: ""
11
- - file_paths: ""
12
- - emodel: ""
13
- - eprovider: ""
14
- - concept: ""
15
- - db_path: ""
16
- - limit: ""
17
-
18
- steps:
19
- {% if type == "mem" %}
20
- - name: search
21
- engine: mem_search
22
- query: "{{ query }}"
23
- status: "{{ status }}"
24
- npc_name: "{{ npc_name }}"
25
- team_name: "{{ team_name }}"
26
- db_path: "{{ db_path }}"
27
- {% elif type == "kg" %}
28
- - name: search
29
- engine: kg_search
30
- query: "{{ query }}"
31
- concept: "{{ concept }}"
32
- npc_name: "{{ npc_name }}"
33
- team_name: "{{ team_name }}"
34
- db_path: "{{ db_path }}"
35
- {% elif type == "file" %}
36
- - name: search
37
- engine: file_search
38
- query: "{{ query }}"
39
- file_paths: "{{ file_paths }}"
40
- emodel: "{{ emodel }}"
41
- eprovider: "{{ eprovider }}"
42
- {% elif type == "db" %}
43
- - name: search
44
- engine: db_search
45
- query: "{{ query }}"
46
- db_path: "{{ db_path }}"
47
- limit: "{{ limit }}"
48
- {% else %}
49
- - name: search
50
- engine: web_search
51
- query: "{{ query }}"
52
- provider: "{{ provider }}"
53
- num_results: "{{ num_results }}"
54
- {% endif %}
@@ -1,65 +0,0 @@
1
- jinx_name: "build"
2
- description: "Build deployment artifacts for NPC team"
3
- inputs:
4
- - target: "flask"
5
- - outdir: "./build"
6
- - team: "./npc_team"
7
- - port: 5337
8
- - cors: ""
9
- steps:
10
- - name: "execute_build"
11
- engine: "python"
12
- code: |
13
- import os
14
-
15
- # Assume these build functions are available in the execution environment
16
- # from a larger project context, e.g., from npcpy.build_funcs
17
- try:
18
- from npcpy.build_funcs import (
19
- build_flask_server,
20
- build_docker_compose,
21
- build_cli_executable,
22
- build_static_site,
23
- )
24
- except ImportError:
25
- # Provide mock functions for demonstration or error handling
26
- def build_flask_server(config, **kwargs): return {"output": f"Mock build flask: {config}", "messages": []}
27
- def build_docker_compose(config, **kwargs): return {"output": f"Mock build docker: {config}", "messages": []}
28
- def build_cli_executable(config, **kwargs): return {"output": f"Mock build cli: {config}", "messages": []}
29
- def build_static_site(config, **kwargs): return {"output": f"Mock build static: {config}", "messages": []}
30
-
31
- target = context.get('target') or 'flask'
32
- output_dir = context.get('outdir') or './build'
33
- team_path = context.get('team') or './npc_team'
34
- port = context.get('port') or 5337
35
- cors_origins_str = context.get('cors') or ''
36
-
37
- cors_origins = [origin.strip() for origin in cors_origins_str.split(',') if origin.strip()] or None
38
-
39
- build_config = {
40
- 'team_path': os.path.abspath(os.path.expanduser(team_path)),
41
- 'output_dir': os.path.abspath(os.path.expanduser(output_dir)),
42
- 'target': target,
43
- 'port': port,
44
- 'cors_origins': cors_origins,
45
- }
46
-
47
- builders = {
48
- 'flask': build_flask_server,
49
- 'docker': build_docker_compose,
50
- 'cli': build_cli_executable,
51
- 'static': build_static_site,
52
- }
53
-
54
- output_messages = context.get('messages', [])
55
- output_result = ""
56
-
57
- if target not in builders:
58
- output_result = f"Unknown target: {target}. Available: {list(builders.keys())}"
59
- else:
60
- result = builders[target](build_config, messages=output_messages)
61
- output_result = result.get('output', 'Build command executed.')
62
- output_messages = result.get('messages', output_messages) # Update messages from builder call
63
-
64
- context['output'] = output_result
65
- context['messages'] = output_messages
@@ -1,65 +0,0 @@
1
- jinx_name: "build"
2
- description: "Build deployment artifacts for NPC team"
3
- inputs:
4
- - target: "flask"
5
- - outdir: "./build"
6
- - team: "./npc_team"
7
- - port: 5337
8
- - cors: ""
9
- steps:
10
- - name: "execute_build"
11
- engine: "python"
12
- code: |
13
- import os
14
-
15
- # Assume these build functions are available in the execution environment
16
- # from a larger project context, e.g., from npcpy.build_funcs
17
- try:
18
- from npcpy.build_funcs import (
19
- build_flask_server,
20
- build_docker_compose,
21
- build_cli_executable,
22
- build_static_site,
23
- )
24
- except ImportError:
25
- # Provide mock functions for demonstration or error handling
26
- def build_flask_server(config, **kwargs): return {"output": f"Mock build flask: {config}", "messages": []}
27
- def build_docker_compose(config, **kwargs): return {"output": f"Mock build docker: {config}", "messages": []}
28
- def build_cli_executable(config, **kwargs): return {"output": f"Mock build cli: {config}", "messages": []}
29
- def build_static_site(config, **kwargs): return {"output": f"Mock build static: {config}", "messages": []}
30
-
31
- target = context.get('target') or 'flask'
32
- output_dir = context.get('outdir') or './build'
33
- team_path = context.get('team') or './npc_team'
34
- port = context.get('port') or 5337
35
- cors_origins_str = context.get('cors') or ''
36
-
37
- cors_origins = [origin.strip() for origin in cors_origins_str.split(',') if origin.strip()] or None
38
-
39
- build_config = {
40
- 'team_path': os.path.abspath(os.path.expanduser(team_path)),
41
- 'output_dir': os.path.abspath(os.path.expanduser(output_dir)),
42
- 'target': target,
43
- 'port': port,
44
- 'cors_origins': cors_origins,
45
- }
46
-
47
- builders = {
48
- 'flask': build_flask_server,
49
- 'docker': build_docker_compose,
50
- 'cli': build_cli_executable,
51
- 'static': build_static_site,
52
- }
53
-
54
- output_messages = context.get('messages', [])
55
- output_result = ""
56
-
57
- if target not in builders:
58
- output_result = f"Unknown target: {target}. Available: {list(builders.keys())}"
59
- else:
60
- result = builders[target](build_config, messages=output_messages)
61
- output_result = result.get('output', 'Build command executed.')
62
- output_messages = result.get('messages', output_messages) # Update messages from builder call
63
-
64
- context['output'] = output_result
65
- context['messages'] = output_messages