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,412 +0,0 @@
1
- jinx_name: paper_search
2
- description: Search for academic papers with interactive TUI (Semantic Scholar + arXiv)
3
- inputs:
4
- - query: ""
5
- - limit: "20"
6
- - source: "all"
7
- - text: "false"
8
-
9
- steps:
10
- - name: search_papers
11
- engine: python
12
- code: |
13
- import os
14
- import sys
15
- import tty
16
- import termios
17
- import time
18
- import requests
19
- import urllib.request
20
- import urllib.parse
21
- import xml.etree.ElementTree as ET
22
- import webbrowser
23
- import subprocess
24
-
25
- query = context.get('query', '')
26
- limit = int(context.get('limit', 20))
27
- source = context.get('source', 'all').lower()
28
- text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
29
-
30
- if not query:
31
- lines = [
32
- "Usage: /paper_search <query>",
33
- "",
34
- "Options:",
35
- " limit - Max results per source (default 20)",
36
- " source - all (default), s2, arxiv",
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 year/citations/author",
42
- " s - Filter by source (all/S2/arXiv)",
43
- " y - Filter by year",
44
- " p - Preview abstract",
45
- " o - Open in browser",
46
- " i - Open in incognide",
47
- " d - Download paper info",
48
- " q/ESC - Quit",
49
- "",
50
- "Examples:",
51
- " /paper_search machine learning",
52
- " /paper_search neural networks source=arxiv",
53
- ]
54
- context['output'] = "\n".join(lines)
55
- else:
56
- all_results = []
57
-
58
- # Semantic Scholar
59
- if source in ['all', 's2']:
60
- api_key = os.environ.get('S2_API_KEY')
61
- if api_key:
62
- try:
63
- url = "https://api.semanticscholar.org/graph/v1/paper/search"
64
- headers = {"x-api-key": api_key}
65
- params = {"query": query, "limit": limit, "fields": "title,abstract,authors,year,citationCount,url,venue"}
66
- response = requests.get(url, headers=headers, params=params, timeout=30)
67
- response.raise_for_status()
68
- for paper in response.json().get('data', []):
69
- all_results.append({
70
- 'source': 'S2',
71
- 'title': paper.get('title', ''),
72
- 'year': paper.get('year'),
73
- 'citations': paper.get('citationCount', 0),
74
- 'authors': [a.get('name', '') for a in paper.get('authors', [])],
75
- 'abstract': paper.get('abstract', '') or '',
76
- 'url': paper.get('url', ''),
77
- 'venue': paper.get('venue', '')
78
- })
79
- except Exception as e:
80
- pass
81
-
82
- # arXiv
83
- if source in ['all', 'arxiv']:
84
- try:
85
- base_url = "http://export.arxiv.org/api/query"
86
- params = {"search_query": f"all:{query}", "max_results": limit}
87
- arxiv_url = f"{base_url}?{urllib.parse.urlencode(params)}"
88
- with urllib.request.urlopen(arxiv_url, timeout=30) as response:
89
- data = response.read().decode('utf-8')
90
- root = ET.fromstring(data)
91
- ns = {'atom': 'http://www.w3.org/2005/Atom'}
92
- for entry in root.findall('atom:entry', ns):
93
- all_results.append({
94
- 'source': 'arXiv',
95
- 'title': entry.find('atom:title', ns).text.strip().replace('\n', ' '),
96
- 'year': int(entry.find('atom:published', ns).text[:4]),
97
- 'citations': None,
98
- 'authors': [a.find('atom:name', ns).text for a in entry.findall('atom:author', ns)],
99
- 'abstract': entry.find('atom:summary', ns).text.strip(),
100
- 'url': entry.find('atom:id', ns).text,
101
- 'venue': 'arXiv'
102
- })
103
- except Exception as e:
104
- pass
105
-
106
- if not all_results:
107
- context['output'] = f"No papers found for: {query}"
108
- elif text_mode:
109
- # Text-only output
110
- results = []
111
- for i, paper in enumerate(all_results[:limit], 1):
112
- authors = ', '.join(paper['authors'][:3])
113
- if len(paper['authors']) > 3:
114
- authors += ' et al.'
115
- year = paper.get('year', '?')
116
- citations = f", {paper['citations']} citations" if paper.get('citations') else ""
117
-
118
- results.append(f"{i}. [{paper['source']}] {paper['title']} ({year}{citations})")
119
- results.append(f" Authors: {authors}")
120
- if paper['abstract']:
121
- results.append(f" Abstract: {paper['abstract'][:200]}...")
122
- results.append(f" URL: {paper['url']}")
123
- results.append("")
124
-
125
- context['output'] = f"Found {len(all_results)} papers:\n\n" + "\n".join(results)
126
- context['papers'] = all_results
127
- else:
128
- # Interactive TUI mode
129
- def get_terminal_size():
130
- try:
131
- size = os.get_terminal_size()
132
- return size.columns, size.lines
133
- except:
134
- return 80, 24
135
-
136
- width, height = get_terminal_size()
137
- selected = 0
138
- scroll = 0
139
- list_height = height - 5
140
- mode = 'list'
141
- preview_scroll = 0
142
- sort_mode = 'relevance'
143
- source_filter = 'all'
144
- year_filter = None
145
-
146
- def sort_papers(papers, sort_mode):
147
- if sort_mode == 'year':
148
- return sorted(papers, key=lambda x: x.get('year') or 0, reverse=True)
149
- elif sort_mode == 'citations':
150
- return sorted(papers, key=lambda x: x.get('citations') or 0, reverse=True)
151
- elif sort_mode == 'author':
152
- return sorted(papers, key=lambda x: (x.get('authors', [''])[0] if x.get('authors') else ''))
153
- return papers
154
-
155
- def filter_papers(papers, source_filter, year_filter):
156
- result = papers
157
- if source_filter != 'all':
158
- result = [p for p in result if p.get('source', '').lower() == source_filter.lower()]
159
- if year_filter:
160
- result = [p for p in result if p.get('year') and p.get('year') >= year_filter]
161
- return result
162
-
163
- display_papers = filter_papers(sort_papers(all_results, sort_mode), source_filter, year_filter)
164
-
165
- fd = sys.stdin.fileno()
166
- old_settings = termios.tcgetattr(fd)
167
-
168
- try:
169
- tty.setcbreak(fd)
170
- sys.stdout.write('\033[?25l')
171
- sys.stdout.write('\033[2J\033[H')
172
-
173
- while True:
174
- width, height = get_terminal_size()
175
- list_height = height - 5
176
-
177
- if mode == 'list':
178
- if selected < scroll:
179
- scroll = selected
180
- elif selected >= scroll + list_height:
181
- scroll = selected - list_height + 1
182
-
183
- sys.stdout.write('\033[H')
184
-
185
- # Header
186
- if mode == 'list':
187
- sort_ind = {'relevance': '0', 'year': '1', 'citations': '2', 'author': '3'}[sort_mode]
188
- yr = f" year>={year_filter}" if year_filter else ""
189
- header = f" PAPERS ({len(display_papers)}): '{query}' [sort:{sort_mode}({sort_ind}) src:{source_filter}{yr}] "
190
- else:
191
- header = f" PREVIEW: {display_papers[selected].get('title', '')[:width-12]} "
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' {"SRC":<6} {"YEAR":<5} {"CITE":<5} {"AUTHORS":<22} {"TITLE":<50}'
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_papers):
206
- continue
207
-
208
- p = display_papers[idx]
209
- src = p.get('source', '?')[:6]
210
- year = str(p.get('year') or '?')[:5]
211
- citations = str(p.get('citations') or '-')[:5]
212
- authors = ', '.join([a[:12] for a in p.get('authors', [])[:2]])
213
- if len(p.get('authors', [])) > 2:
214
- authors += '..'
215
- authors = authors[:22]
216
- title = (p.get('title') or '')[:55].replace('\n', ' ')
217
-
218
- # Color by source
219
- if p.get('source') == 'S2':
220
- src_color = '\033[34m' # blue
221
- else:
222
- src_color = '\033[33m' # orange/yellow
223
-
224
- line = f" {src_color}{src:<6}\033[0m {year:<5} {citations:<5} {authors:<22} {title}"
225
- line = line[:width+10]
226
-
227
- if idx == selected:
228
- sys.stdout.write(f'\033[7;1m>{line}\033[0m')
229
- else:
230
- sys.stdout.write(f' {line}')
231
-
232
- # Status bar
233
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
234
- sel = display_papers[selected] if display_papers else {}
235
- venue = (sel.get('venue') or '')[:40]
236
- sys.stdout.write(f'\033[{height-1};1H\033[K {venue}'.ljust(width))
237
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav 0/1/2/3:Sort s:Source y:Year p:Preview o:Open d:DL q:Quit [{selected+1}/{len(display_papers)}] \033[0m')
238
-
239
- else: # preview mode
240
- sel = display_papers[selected]
241
- abstract = sel.get('abstract') or 'No abstract available'
242
-
243
- preview_lines = [
244
- f"Title: {sel.get('title', '')}",
245
- "",
246
- f"Source: {sel.get('source', '')}",
247
- f"Year: {sel.get('year', '?')}",
248
- f"Citations: {sel.get('citations') or 'N/A'}",
249
- f"Venue: {sel.get('venue', '')}",
250
- "",
251
- "Authors:",
252
- ]
253
- for a in sel.get('authors', []):
254
- preview_lines.append(f" - {a}")
255
- preview_lines.append("")
256
- preview_lines.append("Abstract:")
257
-
258
- # Word wrap abstract
259
- words = abstract.split()
260
- line = ""
261
- for w in words:
262
- if len(line) + len(w) + 1 > width - 4:
263
- preview_lines.append(f" {line}")
264
- line = w
265
- else:
266
- line = f"{line} {w}" if line else w
267
- if line:
268
- preview_lines.append(f" {line}")
269
-
270
- preview_lines.append("")
271
- preview_lines.append(f"URL: {sel.get('url', '')}")
272
-
273
- for i in range(list_height):
274
- idx = preview_scroll + i
275
- sys.stdout.write(f'\033[{3+i};1H\033[K')
276
- if idx < len(preview_lines):
277
- sys.stdout.write(preview_lines[idx][:width-1])
278
-
279
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
280
- sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(preview_lines)} lines]')
281
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back o:Open d:Download q:Quit \033[0m')
282
-
283
- sys.stdout.flush()
284
-
285
- c = sys.stdin.read(1)
286
-
287
- if c == '\x1b':
288
- c2 = sys.stdin.read(1)
289
- if c2 == '[':
290
- c3 = sys.stdin.read(1)
291
- if c3 == 'A':
292
- if mode == 'list' and selected > 0:
293
- selected -= 1
294
- elif mode == 'preview' and preview_scroll > 0:
295
- preview_scroll -= 1
296
- elif c3 == 'B':
297
- if mode == 'list' and selected < len(display_papers) - 1:
298
- selected += 1
299
- elif mode == 'preview' and preview_scroll < 100:
300
- preview_scroll += 1
301
- else:
302
- if mode == 'preview':
303
- mode = 'list'
304
- sys.stdout.write('\033[2J\033[H')
305
- else:
306
- context['output'] = "Cancelled."
307
- break
308
- continue
309
-
310
- if c == 'q' or c == '\x03':
311
- context['output'] = "Cancelled."
312
- break
313
- elif c == 'k':
314
- if mode == 'list' and selected > 0:
315
- selected -= 1
316
- elif mode == 'preview' and preview_scroll > 0:
317
- preview_scroll -= 1
318
- elif c == 'j':
319
- if mode == 'list' and selected < len(display_papers) - 1:
320
- selected += 1
321
- elif mode == 'preview' and preview_scroll < 100:
322
- preview_scroll += 1
323
- elif c == '0':
324
- sort_mode = 'relevance'
325
- display_papers = filter_papers(sort_papers(all_results, sort_mode), source_filter, year_filter)
326
- selected = 0
327
- scroll = 0
328
- elif c == '1':
329
- sort_mode = 'year'
330
- display_papers = filter_papers(sort_papers(all_results, sort_mode), source_filter, year_filter)
331
- selected = 0
332
- scroll = 0
333
- elif c == '2':
334
- sort_mode = 'citations'
335
- display_papers = filter_papers(sort_papers(all_results, sort_mode), source_filter, year_filter)
336
- selected = 0
337
- scroll = 0
338
- elif c == '3':
339
- sort_mode = 'author'
340
- display_papers = filter_papers(sort_papers(all_results, sort_mode), source_filter, year_filter)
341
- selected = 0
342
- scroll = 0
343
- elif c == 's' and mode == 'list':
344
- # Cycle through source filters
345
- if source_filter == 'all':
346
- source_filter = 's2'
347
- elif source_filter == 's2':
348
- source_filter = 'arxiv'
349
- else:
350
- source_filter = 'all'
351
- display_papers = filter_papers(sort_papers(all_results, sort_mode), source_filter, year_filter)
352
- selected = 0
353
- scroll = 0
354
- elif c == 'y' and mode == 'list':
355
- import datetime
356
- current_year = datetime.datetime.now().year
357
- if year_filter is None:
358
- year_filter = current_year - 1
359
- elif year_filter == current_year - 1:
360
- year_filter = current_year - 3
361
- elif year_filter == current_year - 3:
362
- year_filter = current_year - 5
363
- else:
364
- year_filter = None
365
- display_papers = filter_papers(sort_papers(all_results, sort_mode), source_filter, year_filter)
366
- selected = 0
367
- scroll = 0
368
- elif c == 'p' and mode == 'list' and display_papers:
369
- mode = 'preview'
370
- preview_scroll = 0
371
- sys.stdout.write('\033[2J\033[H')
372
- elif c == 'b' and mode == 'preview':
373
- mode = 'list'
374
- sys.stdout.write('\033[2J\033[H')
375
- elif c == 'o' and display_papers:
376
- paper_url = display_papers[selected].get('url', '')
377
- if paper_url:
378
- webbrowser.open(paper_url)
379
- elif c == 'i' and display_papers:
380
- paper_url = display_papers[selected].get('url', '')
381
- if paper_url:
382
- try:
383
- subprocess.run(['npcsh', '-c', f'/navigate url={paper_url}'], check=False, capture_output=True)
384
- except:
385
- webbrowser.open(paper_url)
386
- elif c == 'd' and display_papers:
387
- sel = display_papers[selected]
388
- title = sel.get('title', 'paper')
389
- safe_title = "".join(ch if ch.isalnum() or ch in ' -_' else '_' for ch in title)[:50]
390
- filename = f"{safe_title}.txt"
391
- with open(filename, 'w') as f:
392
- f.write(f"Title: {sel.get('title', '')}\n")
393
- f.write(f"Source: {sel.get('source', '')}\n")
394
- f.write(f"Year: {sel.get('year', '')}\n")
395
- f.write(f"Citations: {sel.get('citations', 'N/A')}\n")
396
- f.write(f"Venue: {sel.get('venue', '')}\n")
397
- f.write(f"Authors: {', '.join(sel.get('authors', []))}\n")
398
- f.write(f"URL: {sel.get('url', '')}\n")
399
- f.write(f"\nAbstract:\n{sel.get('abstract', '')}\n")
400
- context['output'] = f"Saved to: {filename}"
401
- break
402
- elif c in ('\r', '\n') and display_papers:
403
- sel = display_papers[selected]
404
- context['output'] = f"Selected: {sel.get('title', '')}\nURL: {sel.get('url', '')}"
405
- context['selected_paper'] = sel
406
- break
407
-
408
- finally:
409
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
410
- sys.stdout.write('\033[?25h')
411
- sys.stdout.write('\033[2J\033[H')
412
- sys.stdout.flush()