npcsh 1.1.19__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 (173) hide show
  1. npcsh/_state.py +16 -78
  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 +18 -7
  5. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +18 -7
  6. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +20 -9
  7. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +53 -15
  8. npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
  9. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +393 -317
  10. npcsh/npc_team/jinxs/lib/utils/models.jinx +343 -0
  11. npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +8 -7
  12. npcsh/npc_team/jinxs/modes/alicanto.jinx +1573 -296
  13. npcsh/npc_team/jinxs/modes/arxiv.jinx +6 -6
  14. npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
  15. npcsh/npc_team/jinxs/modes/corca.jinx +4 -4
  16. npcsh/npc_team/jinxs/modes/git.jinx +795 -0
  17. npcsh/npc_team/jinxs/modes/guac.jinx +4 -4
  18. npcsh/npc_team/jinxs/modes/kg.jinx +941 -0
  19. npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
  20. npcsh/npc_team/jinxs/modes/nql.jinx +460 -0
  21. npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
  22. npcsh/npc_team/jinxs/modes/plonk.jinx +490 -304
  23. npcsh/npc_team/jinxs/modes/pti.jinx +1 -1
  24. npcsh/npc_team/jinxs/modes/reattach.jinx +4 -4
  25. npcsh/npc_team/jinxs/modes/spool.jinx +4 -4
  26. npcsh/npc_team/jinxs/modes/team.jinx +504 -0
  27. npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
  28. npcsh/npc_team/jinxs/modes/wander.jinx +455 -182
  29. npcsh/npc_team/jinxs/modes/yap.jinx +10 -3
  30. npcsh/npcsh.py +112 -47
  31. npcsh/routes.py +12 -3
  32. npcsh/salmon_simulation.py +0 -0
  33. npcsh-1.1.21.data/data/npcsh/npc_team/alicanto.jinx +1633 -0
  34. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/arxiv.jinx +6 -6
  35. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
  36. npcsh-1.1.21.data/data/npcsh/npc_team/compress.jinx +428 -0
  37. npcsh-1.1.21.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  38. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.jinx +4 -4
  39. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/db_search.jinx +18 -7
  40. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/file_search.jinx +18 -7
  41. npcsh-1.1.21.data/data/npcsh/npc_team/git.jinx +795 -0
  42. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.jinx +4 -4
  43. npcsh-1.1.21.data/data/npcsh/npc_team/jinxs.jinx +407 -0
  44. npcsh-1.1.21.data/data/npcsh/npc_team/kg.jinx +941 -0
  45. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kg_search.jinx +20 -9
  46. npcsh-1.1.21.data/data/npcsh/npc_team/memories.jinx +414 -0
  47. npcsh-1.1.21.data/data/npcsh/npc_team/models.jinx +343 -0
  48. npcsh-1.1.21.data/data/npcsh/npc_team/nql.jinx +460 -0
  49. npcsh-1.1.21.data/data/npcsh/npc_team/papers.jinx +578 -0
  50. npcsh-1.1.21.data/data/npcsh/npc_team/plonk.jinx +565 -0
  51. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/pti.jinx +1 -1
  52. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/reattach.jinx +4 -4
  53. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/setup.jinx +8 -7
  54. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.jinx +4 -4
  55. npcsh-1.1.21.data/data/npcsh/npc_team/team.jinx +504 -0
  56. npcsh-1.1.21.data/data/npcsh/npc_team/vixynt.jinx +388 -0
  57. npcsh-1.1.21.data/data/npcsh/npc_team/wander.jinx +728 -0
  58. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/web_search.jinx +53 -15
  59. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.jinx +10 -3
  60. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/METADATA +2 -2
  61. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/RECORD +147 -148
  62. npcsh-1.1.21.dist-info/entry_points.txt +11 -0
  63. npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -299
  64. npcsh/npc_team/jinxs/bin/memories.jinx +0 -316
  65. npcsh/npc_team/jinxs/bin/nql.jinx +0 -141
  66. npcsh/npc_team/jinxs/bin/team_tui.jinx +0 -327
  67. npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
  68. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
  69. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
  70. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
  71. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
  72. npcsh/npc_team/plonkjr.npc +0 -23
  73. npcsh-1.1.19.data/data/npcsh/npc_team/alicanto.jinx +0 -356
  74. npcsh-1.1.19.data/data/npcsh/npc_team/compress.jinx +0 -140
  75. npcsh-1.1.19.data/data/npcsh/npc_team/config_tui.jinx +0 -299
  76. npcsh-1.1.19.data/data/npcsh/npc_team/jinxs.jinx +0 -331
  77. npcsh-1.1.19.data/data/npcsh/npc_team/mem_review.jinx +0 -73
  78. npcsh-1.1.19.data/data/npcsh/npc_team/mem_search.jinx +0 -388
  79. npcsh-1.1.19.data/data/npcsh/npc_team/memories.jinx +0 -316
  80. npcsh-1.1.19.data/data/npcsh/npc_team/nql.jinx +0 -141
  81. npcsh-1.1.19.data/data/npcsh/npc_team/paper_search.jinx +0 -412
  82. npcsh-1.1.19.data/data/npcsh/npc_team/plonk.jinx +0 -379
  83. npcsh-1.1.19.data/data/npcsh/npc_team/plonkjr.npc +0 -23
  84. npcsh-1.1.19.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
  85. npcsh-1.1.19.data/data/npcsh/npc_team/team_tui.jinx +0 -327
  86. npcsh-1.1.19.data/data/npcsh/npc_team/vixynt.jinx +0 -122
  87. npcsh-1.1.19.data/data/npcsh/npc_team/wander.jinx +0 -455
  88. npcsh-1.1.19.dist-info/entry_points.txt +0 -22
  89. /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
  90. /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
  91. /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
  92. /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
  93. /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
  94. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  95. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  96. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.png +0 -0
  97. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  98. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  99. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/build.jinx +0 -0
  100. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/chat.jinx +0 -0
  101. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/click.jinx +0 -0
  102. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  103. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  104. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  105. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  106. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/compile.jinx +0 -0
  107. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  108. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/convene.jinx +0 -0
  109. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.npc +0 -0
  110. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.png +0 -0
  111. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca_example.png +0 -0
  112. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  113. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  114. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  115. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic.npc +0 -0
  116. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic4.png +0 -0
  117. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.npc +0 -0
  118. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.png +0 -0
  119. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/help.jinx +0 -0
  120. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  121. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/init.jinx +0 -0
  122. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  123. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  124. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  125. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  126. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  127. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  128. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  129. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/notify.jinx +0 -0
  130. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  131. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  132. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  133. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  134. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/ots.jinx +0 -0
  135. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/paste.jinx +0 -0
  136. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.npc +0 -0
  137. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.png +0 -0
  138. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  139. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/python.jinx +0 -0
  140. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  141. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/roll.jinx +0 -0
  142. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  143. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sample.jinx +0 -0
  144. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  145. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/search.jinx +0 -0
  146. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  147. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/serve.jinx +0 -0
  148. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/set.jinx +0 -0
  149. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sh.jinx +0 -0
  150. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/shh.jinx +0 -0
  151. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  152. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.png +0 -0
  153. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  154. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  155. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.png +0 -0
  156. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sql.jinx +0 -0
  157. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch.jinx +0 -0
  158. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  159. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  160. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switches.jinx +0 -0
  161. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sync.jinx +0 -0
  162. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  163. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  164. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  165. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/usage.jinx +0 -0
  166. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  167. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/wait.jinx +0 -0
  168. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  169. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.png +0 -0
  170. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  171. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/WHEEL +0 -0
  172. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/licenses/LICENSE +0 -0
  173. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/top_level.txt +0 -0
@@ -1,386 +0,0 @@
1
- jinx_name: semantic_scholar
2
- description: Search Semantic Scholar with interactive TUI. Requires S2_API_KEY env var.
3
- inputs:
4
- - query: ""
5
- - limit: "20"
6
- - text: "false"
7
-
8
- steps:
9
- - name: search_s2
10
- engine: python
11
- code: |
12
- import os
13
- import sys
14
- import tty
15
- import termios
16
- import time
17
- import requests
18
- import webbrowser
19
- import subprocess
20
-
21
- query = context.get('query', '')
22
- limit = int(context.get('limit', 20))
23
- text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
24
-
25
- if not query:
26
- lines = [
27
- "Usage: /semantic_scholar <query>",
28
- "",
29
- "Options:",
30
- " limit - Max results (default 20)",
31
- " text - Text-only output, no TUI (true/false)",
32
- "",
33
- "TUI Controls:",
34
- " j/k or arrows - Navigate",
35
- " 1/2/3 - Sort by year/citations/author",
36
- " y - Filter by year",
37
- " p - Preview abstract/TL;DR",
38
- " o - Open in browser",
39
- " i - Open in incognide",
40
- " d - Download paper info",
41
- " q/ESC - Quit",
42
- "",
43
- "Examples:",
44
- " /semantic_scholar machine learning",
45
- " /semantic_scholar neural networks limit=50",
46
- ]
47
- context['output'] = "\n".join(lines)
48
- else:
49
- api_key = os.environ.get('S2_API_KEY')
50
- if not api_key:
51
- context['output'] = "Error: S2_API_KEY environment variable not set.\nGet one at https://www.semanticscholar.org/product/api"
52
- else:
53
- url = "https://api.semanticscholar.org/graph/v1/paper/search"
54
- headers = {"x-api-key": api_key}
55
- params = {
56
- "query": query,
57
- "limit": limit,
58
- "fields": "title,abstract,authors,year,citationCount,url,tldr,venue,openAccessPdf"
59
- }
60
-
61
- try:
62
- response = requests.get(url, headers=headers, params=params, timeout=30)
63
- response.raise_for_status()
64
- papers = response.json().get('data', [])
65
-
66
- if not papers:
67
- context['output'] = f"No papers found for: {query}"
68
- elif text_mode:
69
- # Text-only output
70
- results = []
71
- for i, paper in enumerate(papers, 1):
72
- title = paper.get('title', 'No title')
73
- year = paper.get('year', '?')
74
- citations = paper.get('citationCount', 0)
75
- authors = ', '.join([a.get('name', '') for a in paper.get('authors', [])[:3]])
76
- if len(paper.get('authors', [])) > 3:
77
- authors += ' et al.'
78
- abstract = paper.get('abstract', '')[:200] + '...' if paper.get('abstract') else 'No abstract'
79
- tldr = paper.get('tldr', {}).get('text', '') if paper.get('tldr') else ''
80
- paper_url = paper.get('url', '')
81
-
82
- results.append(f"{i}. {title} ({year})")
83
- results.append(f" Authors: {authors}")
84
- results.append(f" Citations: {citations}")
85
- if tldr:
86
- results.append(f" TL;DR: {tldr}")
87
- else:
88
- results.append(f" Abstract: {abstract}")
89
- results.append(f" URL: {paper_url}")
90
- results.append("")
91
-
92
- context['output'] = f"Found {len(papers)} papers:\n\n" + "\n".join(results)
93
- context['papers'] = papers
94
- else:
95
- # Interactive TUI mode
96
- def get_terminal_size():
97
- try:
98
- size = os.get_terminal_size()
99
- return size.columns, size.lines
100
- except:
101
- return 80, 24
102
-
103
- width, height = get_terminal_size()
104
- selected = 0
105
- scroll = 0
106
- list_height = height - 5
107
- mode = 'list'
108
- preview_scroll = 0
109
- sort_mode = 'relevance' # relevance, year, citations, author
110
- year_filter = None
111
-
112
- def sort_papers(papers, sort_mode):
113
- if sort_mode == 'year':
114
- return sorted(papers, key=lambda x: x.get('year') or 0, reverse=True)
115
- elif sort_mode == 'citations':
116
- return sorted(papers, key=lambda x: x.get('citationCount') or 0, reverse=True)
117
- elif sort_mode == 'author':
118
- return sorted(papers, key=lambda x: (x.get('authors', [{}])[0].get('name', '') if x.get('authors') else ''))
119
- return papers # relevance = original order
120
-
121
- def filter_papers(papers, year_filter):
122
- if not year_filter:
123
- return papers
124
- return [p for p in papers if p.get('year') and p.get('year') >= year_filter]
125
-
126
- display_papers = filter_papers(sort_papers(papers, sort_mode), year_filter)
127
-
128
- fd = sys.stdin.fileno()
129
- old_settings = termios.tcgetattr(fd)
130
-
131
- try:
132
- tty.setcbreak(fd)
133
- sys.stdout.write('\033[?25l')
134
- sys.stdout.write('\033[2J\033[H')
135
-
136
- while True:
137
- width, height = get_terminal_size()
138
- list_height = height - 5
139
-
140
- if mode == 'list':
141
- if selected < scroll:
142
- scroll = selected
143
- elif selected >= scroll + list_height:
144
- scroll = selected - list_height + 1
145
-
146
- sys.stdout.write('\033[H')
147
-
148
- # Header
149
- if mode == 'list':
150
- sort_ind = {'relevance': '0', 'year': '1', 'citations': '2', 'author': '3'}[sort_mode]
151
- yr = f" year>={year_filter}" if year_filter else ""
152
- header = f" SEMANTIC SCHOLAR ({len(display_papers)} papers): '{query}' [sort:{sort_mode}({sort_ind}){yr}] "
153
- else:
154
- header = f" PREVIEW: {display_papers[selected].get('title', '')[:width-12]} "
155
- sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
156
-
157
- # Column headers
158
- if mode == 'list':
159
- col_header = f' {"YEAR":<6} {"CITE":<6} {"AUTHORS":<25} {"TITLE":<50}'
160
- sys.stdout.write(f'\033[90m{col_header[:width]}\033[0m\n')
161
- else:
162
- sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
163
-
164
- if mode == 'list':
165
- for i in range(list_height):
166
- idx = scroll + i
167
- sys.stdout.write(f'\033[{3+i};1H\033[K')
168
- if idx >= len(display_papers):
169
- continue
170
-
171
- p = display_papers[idx]
172
- year = str(p.get('year') or '?')[:6]
173
- citations = str(p.get('citationCount') or 0)[:6]
174
- authors = ', '.join([a.get('name', '')[:15] for a in p.get('authors', [])[:2]])
175
- if len(p.get('authors', [])) > 2:
176
- authors += ' et al.'
177
- authors = authors[:25]
178
- title = (p.get('title') or '')[:60].replace('\n', ' ')
179
-
180
- line = f" {year:<6} {citations:<6} {authors:<25} {title}"
181
- line = line[:width-1]
182
-
183
- if idx == selected:
184
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
185
- else:
186
- sys.stdout.write(f' {line}')
187
-
188
- # Status bar
189
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
190
- sel = display_papers[selected] if display_papers else {}
191
- venue = (sel.get('venue') or '')[:30]
192
- has_pdf = 'PDF' if sel.get('openAccessPdf') else ''
193
- sys.stdout.write(f'\033[{height-1};1H\033[K {venue} {has_pdf}'.ljust(width))
194
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav 0/1/2/3:Sort y:Year p:Preview o:Open i:Incog d:Download q:Quit [{selected+1}/{len(display_papers)}] \033[0m')
195
-
196
- else: # preview mode
197
- sel = display_papers[selected]
198
- tldr = sel.get('tldr', {}).get('text', '') if sel.get('tldr') else ''
199
- abstract = sel.get('abstract') or 'No abstract available'
200
-
201
- # Build preview lines
202
- preview_lines = [
203
- f"Title: {sel.get('title', '')}",
204
- "",
205
- f"Year: {sel.get('year', '?')}",
206
- f"Citations: {sel.get('citationCount', 0)}",
207
- f"Venue: {sel.get('venue', '')}",
208
- "",
209
- "Authors:",
210
- ]
211
- for a in sel.get('authors', []):
212
- preview_lines.append(f" - {a.get('name', '')}")
213
- preview_lines.append("")
214
-
215
- if tldr:
216
- preview_lines.append("TL;DR:")
217
- # Word wrap TL;DR
218
- words = tldr.split()
219
- line = ""
220
- for w in words:
221
- if len(line) + len(w) + 1 > width - 4:
222
- preview_lines.append(f" {line}")
223
- line = w
224
- else:
225
- line = f"{line} {w}" if line else w
226
- if line:
227
- preview_lines.append(f" {line}")
228
- preview_lines.append("")
229
-
230
- preview_lines.append("Abstract:")
231
- # Word wrap abstract
232
- words = abstract.split()
233
- line = ""
234
- for w in words:
235
- if len(line) + len(w) + 1 > width - 4:
236
- preview_lines.append(f" {line}")
237
- line = w
238
- else:
239
- line = f"{line} {w}" if line else w
240
- if line:
241
- preview_lines.append(f" {line}")
242
-
243
- preview_lines.append("")
244
- preview_lines.append(f"URL: {sel.get('url', '')}")
245
- if sel.get('openAccessPdf'):
246
- preview_lines.append(f"PDF: {sel.get('openAccessPdf', {}).get('url', '')}")
247
-
248
- for i in range(list_height):
249
- idx = preview_scroll + i
250
- sys.stdout.write(f'\033[{3+i};1H\033[K')
251
- if idx < len(preview_lines):
252
- sys.stdout.write(preview_lines[idx][:width-1])
253
-
254
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
255
- sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(preview_lines)} lines]')
256
- 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')
257
-
258
- sys.stdout.flush()
259
-
260
- c = sys.stdin.read(1)
261
-
262
- if c == '\x1b':
263
- c2 = sys.stdin.read(1)
264
- if c2 == '[':
265
- c3 = sys.stdin.read(1)
266
- if c3 == 'A': # Up
267
- if mode == 'list' and selected > 0:
268
- selected -= 1
269
- elif mode == 'preview' and preview_scroll > 0:
270
- preview_scroll -= 1
271
- elif c3 == 'B': # Down
272
- if mode == 'list' and selected < len(display_papers) - 1:
273
- selected += 1
274
- elif mode == 'preview' and preview_scroll < 100:
275
- preview_scroll += 1
276
- else:
277
- if mode == 'preview':
278
- mode = 'list'
279
- sys.stdout.write('\033[2J\033[H')
280
- else:
281
- context['output'] = "Cancelled."
282
- break
283
- continue
284
-
285
- if c == 'q' or c == '\x03':
286
- context['output'] = "Cancelled."
287
- break
288
- elif c == 'k':
289
- if mode == 'list' and selected > 0:
290
- selected -= 1
291
- elif mode == 'preview' and preview_scroll > 0:
292
- preview_scroll -= 1
293
- elif c == 'j':
294
- if mode == 'list' and selected < len(display_papers) - 1:
295
- selected += 1
296
- elif mode == 'preview' and preview_scroll < 100:
297
- preview_scroll += 1
298
- elif c == '0':
299
- sort_mode = 'relevance'
300
- display_papers = filter_papers(sort_papers(papers, sort_mode), year_filter)
301
- selected = 0
302
- scroll = 0
303
- elif c == '1':
304
- sort_mode = 'year'
305
- display_papers = filter_papers(sort_papers(papers, sort_mode), year_filter)
306
- selected = 0
307
- scroll = 0
308
- elif c == '2':
309
- sort_mode = 'citations'
310
- display_papers = filter_papers(sort_papers(papers, sort_mode), year_filter)
311
- selected = 0
312
- scroll = 0
313
- elif c == '3':
314
- sort_mode = 'author'
315
- display_papers = filter_papers(sort_papers(papers, sort_mode), year_filter)
316
- selected = 0
317
- scroll = 0
318
- elif c == 'y' and mode == 'list':
319
- # Cycle through year filters
320
- import datetime
321
- current_year = datetime.datetime.now().year
322
- if year_filter is None:
323
- year_filter = current_year - 1
324
- elif year_filter == current_year - 1:
325
- year_filter = current_year - 3
326
- elif year_filter == current_year - 3:
327
- year_filter = current_year - 5
328
- else:
329
- year_filter = None
330
- display_papers = filter_papers(sort_papers(papers, sort_mode), year_filter)
331
- selected = 0
332
- scroll = 0
333
- elif c == 'p' and mode == 'list' and display_papers:
334
- mode = 'preview'
335
- preview_scroll = 0
336
- sys.stdout.write('\033[2J\033[H')
337
- elif c == 'b' and mode == 'preview':
338
- mode = 'list'
339
- sys.stdout.write('\033[2J\033[H')
340
- elif c == 'o' and display_papers:
341
- sel = display_papers[selected]
342
- paper_url = sel.get('url', '')
343
- if paper_url:
344
- webbrowser.open(paper_url)
345
- elif c == 'i' and display_papers:
346
- sel = display_papers[selected]
347
- paper_url = sel.get('url', '')
348
- if paper_url:
349
- try:
350
- subprocess.run(['npcsh', '-c', f'/navigate url={paper_url}'], check=False, capture_output=True)
351
- except:
352
- webbrowser.open(paper_url)
353
- elif c == 'd' and display_papers:
354
- sel = display_papers[selected]
355
- # Save paper info to file
356
- title = sel.get('title', 'paper')
357
- safe_title = "".join(c if c.isalnum() or c in ' -_' else '_' for c in title)[:50]
358
- filename = f"{safe_title}.txt"
359
- with open(filename, 'w') as f:
360
- f.write(f"Title: {sel.get('title', '')}\n")
361
- f.write(f"Year: {sel.get('year', '')}\n")
362
- f.write(f"Citations: {sel.get('citationCount', 0)}\n")
363
- f.write(f"Venue: {sel.get('venue', '')}\n")
364
- f.write(f"Authors: {', '.join([a.get('name', '') for a in sel.get('authors', [])])}\n")
365
- f.write(f"URL: {sel.get('url', '')}\n")
366
- if sel.get('openAccessPdf'):
367
- f.write(f"PDF: {sel.get('openAccessPdf', {}).get('url', '')}\n")
368
- f.write(f"\nAbstract:\n{sel.get('abstract', '')}\n")
369
- if sel.get('tldr'):
370
- f.write(f"\nTL;DR:\n{sel.get('tldr', {}).get('text', '')}\n")
371
- context['output'] = f"Saved to: {filename}"
372
- break
373
- elif c in ('\r', '\n') and display_papers:
374
- sel = display_papers[selected]
375
- context['output'] = f"Selected: {sel.get('title', '')}\nURL: {sel.get('url', '')}"
376
- context['selected_paper'] = sel
377
- break
378
-
379
- finally:
380
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
381
- sys.stdout.write('\033[?25h')
382
- sys.stdout.write('\033[2J\033[H')
383
- sys.stdout.flush()
384
-
385
- except requests.exceptions.RequestException as e:
386
- context['output'] = f"Semantic Scholar API error: {e}"