npcsh 1.1.20__py3-none-any.whl → 1.1.21__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 (166) hide show
  1. npcsh/_state.py +5 -71
  2. npcsh/diff_viewer.py +3 -3
  3. npcsh/npc_team/jinxs/lib/core/compress.jinx +373 -85
  4. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +17 -6
  5. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +17 -6
  6. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +19 -8
  7. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +52 -14
  8. npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
  9. npcsh/npc_team/jinxs/{bin → lib/utils}/jinxs.jinx +12 -12
  10. npcsh/npc_team/jinxs/{bin → lib/utils}/models.jinx +7 -7
  11. npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +6 -6
  12. npcsh/npc_team/jinxs/modes/alicanto.jinx +1573 -296
  13. npcsh/npc_team/jinxs/modes/arxiv.jinx +5 -5
  14. npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
  15. npcsh/npc_team/jinxs/modes/corca.jinx +3 -3
  16. npcsh/npc_team/jinxs/modes/git.jinx +795 -0
  17. {npcsh-1.1.20.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/modes}/kg.jinx +13 -13
  18. npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
  19. npcsh/npc_team/jinxs/{bin → modes}/nql.jinx +10 -21
  20. npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
  21. npcsh/npc_team/jinxs/modes/plonk.jinx +490 -304
  22. npcsh/npc_team/jinxs/modes/reattach.jinx +3 -3
  23. npcsh/npc_team/jinxs/modes/spool.jinx +3 -3
  24. npcsh/npc_team/jinxs/{bin → modes}/team.jinx +12 -12
  25. npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
  26. npcsh/npc_team/jinxs/modes/wander.jinx +454 -181
  27. npcsh/npc_team/jinxs/modes/yap.jinx +10 -3
  28. npcsh/npcsh.py +112 -47
  29. npcsh/routes.py +4 -1
  30. npcsh/salmon_simulation.py +0 -0
  31. npcsh-1.1.21.data/data/npcsh/npc_team/alicanto.jinx +1633 -0
  32. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/arxiv.jinx +5 -5
  33. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
  34. npcsh-1.1.21.data/data/npcsh/npc_team/compress.jinx +428 -0
  35. npcsh-1.1.21.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  36. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.jinx +3 -3
  37. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/db_search.jinx +17 -6
  38. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/file_search.jinx +17 -6
  39. npcsh-1.1.21.data/data/npcsh/npc_team/git.jinx +795 -0
  40. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/jinxs.jinx +12 -12
  41. {npcsh/npc_team/jinxs/bin → npcsh-1.1.21.data/data/npcsh/npc_team}/kg.jinx +13 -13
  42. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kg_search.jinx +19 -8
  43. npcsh-1.1.21.data/data/npcsh/npc_team/memories.jinx +414 -0
  44. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/models.jinx +7 -7
  45. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/nql.jinx +10 -21
  46. npcsh-1.1.21.data/data/npcsh/npc_team/papers.jinx +578 -0
  47. npcsh-1.1.21.data/data/npcsh/npc_team/plonk.jinx +565 -0
  48. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/reattach.jinx +3 -3
  49. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/setup.jinx +6 -6
  50. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.jinx +3 -3
  51. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/team.jinx +12 -12
  52. npcsh-1.1.21.data/data/npcsh/npc_team/vixynt.jinx +388 -0
  53. npcsh-1.1.21.data/data/npcsh/npc_team/wander.jinx +728 -0
  54. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/web_search.jinx +52 -14
  55. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.jinx +10 -3
  56. {npcsh-1.1.20.dist-info → npcsh-1.1.21.dist-info}/METADATA +2 -2
  57. {npcsh-1.1.20.dist-info → npcsh-1.1.21.dist-info}/RECORD +145 -150
  58. npcsh-1.1.21.dist-info/entry_points.txt +11 -0
  59. npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -300
  60. npcsh/npc_team/jinxs/bin/memories.jinx +0 -317
  61. npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
  62. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
  63. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
  64. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
  65. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
  66. npcsh/npc_team/plonkjr.npc +0 -23
  67. npcsh-1.1.20.data/data/npcsh/npc_team/alicanto.jinx +0 -356
  68. npcsh-1.1.20.data/data/npcsh/npc_team/compress.jinx +0 -140
  69. npcsh-1.1.20.data/data/npcsh/npc_team/config_tui.jinx +0 -300
  70. npcsh-1.1.20.data/data/npcsh/npc_team/mem_review.jinx +0 -73
  71. npcsh-1.1.20.data/data/npcsh/npc_team/mem_search.jinx +0 -388
  72. npcsh-1.1.20.data/data/npcsh/npc_team/memories.jinx +0 -317
  73. npcsh-1.1.20.data/data/npcsh/npc_team/paper_search.jinx +0 -412
  74. npcsh-1.1.20.data/data/npcsh/npc_team/plonk.jinx +0 -379
  75. npcsh-1.1.20.data/data/npcsh/npc_team/plonkjr.npc +0 -23
  76. npcsh-1.1.20.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
  77. npcsh-1.1.20.data/data/npcsh/npc_team/vixynt.jinx +0 -122
  78. npcsh-1.1.20.data/data/npcsh/npc_team/wander.jinx +0 -455
  79. npcsh-1.1.20.dist-info/entry_points.txt +0 -25
  80. /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
  81. /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
  82. /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
  83. /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
  84. /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
  85. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  86. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  87. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.png +0 -0
  88. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  89. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  90. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/build.jinx +0 -0
  91. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/chat.jinx +0 -0
  92. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/click.jinx +0 -0
  93. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  94. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  95. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  96. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  97. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/compile.jinx +0 -0
  98. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  99. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/convene.jinx +0 -0
  100. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.npc +0 -0
  101. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.png +0 -0
  102. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca_example.png +0 -0
  103. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  104. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  105. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  106. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic.npc +0 -0
  107. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic4.png +0 -0
  108. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.jinx +0 -0
  109. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.npc +0 -0
  110. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.png +0 -0
  111. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/help.jinx +0 -0
  112. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  113. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/init.jinx +0 -0
  114. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  115. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  116. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  117. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  118. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  119. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  120. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  121. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/notify.jinx +0 -0
  122. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  123. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  124. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  125. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  126. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/ots.jinx +0 -0
  127. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/paste.jinx +0 -0
  128. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.npc +0 -0
  129. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.png +0 -0
  130. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  131. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/pti.jinx +0 -0
  132. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/python.jinx +0 -0
  133. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  134. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/roll.jinx +0 -0
  135. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  136. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sample.jinx +0 -0
  137. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  138. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/search.jinx +0 -0
  139. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  140. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/serve.jinx +0 -0
  141. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/set.jinx +0 -0
  142. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sh.jinx +0 -0
  143. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/shh.jinx +0 -0
  144. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  145. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.png +0 -0
  146. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  147. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  148. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.png +0 -0
  149. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sql.jinx +0 -0
  150. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch.jinx +0 -0
  151. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  152. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  153. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switches.jinx +0 -0
  154. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sync.jinx +0 -0
  155. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  156. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  157. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  158. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/usage.jinx +0 -0
  159. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  160. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/wait.jinx +0 -0
  161. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  162. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.png +0 -0
  163. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  164. {npcsh-1.1.20.dist-info → npcsh-1.1.21.dist-info}/WHEEL +0 -0
  165. {npcsh-1.1.20.dist-info → npcsh-1.1.21.dist-info}/licenses/LICENSE +0 -0
  166. {npcsh-1.1.20.dist-info → npcsh-1.1.21.dist-info}/top_level.txt +0 -0
@@ -1,122 +0,0 @@
1
- jinx_name: "vixynt"
2
- description: "Generates images from text descriptions or edits existing ones."
3
- inputs:
4
- - prompt
5
- - model: null
6
- - provider: null
7
- - output_name: null
8
- - attachments: null
9
- - n_images: null
10
- - height: null
11
- - width: null
12
- steps:
13
- - name: "generate_or_edit_image"
14
- engine: "python"
15
- code: |
16
- import os
17
- import base64
18
- from io import BytesIO
19
- from datetime import datetime
20
- from PIL import Image
21
- from npcpy.llm_funcs import gen_image
22
-
23
- # Extract inputs from context with proper type conversion
24
- image_prompt = str(context.get('prompt', '')).strip()
25
- output_name = context.get('output_name')
26
- attachments_str = context.get('attachments')
27
-
28
- # Handle integer inputs - they may come as strings or ints
29
- try:
30
- n_images = int(context.get('n_images', 1))
31
- except (ValueError, TypeError):
32
- n_images = 1
33
-
34
- try:
35
- height = int(context.get('height', 1024))
36
- except (ValueError, TypeError):
37
- height = 1024
38
-
39
- try:
40
- width = int(context.get('width', 1024))
41
- except (ValueError, TypeError):
42
- width = 1024
43
-
44
- # Get model and provider from context or environment
45
- model = context.get('model')
46
- provider = context.get('provider')
47
-
48
- # Fallback to environment variables
49
- if not model:
50
- model = os.getenv('NPCSH_IMAGE_GEN_MODEL')
51
- if not provider:
52
- provider = os.getenv('NPCSH_IMAGE_GEN_PROVIDER')
53
-
54
- # Parse attachments
55
- input_images = []
56
- if attachments_str and str(attachments_str).strip():
57
- input_images = [p.strip() for p in str(attachments_str).split(',')]
58
-
59
- output_messages = context.get('messages', [])
60
-
61
- if not image_prompt:
62
- output = "Error: No prompt provided for image generation."
63
- else:
64
- try:
65
- # Generate image(s)
66
- result = gen_image(
67
- prompt=image_prompt,
68
- model=model,
69
- provider=provider,
70
- npc=npc,
71
- height=height,
72
- width=width,
73
- n_images=n_images,
74
- input_images=input_images if input_images else None
75
- )
76
-
77
- # Ensure we have a list of images
78
- if not isinstance(result, list):
79
- images_list = [result] if result is not None else []
80
- else:
81
- images_list = result
82
-
83
- saved_files = []
84
-
85
- for i, image in enumerate(images_list):
86
- if image is None:
87
- continue
88
-
89
- # Determine output filename
90
- if output_name and str(output_name).strip():
91
- base_name, ext = os.path.splitext(os.path.expanduser(str(output_name)))
92
- if not ext:
93
- ext = ".png"
94
- current_output_file = f"{base_name}_{i}{ext}" if len(images_list) > 1 else f"{base_name}{ext}"
95
- else:
96
- os.makedirs(os.path.expanduser("~/.npcsh/images/"), exist_ok=True)
97
- current_output_file = (
98
- os.path.expanduser("~/.npcsh/images/")
99
- + f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{i}.png"
100
- )
101
-
102
- # Save image to file
103
- image.save(current_output_file)
104
- saved_files.append(current_output_file)
105
-
106
- if saved_files:
107
- output = f"Image(s) generated: {', '.join(saved_files)}"
108
- if input_images:
109
- output = f"Image(s) edited: {', '.join(saved_files)}"
110
- context['generated_images'] = saved_files
111
- else:
112
- output = "No images were generated."
113
-
114
- except Exception as e:
115
- import traceback
116
- traceback.print_exc()
117
- output = f"Error {'editing' if input_images else 'generating'} image: {str(e)}"
118
-
119
- context['output'] = output
120
- context['messages'] = output_messages
121
- context['model'] = model
122
- context['provider'] = provider
@@ -1,73 +0,0 @@
1
- jinx_name: mem_review
2
- description: Review pending memories with interactive UI
3
- inputs:
4
- - limit: "20"
5
- - npc_filter: ""
6
- - team_filter: ""
7
- - db_path: ""
8
-
9
- steps:
10
- - name: review_memories
11
- engine: python
12
- code: |
13
- import os
14
- from npcpy.memory.command_history import CommandHistory
15
- from npcpy.memory.memory_processor import memory_batch_review_ui
16
-
17
- limit = int(context.get('limit') or 20)
18
- npc_filter = context.get('npc_filter') or None
19
- team_filter = context.get('team_filter') or None
20
- db_path = context.get('db_path') or os.path.expanduser("~/npcsh_history.db")
21
-
22
- # Get current npc/team from context if not specified
23
- if not npc_filter:
24
- try:
25
- npc_filter = npc.name if 'npc' in dir() and npc else None
26
- except:
27
- pass
28
-
29
- if not team_filter:
30
- try:
31
- team_filter = state.team.name if 'state' in dir() and state and state.team else None
32
- except:
33
- pass
34
-
35
- try:
36
- cmd_history = CommandHistory(db_path)
37
-
38
- # Show what we're about to review
39
- pending = cmd_history.get_pending_memories(limit=limit)
40
- if npc_filter:
41
- pending = [m for m in pending if m.get('npc') == npc_filter]
42
- if team_filter:
43
- pending = [m for m in pending if m.get('team') == team_filter]
44
-
45
- if not pending:
46
- context['output'] = "No pending memories to review."
47
- else:
48
- print(f"\nFound {len(pending)} pending memories")
49
- if npc_filter:
50
- print(f" NPC filter: {npc_filter}")
51
- if team_filter:
52
- print(f" Team filter: {team_filter}")
53
-
54
- # Run interactive review
55
- stats = memory_batch_review_ui(
56
- cmd_history,
57
- npc_filter=npc_filter,
58
- team_filter=team_filter,
59
- limit=limit
60
- )
61
-
62
- lines = [
63
- "Review complete:",
64
- f" Approved: {stats.get('approved', 0)}",
65
- f" Rejected: {stats.get('rejected', 0)}",
66
- f" Edited: {stats.get('edited', 0)}",
67
- f" Skipped: {stats.get('skipped', 0)}",
68
- ]
69
- context['output'] = "\n".join(lines)
70
-
71
- except Exception as e:
72
- import traceback
73
- context['output'] = "Memory review error: " + str(e) + "\n" + traceback.format_exc()
@@ -1,388 +0,0 @@
1
- jinx_name: mem_search
2
- description: Search memories with interactive TUI and approval workflow
3
- inputs:
4
- - query: ""
5
- - status: "all"
6
- - npc_name: ""
7
- - team_name: ""
8
- - max_results: "20"
9
- - db_path: ""
10
- - text: "false"
11
-
12
- steps:
13
- - name: search_memories
14
- engine: python
15
- code: |
16
- import os
17
- import sys
18
- import tty
19
- import termios
20
- from datetime import datetime
21
- from npcpy.memory.command_history import CommandHistory
22
- from npcpy.memory.memory_processor import get_relevant_memories
23
-
24
- query = context.get('query', '').strip()
25
- text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
26
-
27
- if not query:
28
- lines = [
29
- "Usage: /mem_search <query> [status=all|approved|pending]",
30
- "",
31
- "Options:",
32
- " status - Filter by status (all, approved, pending). Default is all",
33
- " npc_name - Filter by NPC name",
34
- " team_name - Filter by team name",
35
- " max_results - Max results to return (default 20)",
36
- " db_path - Path to history database",
37
- " text - Text-only output, no TUI (true/false)",
38
- "",
39
- "TUI Controls:",
40
- " j/k or arrows - Navigate",
41
- " 1/2/3 - Sort by time/status/npc",
42
- " f - Filter by status (all/approved/pending)",
43
- " p - Preview full memory",
44
- " a - Approve selected memory",
45
- " x - Reject selected memory",
46
- " q/ESC - Quit",
47
- "",
48
- "Examples:",
49
- " /mem_search python",
50
- " /mem_search debugging status=pending",
51
- ]
52
- context['output'] = "\n".join(lines)
53
- else:
54
- status_filter = context.get('status', 'all').lower()
55
- npc_name = context.get('npc_name') or (npc.name if 'npc' in dir() and npc else None)
56
- team_name = context.get('team_name') or None
57
- try:
58
- team_name = team_name or (state.team.name if 'state' in dir() and state and state.team else None)
59
- except:
60
- pass
61
- max_results = int(context.get('max_results') or 20)
62
- db_path = context.get('db_path') or os.path.expanduser("~/npcsh_history.db")
63
- current_path = os.getcwd()
64
-
65
- try:
66
- cmd_history = CommandHistory(db_path)
67
-
68
- if status_filter == 'approved':
69
- state_obj = state if 'state' in dir() else None
70
- memories = get_relevant_memories(
71
- command_history=cmd_history,
72
- npc_name=npc_name or '__none__',
73
- team_name=team_name or '__none__',
74
- path=current_path,
75
- query=query,
76
- max_memories=max_results,
77
- state=state_obj
78
- )
79
- else:
80
- memories = cmd_history.search_memory(
81
- query=query,
82
- npc=npc_name,
83
- team=team_name,
84
- status_filter=status_filter if status_filter != 'all' else None,
85
- limit=max_results
86
- )
87
-
88
- # Normalize to list of dicts
89
- if memories:
90
- normalized = []
91
- for mem in memories:
92
- if isinstance(mem, dict):
93
- normalized.append(mem)
94
- else:
95
- normalized.append({'content': str(mem), 'status': 'unknown', 'timestamp': '', 'npc': ''})
96
- memories = normalized
97
- else:
98
- memories = []
99
-
100
- if not memories:
101
- context['output'] = f"No memories found for '{query}' (status={status_filter})"
102
- elif text_mode:
103
- # Text-only output
104
- lines = [f"Found {len(memories)} memories (status={status_filter}):", ""]
105
- for i, mem in enumerate(memories, 1):
106
- ts = mem.get('timestamp', 'unknown')
107
- content = mem.get('final_memory') or mem.get('initial_memory') or mem.get('content', '')
108
- status = mem.get('status', '')
109
- lines.append(f"{i}. [{ts}] ({status}) {str(content)[:80]}")
110
- context['output'] = "\n".join(lines)
111
- else:
112
- # Interactive TUI mode
113
- def get_terminal_size():
114
- try:
115
- size = os.get_terminal_size()
116
- return size.columns, size.lines
117
- except:
118
- return 80, 24
119
-
120
- def format_ts(ts):
121
- if not ts:
122
- return 'unknown'
123
- try:
124
- if 'T' in str(ts):
125
- dt = datetime.fromisoformat(str(ts).replace('Z', '+00:00'))
126
- else:
127
- dt = datetime.strptime(str(ts)[:19], '%Y-%m-%d %H:%M:%S')
128
- now = datetime.now()
129
- diff = now - dt.replace(tzinfo=None)
130
- if diff.days == 0:
131
- return f"Today {dt.strftime('%H:%M')}"
132
- elif diff.days == 1:
133
- return f"Yesterday {dt.strftime('%H:%M')}"
134
- elif diff.days < 7:
135
- return dt.strftime('%a %H:%M')
136
- else:
137
- return dt.strftime('%b %d')
138
- except:
139
- return str(ts)[:12]
140
-
141
- width, height = get_terminal_size()
142
- selected = 0
143
- scroll = 0
144
- list_height = height - 5
145
- mode = 'list'
146
- preview_scroll = 0
147
- sort_mode = 'time' # time, status, npc
148
- current_filter = status_filter
149
-
150
- def sort_memories(mems, sort_mode):
151
- if sort_mode == 'time':
152
- return sorted(mems, key=lambda x: x.get('timestamp') or '', reverse=True)
153
- elif sort_mode == 'status':
154
- return sorted(mems, key=lambda x: (x.get('status') or '', x.get('timestamp') or ''), reverse=True)
155
- elif sort_mode == 'npc':
156
- return sorted(mems, key=lambda x: (x.get('npc') or '', x.get('timestamp') or ''), reverse=True)
157
- return mems
158
-
159
- def filter_memories(mems, filter_status):
160
- if filter_status == 'all':
161
- return mems
162
- return [m for m in mems if m.get('status') == filter_status]
163
-
164
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
165
-
166
- fd = sys.stdin.fileno()
167
- old_settings = termios.tcgetattr(fd)
168
-
169
- try:
170
- tty.setcbreak(fd)
171
- sys.stdout.write('\033[?25l')
172
- sys.stdout.write('\033[2J\033[H')
173
-
174
- while True:
175
- width, height = get_terminal_size()
176
- list_height = height - 5
177
-
178
- if mode == 'list':
179
- if selected < scroll:
180
- scroll = selected
181
- elif selected >= scroll + list_height:
182
- scroll = selected - list_height + 1
183
-
184
- sys.stdout.write('\033[H')
185
-
186
- # Header
187
- if mode == 'list':
188
- sort_ind = {'time': '1', 'status': '2', 'npc': '3'}[sort_mode]
189
- header = f" MEM SEARCH ({len(display_memories)} results): '{query}' [sort:{sort_mode}({sort_ind}) filter:{current_filter}] "
190
- else:
191
- header = f" PREVIEW MEMORY "
192
- sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
193
-
194
- # Column headers
195
- if mode == 'list':
196
- col_header = f' {"STATUS":<10} {"TIMESTAMP":<14} {"NPC":<12} {"CONTENT":<40}'
197
- sys.stdout.write(f'\033[90m{col_header[:width]}\033[0m\n')
198
- else:
199
- sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
200
-
201
- if mode == 'list':
202
- for i in range(list_height):
203
- idx = scroll + i
204
- sys.stdout.write(f'\033[{3+i};1H\033[K')
205
- if idx >= len(display_memories):
206
- continue
207
-
208
- m = display_memories[idx]
209
- status = (m.get('status') or '?')[:10]
210
- ts = format_ts(m.get('timestamp'))
211
- npc_str = (m.get('npc') or 'default')[:12]
212
- content = (m.get('final_memory') or m.get('initial_memory') or m.get('content', ''))
213
- content = str(content)[:50].replace('\n', ' ')
214
-
215
- # Color by status
216
- if m.get('status') == 'approved':
217
- status_color = '\033[32m' # green
218
- elif m.get('status') == 'pending':
219
- status_color = '\033[33m' # yellow
220
- elif m.get('status') == 'rejected':
221
- status_color = '\033[31m' # red
222
- else:
223
- status_color = '\033[90m' # gray
224
-
225
- line = f" {status_color}{status:<10}\033[0m {ts:<14} {npc_str:<12} {content}"
226
- line = line[:width+15]
227
-
228
- if idx == selected:
229
- sys.stdout.write(f'\033[7;1m>{line}\033[0m')
230
- else:
231
- sys.stdout.write(f' {line}')
232
-
233
- # Status bar
234
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
235
- sel = display_memories[selected] if display_memories else {}
236
- mem_id = sel.get('id', '') or sel.get('memory_id', '')
237
- sys.stdout.write(f'\033[{height-1};1H\033[K ID: {mem_id}'.ljust(width))
238
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav 1/2/3:Sort f:Filter p:Preview a:Approve x:Reject q:Quit [{selected+1}/{len(display_memories)}] \033[0m')
239
-
240
- else: # preview mode
241
- sel = display_memories[selected]
242
- content = sel.get('final_memory') or sel.get('initial_memory') or sel.get('content', '')
243
- content_lines = str(content).split('\n')
244
-
245
- # Add metadata at top
246
- meta_lines = [
247
- f"Status: {sel.get('status', '')}",
248
- f"Timestamp: {sel.get('timestamp', '')}",
249
- f"NPC: {sel.get('npc', '')}",
250
- f"Team: {sel.get('team', '')}",
251
- f"ID: {sel.get('id', '') or sel.get('memory_id', '')}",
252
- "─" * 40,
253
- ]
254
- all_lines = meta_lines + content_lines
255
-
256
- for i in range(list_height):
257
- idx = preview_scroll + i
258
- sys.stdout.write(f'\033[{3+i};1H\033[K')
259
- if idx < len(all_lines):
260
- sys.stdout.write(all_lines[idx][:width-1])
261
-
262
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
263
- sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(all_lines)} lines]')
264
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back a:Approve x:Reject q:Quit \033[0m')
265
-
266
- sys.stdout.flush()
267
-
268
- c = sys.stdin.read(1)
269
-
270
- if c == '\x1b':
271
- c2 = sys.stdin.read(1)
272
- if c2 == '[':
273
- c3 = sys.stdin.read(1)
274
- if c3 == 'A': # Up
275
- if mode == 'list' and selected > 0:
276
- selected -= 1
277
- elif mode == 'preview' and preview_scroll > 0:
278
- preview_scroll -= 1
279
- elif c3 == 'B': # Down
280
- if mode == 'list' and selected < len(display_memories) - 1:
281
- selected += 1
282
- elif mode == 'preview':
283
- sel = display_memories[selected]
284
- content = str(sel.get('final_memory') or sel.get('content', ''))
285
- all_lines = content.split('\n')
286
- if preview_scroll < max(0, len(all_lines) + 6 - list_height):
287
- preview_scroll += 1
288
- else:
289
- if mode == 'preview':
290
- mode = 'list'
291
- sys.stdout.write('\033[2J\033[H')
292
- else:
293
- context['output'] = "Cancelled."
294
- break
295
- continue
296
-
297
- if c == 'q' or c == '\x03':
298
- context['output'] = "Cancelled."
299
- break
300
- elif c == 'k':
301
- if mode == 'list' and selected > 0:
302
- selected -= 1
303
- elif mode == 'preview' and preview_scroll > 0:
304
- preview_scroll -= 1
305
- elif c == 'j':
306
- if mode == 'list' and selected < len(display_memories) - 1:
307
- selected += 1
308
- elif mode == 'preview':
309
- sel = display_memories[selected]
310
- content = str(sel.get('final_memory') or sel.get('content', ''))
311
- all_lines = content.split('\n')
312
- if preview_scroll < max(0, len(all_lines) + 6 - list_height):
313
- preview_scroll += 1
314
- elif c == '1':
315
- sort_mode = 'time'
316
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
317
- selected = 0
318
- scroll = 0
319
- elif c == '2':
320
- sort_mode = 'status'
321
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
322
- selected = 0
323
- scroll = 0
324
- elif c == '3':
325
- sort_mode = 'npc'
326
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
327
- selected = 0
328
- scroll = 0
329
- elif c == 'f' and mode == 'list':
330
- # Cycle through filters
331
- if current_filter == 'all':
332
- current_filter = 'pending'
333
- elif current_filter == 'pending':
334
- current_filter = 'approved'
335
- else:
336
- current_filter = 'all'
337
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
338
- selected = 0
339
- scroll = 0
340
- elif c == 'a' and display_memories:
341
- # Approve memory
342
- sel = display_memories[selected]
343
- mem_id = sel.get('id') or sel.get('memory_id')
344
- if mem_id:
345
- try:
346
- cmd_history.update_memory_status(mem_id, 'approved')
347
- sel['status'] = 'approved'
348
- # Update in original list too
349
- for m in memories:
350
- if (m.get('id') or m.get('memory_id')) == mem_id:
351
- m['status'] = 'approved'
352
- except Exception as e:
353
- pass
354
- elif c == 'x' and display_memories:
355
- # Reject memory
356
- sel = display_memories[selected]
357
- mem_id = sel.get('id') or sel.get('memory_id')
358
- if mem_id:
359
- try:
360
- cmd_history.update_memory_status(mem_id, 'rejected')
361
- sel['status'] = 'rejected'
362
- for m in memories:
363
- if (m.get('id') or m.get('memory_id')) == mem_id:
364
- m['status'] = 'rejected'
365
- except Exception as e:
366
- pass
367
- elif c == 'p' and mode == 'list' and display_memories:
368
- mode = 'preview'
369
- preview_scroll = 0
370
- sys.stdout.write('\033[2J\033[H')
371
- elif c == 'b' and mode == 'preview':
372
- mode = 'list'
373
- sys.stdout.write('\033[2J\033[H')
374
- elif c in ('\r', '\n') and display_memories:
375
- sel = display_memories[selected]
376
- content = sel.get('final_memory') or sel.get('content', '')
377
- context['output'] = f"Selected memory:\n\n{content}"
378
- break
379
-
380
- finally:
381
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
382
- sys.stdout.write('\033[?25h')
383
- sys.stdout.write('\033[2J\033[H')
384
- sys.stdout.flush()
385
-
386
- except Exception as e:
387
- import traceback
388
- context['output'] = "Memory search error: " + str(e) + "\n" + traceback.format_exc()