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
@@ -1,331 +0,0 @@
1
- jinx_name: jinxs
2
- description: Interactive browser for available jinxs
3
- inputs:
4
- - path: ""
5
- - text: "false"
6
-
7
- steps:
8
- - name: list_jinxs
9
- engine: python
10
- code: |
11
- import os
12
- import sys
13
- import tty
14
- import termios
15
- from pathlib import Path
16
- import yaml
17
-
18
- filter_path = context.get('path', '').strip()
19
- text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
20
-
21
- # Find jinxs directory from team or fallback
22
- jinxs_dir = None
23
- if hasattr(npc, 'team') and npc.team:
24
- if hasattr(npc.team, 'jinxs_dir') and npc.team.jinxs_dir:
25
- jinxs_dir = Path(npc.team.jinxs_dir)
26
- elif hasattr(npc.team, 'team_path') and npc.team.team_path:
27
- candidate = Path(npc.team.team_path) / "jinxs"
28
- if candidate.exists():
29
- jinxs_dir = candidate
30
-
31
- if not jinxs_dir:
32
- global_jinxs = Path.home() / ".npcsh" / "npc_team" / "jinxs"
33
- if global_jinxs.exists():
34
- jinxs_dir = global_jinxs
35
-
36
- if not jinxs_dir or not jinxs_dir.exists():
37
- context['output'] = "Error: Could not find jinxs directory"
38
- else:
39
- def get_jinx_info(jinx_path):
40
- try:
41
- with open(jinx_path, 'r') as f:
42
- content = f.read()
43
- header = content.split('steps:')[0] if 'steps:' in content else content
44
- data = yaml.safe_load(header)
45
- name = data.get('jinx_name', jinx_path.stem)
46
- desc = data.get('description', 'No description')
47
- inputs = data.get('inputs', [])
48
- return name, desc, inputs
49
- except:
50
- return jinx_path.stem, 'No description', []
51
-
52
- def get_all_jinxs(base_path):
53
- jinxs = []
54
- folders = set()
55
- for root, dirs, files in os.walk(base_path):
56
- dirs[:] = [d for d in dirs if not d.startswith('.')]
57
- rel_path = Path(root).relative_to(base_path)
58
- folder = str(rel_path) if str(rel_path) != '.' else 'root'
59
- if folder != 'root':
60
- folders.add(folder.split('/')[0])
61
- for jf in files:
62
- if jf.endswith('.jinx'):
63
- jinx_path = Path(root) / jf
64
- name, desc, inputs = get_jinx_info(jinx_path)
65
- jinxs.append({
66
- 'name': name,
67
- 'description': desc,
68
- 'inputs': inputs,
69
- 'folder': folder,
70
- 'path': str(jinx_path)
71
- })
72
- return jinxs, sorted(folders)
73
-
74
- all_jinxs, folders = get_all_jinxs(jinxs_dir)
75
- folders = ['root'] + list(folders)
76
-
77
- if text_mode or not all_jinxs:
78
- # Text-only output
79
- output_lines = ["Available Jinxs", "=" * 40, ""]
80
- by_folder = {}
81
- for j in all_jinxs:
82
- f = j['folder']
83
- if f not in by_folder:
84
- by_folder[f] = []
85
- by_folder[f].append(j)
86
-
87
- for folder in sorted(by_folder.keys()):
88
- if folder == 'root':
89
- output_lines.append("Root:")
90
- else:
91
- output_lines.append(f"{folder}/:")
92
- for j in by_folder[folder]:
93
- output_lines.append(f" /{j['name']} - {j['description'][:50]}")
94
- output_lines.append("")
95
-
96
- output_lines.append("Use /jinxs path=<folder> for details")
97
- output_lines.append("Use text=false for interactive TUI")
98
- context['output'] = "\n".join(output_lines)
99
- else:
100
- # Interactive TUI mode
101
- def get_terminal_size():
102
- try:
103
- size = os.get_terminal_size()
104
- return size.columns, size.lines
105
- except:
106
- return 80, 24
107
-
108
- width, height = get_terminal_size()
109
- selected_folder = 0
110
- selected_jinx = 0
111
- jinx_scroll = 0
112
- list_height = height - 5
113
- mode = 'list' # list or preview
114
- preview_scroll = 0
115
- filter_text = ''
116
-
117
- def get_jinxs_in_folder(folder):
118
- if folder == 'root':
119
- return [j for j in all_jinxs if j['folder'] == 'root']
120
- return [j for j in all_jinxs if j['folder'].startswith(folder)]
121
-
122
- def filter_jinxs(jinxs, text):
123
- if not text:
124
- return jinxs
125
- text = text.lower()
126
- return [j for j in jinxs if text in j['name'].lower() or text in j['description'].lower()]
127
-
128
- current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
129
-
130
- fd = sys.stdin.fileno()
131
- old_settings = termios.tcgetattr(fd)
132
-
133
- try:
134
- tty.setcbreak(fd)
135
- sys.stdout.write('\033[?25l')
136
- sys.stdout.write('\033[2J\033[H')
137
-
138
- while True:
139
- width, height = get_terminal_size()
140
- list_height = height - 5
141
-
142
- if mode == 'list':
143
- if selected_jinx < jinx_scroll:
144
- jinx_scroll = selected_jinx
145
- elif selected_jinx >= jinx_scroll + list_height:
146
- jinx_scroll = selected_jinx - list_height + 1
147
-
148
- sys.stdout.write('\033[H')
149
-
150
- # Header
151
- if mode == 'list':
152
- f = folders[selected_folder] if folders else 'none'
153
- flt = f" filter:'{filter_text}'" if filter_text else ""
154
- header = f" JINXS ({len(current_jinxs)}): {f}{flt} "
155
- else:
156
- j = current_jinxs[selected_jinx] if current_jinxs else {}
157
- header = f" PREVIEW: /{j.get('name', '')} "
158
- sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
159
-
160
- # Folder bar
161
- folder_bar = ""
162
- for i, f in enumerate(folders[:8]):
163
- if i == selected_folder:
164
- folder_bar += f'\033[47;30m [{f[:8]}] \033[0m'
165
- else:
166
- folder_bar += f' {f[:8]} '
167
- if len(folders) > 8:
168
- folder_bar += f'...+{len(folders)-8}'
169
- sys.stdout.write(f'{folder_bar[:width]}\n')
170
-
171
- if mode == 'list':
172
- for i in range(list_height):
173
- idx = jinx_scroll + i
174
- sys.stdout.write(f'\033[{3+i};1H\033[K')
175
- if idx >= len(current_jinxs):
176
- continue
177
-
178
- j = current_jinxs[idx]
179
- name = j['name'][:20]
180
- desc = j['description'][:width-25]
181
-
182
- if idx == selected_jinx:
183
- sys.stdout.write(f'\033[47;30;1m>/{name:<20} {desc}\033[0m')
184
- else:
185
- sys.stdout.write(f' /{name:<20} {desc}')
186
-
187
- # Status bar
188
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
189
- j = current_jinxs[selected_jinx] if current_jinxs else {}
190
- path = j.get('path', '')[-50:] if j else ''
191
- sys.stdout.write(f'\033[{height-1};1H\033[K {path}'.ljust(width))
192
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m h/l:Folder j/k:Jinx p:Preview Enter:Run f:Filter q:Quit [{selected_jinx+1}/{len(current_jinxs)}] \033[0m')
193
-
194
- else: # preview mode
195
- j = current_jinxs[selected_jinx]
196
- preview_lines = [
197
- f"Name: /{j['name']}",
198
- "",
199
- f"Description:",
200
- f" {j['description']}",
201
- "",
202
- f"Path: {j['path']}",
203
- f"Folder: {j['folder']}",
204
- "",
205
- ]
206
-
207
- if j.get('inputs'):
208
- preview_lines.append("Inputs:")
209
- for inp in j['inputs']:
210
- if isinstance(inp, dict):
211
- for k, v in inp.items():
212
- default = f" (default: {v})" if v else ""
213
- preview_lines.append(f" - {k}{default}")
214
- else:
215
- preview_lines.append(f" - {inp}")
216
- preview_lines.append("")
217
-
218
- preview_lines.append("Usage:")
219
- usage = f" /{j['name']}"
220
- if j.get('inputs'):
221
- for inp in j['inputs'][:3]:
222
- if isinstance(inp, dict):
223
- for k, v in inp.items():
224
- usage += f" {k}=<value>"
225
- preview_lines.append(usage)
226
-
227
- for i in range(list_height):
228
- idx = preview_scroll + i
229
- sys.stdout.write(f'\033[{3+i};1H\033[K')
230
- if idx < len(preview_lines):
231
- sys.stdout.write(preview_lines[idx][:width-1])
232
-
233
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
234
- sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(preview_lines)} lines]')
235
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back Enter:Run q:Quit \033[0m')
236
-
237
- sys.stdout.flush()
238
-
239
- c = sys.stdin.read(1)
240
-
241
- if c == '\x1b':
242
- c2 = sys.stdin.read(1)
243
- if c2 == '[':
244
- c3 = sys.stdin.read(1)
245
- if c3 == 'A': # Up
246
- if mode == 'list' and selected_jinx > 0:
247
- selected_jinx -= 1
248
- elif mode == 'preview' and preview_scroll > 0:
249
- preview_scroll -= 1
250
- elif c3 == 'B': # Down
251
- if mode == 'list' and selected_jinx < len(current_jinxs) - 1:
252
- selected_jinx += 1
253
- elif mode == 'preview' and preview_scroll < 50:
254
- preview_scroll += 1
255
- elif c3 == 'C': # Right
256
- if mode == 'list' and selected_folder < len(folders) - 1:
257
- selected_folder += 1
258
- current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
259
- selected_jinx = 0
260
- jinx_scroll = 0
261
- elif c3 == 'D': # Left
262
- if mode == 'list' and selected_folder > 0:
263
- selected_folder -= 1
264
- current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
265
- selected_jinx = 0
266
- jinx_scroll = 0
267
- else:
268
- if mode == 'preview':
269
- mode = 'list'
270
- sys.stdout.write('\033[2J\033[H')
271
- else:
272
- context['output'] = "Cancelled."
273
- break
274
- continue
275
-
276
- if c == 'q' or c == '\x03':
277
- context['output'] = "Cancelled."
278
- break
279
- elif c == 'k':
280
- if mode == 'list' and selected_jinx > 0:
281
- selected_jinx -= 1
282
- elif mode == 'preview' and preview_scroll > 0:
283
- preview_scroll -= 1
284
- elif c == 'j':
285
- if mode == 'list' and selected_jinx < len(current_jinxs) - 1:
286
- selected_jinx += 1
287
- elif mode == 'preview' and preview_scroll < 50:
288
- preview_scroll += 1
289
- elif c == 'h' and mode == 'list':
290
- if selected_folder > 0:
291
- selected_folder -= 1
292
- current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
293
- selected_jinx = 0
294
- jinx_scroll = 0
295
- elif c == 'l' and mode == 'list':
296
- if selected_folder < len(folders) - 1:
297
- selected_folder += 1
298
- current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
299
- selected_jinx = 0
300
- jinx_scroll = 0
301
- elif c == 'f' and mode == 'list':
302
- # Toggle filter - cycle through common filters or clear
303
- if not filter_text:
304
- filter_text = 'search'
305
- elif filter_text == 'search':
306
- filter_text = 'core'
307
- elif filter_text == 'core':
308
- filter_text = 'browser'
309
- else:
310
- filter_text = ''
311
- current_jinxs = filter_jinxs(get_jinxs_in_folder(folders[selected_folder]), filter_text)
312
- selected_jinx = 0
313
- jinx_scroll = 0
314
- elif c == 'p' and mode == 'list' and current_jinxs:
315
- mode = 'preview'
316
- preview_scroll = 0
317
- sys.stdout.write('\033[2J\033[H')
318
- elif c == 'b' and mode == 'preview':
319
- mode = 'list'
320
- sys.stdout.write('\033[2J\033[H')
321
- elif c in ('\r', '\n') and current_jinxs:
322
- j = current_jinxs[selected_jinx]
323
- context['output'] = f"Run: /{j['name']}\n\nDescription: {j['description']}"
324
- context['selected_jinx'] = j
325
- break
326
-
327
- finally:
328
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
329
- sys.stdout.write('\033[?25h')
330
- sys.stdout.write('\033[2J\033[H')
331
- sys.stdout.flush()
@@ -1,141 +0,0 @@
1
- jinx_name: nql
2
- description: "Run NPC-SQL models with AI-powered transformations. Supports cron scheduling."
3
- inputs:
4
- - models_dir: "~/.npcsh/npc_team/models"
5
- - db: "~/npcsh_history.db"
6
- - model: ""
7
- - schema: ""
8
- - show: ""
9
- - cron: ""
10
- - install_cron: ""
11
-
12
- steps:
13
- - name: run_nql
14
- engine: python
15
- code: |
16
- import os
17
- import sys
18
- from pathlib import Path
19
-
20
- models_dir = context.get('models_dir') or '~/.npcsh/npc_team/models'
21
- db_path = context.get('db') or '~/npcsh_history.db'
22
- model_name = context.get('model') or ''
23
- schema = context.get('schema') or ''
24
- list_models = context.get('show') or ''
25
- cron_expr = context.get('cron') or ''
26
- install_cron = context.get('install_cron') or ''
27
-
28
- models_dir = os.path.expanduser(models_dir)
29
- db_path = os.path.expanduser(db_path)
30
-
31
- # Find NPC team directory
32
- npc_dir = None
33
- if os.path.exists('./npc_team'):
34
- npc_dir = './npc_team'
35
- elif os.path.exists(os.path.expanduser('~/.npcsh/npc_team')):
36
- npc_dir = os.path.expanduser('~/.npcsh/npc_team')
37
-
38
- # Import npcsql
39
- try:
40
- from npcpy.sql.npcsql import ModelCompiler, SQLModel
41
- except ImportError:
42
- output = "Error: npcpy.sql.npcsql not found. Install npcpy first."
43
- raise SystemExit(1)
44
-
45
- # List models mode
46
- if list_models:
47
- if not os.path.exists(models_dir):
48
- output = "No models directory found at " + models_dir
49
- else:
50
- models_path = Path(models_dir)
51
- sql_files = list(models_path.glob("**/*.sql"))
52
- if not sql_files:
53
- output = "No .sql models found in " + models_dir
54
- else:
55
- lines = ["Available NQL models in " + models_dir + ":", ""]
56
- for f in sorted(sql_files):
57
- rel = f.relative_to(models_path)
58
- # Check for nql functions
59
- with open(f, 'r') as fh:
60
- content = fh.read()
61
- has_nql = "nql." in content
62
- marker = " [NQL]" if has_nql else ""
63
- lines.append(" " + str(rel) + marker)
64
- output = "\n".join(lines)
65
-
66
- # Install cron mode
67
- elif install_cron:
68
- import subprocess
69
- # Parse cron expression and model
70
- parts = install_cron.split()
71
- if len(parts) < 5:
72
- output = "Error: cron expression must have 5 fields (min hour day month weekday)"
73
- else:
74
- cron_time = " ".join(parts[:5])
75
- cron_model = parts[5] if len(parts) > 5 else ""
76
-
77
- # Build command
78
- nql_cmd = "npc nql"
79
- if cron_model:
80
- nql_cmd += " model=" + cron_model
81
- nql_cmd += " models_dir=" + models_dir
82
- nql_cmd += " db=" + db_path
83
- if schema:
84
- nql_cmd += " schema=" + schema
85
-
86
- cron_line = cron_time + " " + nql_cmd
87
-
88
- # Get current crontab
89
- try:
90
- result = subprocess.run(['crontab', '-l'], capture_output=True, text=True)
91
- current = result.stdout if result.returncode == 0 else ""
92
- except:
93
- current = ""
94
-
95
- # Check if already installed
96
- if nql_cmd in current:
97
- output = "Cron job already exists for: " + nql_cmd
98
- else:
99
- # Add new cron line
100
- new_crontab = current.rstrip() + "\n" + cron_line + "\n"
101
- proc = subprocess.run(['crontab', '-'], input=new_crontab, text=True, capture_output=True)
102
- if proc.returncode == 0:
103
- output = "Installed cron job:\n " + cron_line
104
- else:
105
- output = "Failed to install cron: " + proc.stderr
106
-
107
- # Run models mode
108
- else:
109
- if not os.path.exists(models_dir):
110
- output = "Models directory not found: " + models_dir + "\nCreate it with: mkdir -p " + models_dir
111
- else:
112
- try:
113
- compiler = ModelCompiler(
114
- models_dir=models_dir,
115
- target_engine=db_path,
116
- npc_directory=npc_dir,
117
- target_schema=schema if schema else None
118
- )
119
-
120
- if model_name:
121
- # Run specific model
122
- compiler.discover_models()
123
- if model_name not in compiler.models:
124
- available = list(compiler.models.keys())
125
- output = "Model '" + model_name + "' not found. Available: " + str(available)
126
- else:
127
- print("Running model: " + model_name)
128
- df = compiler.execute_model(model_name)
129
- output = "Model '" + model_name + "' completed. Rows: " + str(len(df))
130
- else:
131
- # Run all models in dependency order
132
- results = compiler.run_all_models()
133
- lines = ["NQL run complete:", ""]
134
- for name, df in results.items():
135
- lines.append(" " + name + ": " + str(len(df)) + " rows")
136
- output = "\n".join(lines)
137
-
138
- except Exception as e:
139
- output = "NQL Error: " + str(e)
140
- import traceback
141
- traceback.print_exc()