symbolicai 1.0.0__py3-none-any.whl → 1.1.1__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 (129) hide show
  1. symai/__init__.py +198 -134
  2. symai/backend/base.py +51 -51
  3. symai/backend/engines/drawing/engine_bfl.py +33 -33
  4. symai/backend/engines/drawing/engine_gpt_image.py +4 -10
  5. symai/backend/engines/embedding/engine_llama_cpp.py +50 -35
  6. symai/backend/engines/embedding/engine_openai.py +22 -16
  7. symai/backend/engines/execute/engine_python.py +16 -16
  8. symai/backend/engines/files/engine_io.py +51 -49
  9. symai/backend/engines/imagecaptioning/engine_blip2.py +27 -23
  10. symai/backend/engines/imagecaptioning/engine_llavacpp_client.py +53 -46
  11. symai/backend/engines/index/engine_pinecone.py +116 -88
  12. symai/backend/engines/index/engine_qdrant.py +1011 -0
  13. symai/backend/engines/index/engine_vectordb.py +78 -52
  14. symai/backend/engines/lean/engine_lean4.py +65 -25
  15. symai/backend/engines/neurosymbolic/__init__.py +35 -28
  16. symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_chat.py +137 -135
  17. symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_reasoning.py +145 -152
  18. symai/backend/engines/neurosymbolic/engine_cerebras.py +328 -0
  19. symai/backend/engines/neurosymbolic/engine_deepseekX_reasoning.py +75 -49
  20. symai/backend/engines/neurosymbolic/engine_google_geminiX_reasoning.py +199 -155
  21. symai/backend/engines/neurosymbolic/engine_groq.py +106 -72
  22. symai/backend/engines/neurosymbolic/engine_huggingface.py +100 -67
  23. symai/backend/engines/neurosymbolic/engine_llama_cpp.py +121 -93
  24. symai/backend/engines/neurosymbolic/engine_openai_gptX_chat.py +213 -132
  25. symai/backend/engines/neurosymbolic/engine_openai_gptX_reasoning.py +180 -137
  26. symai/backend/engines/ocr/engine_apilayer.py +18 -20
  27. symai/backend/engines/output/engine_stdout.py +9 -9
  28. symai/backend/engines/{webscraping → scrape}/engine_requests.py +25 -11
  29. symai/backend/engines/search/engine_openai.py +95 -83
  30. symai/backend/engines/search/engine_parallel.py +665 -0
  31. symai/backend/engines/search/engine_perplexity.py +40 -41
  32. symai/backend/engines/search/engine_serpapi.py +33 -28
  33. symai/backend/engines/speech_to_text/engine_local_whisper.py +37 -27
  34. symai/backend/engines/symbolic/engine_wolframalpha.py +14 -8
  35. symai/backend/engines/text_to_speech/engine_openai.py +15 -19
  36. symai/backend/engines/text_vision/engine_clip.py +34 -28
  37. symai/backend/engines/userinput/engine_console.py +3 -4
  38. symai/backend/mixin/__init__.py +4 -0
  39. symai/backend/mixin/anthropic.py +48 -40
  40. symai/backend/mixin/cerebras.py +9 -0
  41. symai/backend/mixin/deepseek.py +4 -5
  42. symai/backend/mixin/google.py +5 -4
  43. symai/backend/mixin/groq.py +2 -4
  44. symai/backend/mixin/openai.py +132 -110
  45. symai/backend/settings.py +14 -14
  46. symai/chat.py +164 -94
  47. symai/collect/dynamic.py +13 -11
  48. symai/collect/pipeline.py +39 -31
  49. symai/collect/stats.py +109 -69
  50. symai/components.py +578 -238
  51. symai/constraints.py +14 -5
  52. symai/core.py +1495 -1210
  53. symai/core_ext.py +55 -50
  54. symai/endpoints/api.py +113 -58
  55. symai/extended/api_builder.py +22 -17
  56. symai/extended/arxiv_pdf_parser.py +13 -5
  57. symai/extended/bibtex_parser.py +8 -4
  58. symai/extended/conversation.py +88 -69
  59. symai/extended/document.py +40 -27
  60. symai/extended/file_merger.py +45 -7
  61. symai/extended/graph.py +38 -24
  62. symai/extended/html_style_template.py +17 -11
  63. symai/extended/interfaces/blip_2.py +1 -1
  64. symai/extended/interfaces/clip.py +4 -2
  65. symai/extended/interfaces/console.py +5 -3
  66. symai/extended/interfaces/dall_e.py +3 -1
  67. symai/extended/interfaces/file.py +2 -0
  68. symai/extended/interfaces/flux.py +3 -1
  69. symai/extended/interfaces/gpt_image.py +15 -6
  70. symai/extended/interfaces/input.py +2 -1
  71. symai/extended/interfaces/llava.py +1 -1
  72. symai/extended/interfaces/{naive_webscraping.py → naive_scrape.py} +3 -2
  73. symai/extended/interfaces/naive_vectordb.py +2 -2
  74. symai/extended/interfaces/ocr.py +4 -2
  75. symai/extended/interfaces/openai_search.py +2 -0
  76. symai/extended/interfaces/parallel.py +30 -0
  77. symai/extended/interfaces/perplexity.py +2 -0
  78. symai/extended/interfaces/pinecone.py +6 -4
  79. symai/extended/interfaces/python.py +2 -0
  80. symai/extended/interfaces/serpapi.py +2 -0
  81. symai/extended/interfaces/terminal.py +0 -1
  82. symai/extended/interfaces/tts.py +2 -1
  83. symai/extended/interfaces/whisper.py +2 -1
  84. symai/extended/interfaces/wolframalpha.py +1 -0
  85. symai/extended/metrics/__init__.py +1 -1
  86. symai/extended/metrics/similarity.py +5 -2
  87. symai/extended/os_command.py +31 -22
  88. symai/extended/packages/symdev.py +39 -34
  89. symai/extended/packages/sympkg.py +30 -27
  90. symai/extended/packages/symrun.py +46 -35
  91. symai/extended/repo_cloner.py +10 -9
  92. symai/extended/seo_query_optimizer.py +15 -12
  93. symai/extended/solver.py +104 -76
  94. symai/extended/summarizer.py +8 -7
  95. symai/extended/taypan_interpreter.py +10 -9
  96. symai/extended/vectordb.py +28 -15
  97. symai/formatter/formatter.py +39 -31
  98. symai/formatter/regex.py +46 -44
  99. symai/functional.py +184 -86
  100. symai/imports.py +85 -51
  101. symai/interfaces.py +1 -1
  102. symai/memory.py +33 -24
  103. symai/menu/screen.py +28 -19
  104. symai/misc/console.py +27 -27
  105. symai/misc/loader.py +4 -3
  106. symai/models/base.py +147 -76
  107. symai/models/errors.py +1 -1
  108. symai/ops/__init__.py +1 -1
  109. symai/ops/measures.py +17 -14
  110. symai/ops/primitives.py +933 -635
  111. symai/post_processors.py +28 -24
  112. symai/pre_processors.py +58 -52
  113. symai/processor.py +15 -9
  114. symai/prompts.py +714 -649
  115. symai/server/huggingface_server.py +115 -32
  116. symai/server/llama_cpp_server.py +14 -6
  117. symai/server/qdrant_server.py +206 -0
  118. symai/shell.py +98 -39
  119. symai/shellsv.py +307 -223
  120. symai/strategy.py +135 -81
  121. symai/symbol.py +276 -225
  122. symai/utils.py +62 -46
  123. {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/METADATA +19 -9
  124. symbolicai-1.1.1.dist-info/RECORD +169 -0
  125. symbolicai-1.0.0.dist-info/RECORD +0 -163
  126. {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/WHEEL +0 -0
  127. {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/entry_points.txt +0 -0
  128. {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/licenses/LICENSE +0 -0
  129. {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/top_level.txt +0 -0
symai/shellsv.py CHANGED
@@ -13,7 +13,7 @@ from collections.abc import Iterable
13
13
  from pathlib import Path
14
14
  from types import SimpleNamespace
15
15
 
16
- #@TODO: refactor to use rich instead of prompt_toolkit
16
+ # @TODO: refactor to use rich instead of prompt_toolkit
17
17
  from prompt_toolkit import HTML, PromptSession, print_formatted_text
18
18
  from prompt_toolkit.completion import Completer, Completion, WordCompleter
19
19
  from prompt_toolkit.history import History
@@ -47,26 +47,26 @@ logging.getLogger("subprocess").setLevel(logging.ERROR)
47
47
 
48
48
  # load json config from home directory root
49
49
  home_path = HOME_PATH
50
- config_path = home_path / 'symsh.config.json'
50
+ config_path = home_path / "symsh.config.json"
51
51
  # migrate config from old path
52
- if 'colors' not in SYMSH_CONFIG:
52
+ if "colors" not in SYMSH_CONFIG:
53
53
  __new_config__ = {"colors": SYMSH_CONFIG}
54
54
  # add command in config
55
55
  SYMSH_CONFIG = __new_config__
56
56
  # save config
57
- with config_path.open('w') as f:
57
+ with config_path.open("w") as f:
58
58
  json.dump(__new_config__, f, indent=4)
59
59
 
60
60
  # make sure map-nt-cmd is in config
61
- if 'map-nt-cmd' not in SYMSH_CONFIG:
61
+ if "map-nt-cmd" not in SYMSH_CONFIG:
62
62
  # add command in config
63
- SYMSH_CONFIG['map-nt-cmd'] = True
63
+ SYMSH_CONFIG["map-nt-cmd"] = True
64
64
  # save config
65
- with config_path.open('w') as f:
65
+ with config_path.open("w") as f:
66
66
  json.dump(SYMSH_CONFIG, f, indent=4)
67
67
 
68
- print = print_formatted_text # noqa
69
- map_nt_cmd_enabled = SYMSH_CONFIG['map-nt-cmd']
68
+ print = print_formatted_text # noqa
69
+ map_nt_cmd_enabled = SYMSH_CONFIG["map-nt-cmd"]
70
70
 
71
71
  _shell_state = SimpleNamespace(
72
72
  function_type=Function,
@@ -91,6 +91,7 @@ If additional instructions are provided the follow the user query to produce the
91
91
  A well related and helpful answer with suggested improvements is preferred over "I don't know" or "I don't understand" answers or stating the obvious.
92
92
  """
93
93
 
94
+
94
95
  def supports_ansi_escape():
95
96
  try:
96
97
  os.get_terminal_size(0)
@@ -98,22 +99,23 @@ def supports_ansi_escape():
98
99
  except OSError:
99
100
  return False
100
101
 
102
+
101
103
  class PathCompleter(Completer):
102
104
  def get_completions(self, document, _complete_event):
103
105
  complete_word = document.get_word_before_cursor(WORD=True)
104
106
  sep = os.path.sep
105
- if complete_word.startswith(f'~{sep}'):
107
+ if complete_word.startswith(f"~{sep}"):
106
108
  complete_word = FileReader.expand_user_path(complete_word)
107
109
 
108
110
  # list all files and directories in current directory
109
111
  complete_path = Path(complete_word)
110
112
  if complete_word.endswith(sep):
111
113
  parent = complete_path
112
- pattern = '*'
114
+ pattern = "*"
113
115
  else:
114
116
  baseline = Path()
115
117
  parent = complete_path.parent if complete_path.parent != baseline else baseline
116
- pattern = f"{complete_path.name}*" if complete_path.name else '*'
118
+ pattern = f"{complete_path.name}*" if complete_path.name else "*"
117
119
  files = [str(path) for path in parent.glob(pattern)]
118
120
  if len(files) == 0:
119
121
  return None
@@ -124,16 +126,18 @@ class PathCompleter(Completer):
124
126
  for file in files:
125
127
  path_obj = Path(file)
126
128
  # split the command into words by space (ignore escaped spaces)
127
- command_words = document.text.split(' ')
129
+ command_words = document.text.split(" ")
128
130
  if len(command_words) > 1:
129
131
  # Calculate start position of the completion
130
- start_position = len(document.text) - len(' '.join(command_words[:-1])) - 1
132
+ start_position = len(document.text) - len(" ".join(command_words[:-1])) - 1
131
133
  start_position = max(0, start_position)
132
134
  else:
133
135
  start_position = len(document.text)
134
136
  # if there is a space in the file name, then escape it
135
- display_name = file.replace(' ', '\\ ') if ' ' in file else file
136
- if (document.text.startswith('cd') or document.text.startswith('mkdir')) and path_obj.is_file():
137
+ display_name = file.replace(" ", "\\ ") if " " in file else file
138
+ if (
139
+ document.text.startswith("cd") or document.text.startswith("mkdir")
140
+ ) and path_obj.is_file():
137
141
  continue
138
142
  if path_obj.is_dir():
139
143
  dirs_.append(display_name)
@@ -143,25 +147,33 @@ class PathCompleter(Completer):
143
147
  for d in dirs_:
144
148
  # if starts with home directory, then replace it with ~
145
149
  directory_completion = FileReader.expand_user_path(d)
146
- yield Completion(directory_completion, start_position=-start_position,
147
- style='class:path-completion',
148
- selected_style='class:path-completion-selected')
150
+ yield Completion(
151
+ directory_completion,
152
+ start_position=-start_position,
153
+ style="class:path-completion",
154
+ selected_style="class:path-completion-selected",
155
+ )
149
156
 
150
157
  for f in files_:
151
158
  # if starts with home directory, then replace it with ~
152
159
  file_completion = FileReader.expand_user_path(f)
153
- yield Completion(file_completion, start_position=-start_position,
154
- style='class:file-completion',
155
- selected_style='class:file-completion-selected')
160
+ yield Completion(
161
+ file_completion,
162
+ start_position=-start_position,
163
+ style="class:file-completion",
164
+ selected_style="class:file-completion-selected",
165
+ )
166
+
156
167
 
157
168
  class HistoryCompleter(WordCompleter):
158
169
  def get_completions(self, document, complete_event):
159
170
  completions = super().get_completions(document, complete_event)
160
171
  for completion in completions:
161
- completion.style = 'class:history-completion'
162
- completion.selected_style = 'class:history-completion-selected'
172
+ completion.style = "class:history-completion"
173
+ completion.selected_style = "class:history-completion-selected"
163
174
  yield completion
164
175
 
176
+
165
177
  class MergedCompleter(Completer):
166
178
  def __init__(self, path_completer, history_completer):
167
179
  self.path_completer = path_completer
@@ -170,46 +182,52 @@ class MergedCompleter(Completer):
170
182
  def get_completions(self, document, complete_event):
171
183
  text = document.text
172
184
 
173
- if text.startswith('cd ') or\
174
- text.startswith('ls ') or\
175
- text.startswith('touch ') or\
176
- text.startswith('cat ') or\
177
- text.startswith('mkdir ') or\
178
- text.startswith('open ') or\
179
- text.startswith('rm ') or\
180
- text.startswith('git ') or\
181
- text.startswith('vi ') or\
182
- text.startswith('nano ') or\
183
- text.startswith('*') or\
184
- text.startswith(r'.\\') or\
185
- text.startswith(r'~\\') or\
186
- text.startswith(r'\\') or\
187
- text.startswith('.\\') or\
188
- text.startswith('~\\') or\
189
- text.startswith('\\') or\
190
- text.startswith('./') or\
191
- text.startswith('~/') or\
192
- text.startswith('/'):
185
+ if (
186
+ text.startswith("cd ")
187
+ or text.startswith("ls ")
188
+ or text.startswith("touch ")
189
+ or text.startswith("cat ")
190
+ or text.startswith("mkdir ")
191
+ or text.startswith("open ")
192
+ or text.startswith("rm ")
193
+ or text.startswith("git ")
194
+ or text.startswith("vi ")
195
+ or text.startswith("nano ")
196
+ or text.startswith("*")
197
+ or text.startswith(r".\\")
198
+ or text.startswith(r"~\\")
199
+ or text.startswith(r"\\")
200
+ or text.startswith(".\\")
201
+ or text.startswith("~\\")
202
+ or text.startswith("\\")
203
+ or text.startswith("./")
204
+ or text.startswith("~/")
205
+ or text.startswith("/")
206
+ ):
193
207
  yield from self.path_completer.get_completions(document, complete_event)
194
208
  yield from self.history_completer.get_completions(document, complete_event)
195
209
  else:
196
210
  yield from self.history_completer.get_completions(document, complete_event)
197
211
  yield from self.path_completer.get_completions(document, complete_event)
198
212
 
213
+
199
214
  # Create custom keybindings
200
215
  bindings = KeyBindings()
201
216
  # Get a copy of the current environment
202
217
  default_env = os.environ.copy()
203
218
 
219
+
204
220
  def get_exec_prefix():
205
221
  exec_prefix = _shell_state.exec_prefix
206
- return sys.exec_prefix if exec_prefix == 'default' else exec_prefix
222
+ return sys.exec_prefix if exec_prefix == "default" else exec_prefix
223
+
207
224
 
208
225
  def get_conda_env():
209
226
  # what conda env am I in (e.g., where is my Python process from)?
210
227
  ENVBIN = get_exec_prefix()
211
228
  return Path(ENVBIN).name
212
229
 
230
+
213
231
  # bind to 'Ctrl' + 'Space'
214
232
  @bindings.add(Keys.ControlSpace)
215
233
  def _(event):
@@ -222,13 +240,14 @@ def _(event):
222
240
  kb = KeyBindings()
223
241
 
224
242
  cancel = [False]
225
- @kb.add('f')
243
+
244
+ @kb.add("f")
226
245
  def _(_event):
227
- UserMessage('You pressed `f`.', style="alert")
246
+ UserMessage("You pressed `f`.", style="alert")
228
247
 
229
- @kb.add('x')
248
+ @kb.add("x")
230
249
  def _(_event):
231
- " Send Abort (control-c) signal. "
250
+ "Send Abort (control-c) signal."
232
251
  cancel[0] = True
233
252
  os.kill(os.getpid(), signal.SIGINT)
234
253
 
@@ -238,34 +257,37 @@ def _(event):
238
257
  # TODO: hack to simulate progress bar of indeterminate length of an synchronous function
239
258
  for i in pb(range(100)):
240
259
  if i > 50 and i < 70:
241
- time.sleep(.01)
260
+ time.sleep(0.01)
242
261
 
243
262
  if i == 60:
244
- res = func(current_user_input) # hack to see progress bar
263
+ res = func(current_user_input) # hack to see progress bar
245
264
 
246
265
  # Stop when the cancel flag has been set.
247
266
  if cancel[0]:
248
267
  break
249
268
 
250
- with ConsoleStyle('code') as console:
269
+ with ConsoleStyle("code") as console:
251
270
  console.print(res)
252
271
 
272
+
253
273
  @bindings.add(Keys.PageUp)
254
274
  def _(event):
255
275
  # Moving up for 5 lines
256
276
  for _ in range(5):
257
277
  event.current_buffer.auto_up()
258
278
 
279
+
259
280
  @bindings.add(Keys.PageDown)
260
281
  def _(event):
261
282
  # Moving down for 5 lines
262
283
  for _ in range(5):
263
284
  event.current_buffer.auto_down()
264
285
 
286
+
265
287
  class FileHistory(History):
266
- '''
288
+ """
267
289
  :class:`.History` class that stores all strings in a file.
268
- '''
290
+ """
269
291
 
270
292
  def __init__(self, filename: str) -> None:
271
293
  self.filename = Path(filename)
@@ -297,25 +319,32 @@ class FileHistory(History):
297
319
  for line in string.split("\n"):
298
320
  write(f"{line}\n")
299
321
 
322
+
300
323
  # Defining commands history
301
- def load_history(home_path=HOME_PATH, history_file='.bash_history'):
324
+ def load_history(home_path=HOME_PATH, history_file=".bash_history"):
302
325
  history_file_path = home_path / history_file
303
326
  history = FileHistory(history_file_path)
304
327
  return history, list(history.load_history_strings())
305
328
 
329
+
306
330
  # Function to check if current directory is a git directory
307
331
  def get_git_branch():
308
332
  try:
309
- git_process = subprocess.Popen(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
333
+ git_process = subprocess.Popen(
334
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"],
335
+ stdout=subprocess.PIPE,
336
+ stderr=subprocess.PIPE,
337
+ )
310
338
  stdout, _stderr = git_process.communicate()
311
339
  if git_process.returncode == 0:
312
- return stdout.strip().decode('utf-8')
340
+ return stdout.strip().decode("utf-8")
313
341
  except FileNotFoundError:
314
342
  pass
315
343
  return None
316
344
 
345
+
317
346
  def disambiguate(cmds: str) -> tuple[str, int]:
318
- '''
347
+ """
319
348
  Ok, so, possible options for now:
320
349
  1. query | cmd
321
350
  2. query | file [file ...]
@@ -323,14 +352,14 @@ def disambiguate(cmds: str) -> tuple[str, int]:
323
352
  3. query | cmd | file
324
353
  4. query | cmd cmd ...
325
354
  5. query | file | cmd
326
- '''
327
- has_at_least_one_cmd = any(shutil.which(cmd) is not None for cmd in cmds.split(' '))
328
- maybe_cmd = cmds.split(' ')[0].strip() # get first command
355
+ """
356
+ has_at_least_one_cmd = any(shutil.which(cmd) is not None for cmd in cmds.split(" "))
357
+ maybe_cmd = cmds.split(" ")[0].strip() # get first command
329
358
  maybe_files = FileReader.extract_files(cmds)
330
359
  # if cmd follows file(s) or file(s) follows cmd throw error as not supported
331
360
  if maybe_files is not None and has_at_least_one_cmd:
332
361
  msg = (
333
- 'Cannot disambiguate commands that have both files and commands or multiple commands. Please provide '
362
+ "Cannot disambiguate commands that have both files and commands or multiple commands. Please provide "
334
363
  'correct order of commands. Supported are: query | file [file ...] (e.g. "what do these files have in '
335
364
  'common?" | file1 [file2 ...]) and query | cmd (e.g. "what flags can I use with rg?" | rg --help)'
336
365
  )
@@ -339,50 +368,51 @@ def disambiguate(cmds: str) -> tuple[str, int]:
339
368
  if shutil.which(maybe_cmd) is not None:
340
369
  cmd_out = subprocess.run(cmds, check=False, capture_output=True, text=True, shell=True)
341
370
  if not cmd_out.stdout:
342
- msg = f'Command not found or failed. Error: {cmd_out.stderr}'
371
+ msg = f"Command not found or failed. Error: {cmd_out.stderr}"
343
372
  UserMessage(msg, raise_with=ValueError)
344
373
  return cmd_out.stdout, 1
345
374
  if maybe_files is not None:
346
375
  return maybe_files, 2
347
376
  return None
348
377
 
378
+
349
379
  # query language model
350
380
  def _starts_with_prefix(query: str, prefix: str) -> bool:
351
381
  return (
352
382
  query.startswith(f'{prefix}"')
353
383
  or query.startswith(f"{prefix}'")
354
- or query.startswith(f'{prefix}`')
384
+ or query.startswith(f"{prefix}`")
355
385
  )
356
386
 
357
387
 
358
388
  def _is_new_conversation_query(query: str) -> bool:
359
- return _starts_with_prefix(query, '!')
389
+ return _starts_with_prefix(query, "!")
360
390
 
361
391
 
362
392
  def _is_followup_conversation_query(query: str) -> bool:
363
- return _starts_with_prefix(query, '.')
393
+ return _starts_with_prefix(query, ".")
364
394
 
365
395
 
366
396
  def _is_stateful_query(query: str) -> bool:
367
- return any(_starts_with_prefix(query, prefix) for prefix in ['.', '!'])
397
+ return any(_starts_with_prefix(query, prefix) for prefix in [".", "!"])
368
398
 
369
399
 
370
400
  def _extract_query_kwargs(query: str, previous_kwargs, existing_kwargs):
371
- if '--kwargs' not in query and '-kw' not in query:
401
+ if "--kwargs" not in query and "-kw" not in query:
372
402
  return query, existing_kwargs, previous_kwargs
373
403
 
374
- splitter = '--kwargs' if '--kwargs' in query else '-kw'
404
+ splitter = "--kwargs" if "--kwargs" in query else "-kw"
375
405
  splits = query.split(splitter)
376
406
  suffix = splits[-1]
377
- if previous_kwargs is None and '=' not in suffix and ',' not in suffix:
378
- msg = 'Kwargs format must be last in query.'
407
+ if previous_kwargs is None and "=" not in suffix and "," not in suffix:
408
+ msg = "Kwargs format must be last in query."
379
409
  UserMessage(msg, raise_with=ValueError)
380
- if previous_kwargs is not None and '=' not in suffix and ',' not in suffix:
410
+ if previous_kwargs is not None and "=" not in suffix and "," not in suffix:
381
411
  cmd_kwargs = previous_kwargs
382
412
  else:
383
413
  query = splits[0].strip()
384
414
  kwargs_str = suffix.strip()
385
- cmd_kwargs = dict([kw.split('=') for kw in kwargs_str.split(',')])
415
+ cmd_kwargs = dict([kw.split("=") for kw in kwargs_str.split(",")])
386
416
  cmd_kwargs = {k.strip(): Symbol(v.strip()).ast() for k, v in cmd_kwargs.items()}
387
417
 
388
418
  previous_kwargs = cmd_kwargs
@@ -398,7 +428,7 @@ def _process_new_conversation(query, conversation_cls, symai_path, plugin, previ
398
428
  if plugin is None:
399
429
  return conversation, previous_kwargs, None, False
400
430
  with Loader(desc="Inference ...", end=""):
401
- cmd = query[1:].strip('\'"')
431
+ cmd = query[1:].strip("'\"")
402
432
  cmd = f"symrun {plugin} '{cmd}' --disable-pbar"
403
433
  cmd_out = run_shell_command(cmd, auto_query_on_error=True)
404
434
  conversation.store(cmd_out)
@@ -408,18 +438,20 @@ def _process_new_conversation(query, conversation_cls, symai_path, plugin, previ
408
438
  return conversation, previous_kwargs, cmd_out, True
409
439
 
410
440
 
411
- def _process_followup_conversation(query, conversation, conversation_cls, symai_path, plugin, previous_kwargs, state):
441
+ def _process_followup_conversation(
442
+ query, conversation, conversation_cls, symai_path, plugin, previous_kwargs, state
443
+ ):
412
444
  try:
413
445
  conversation = conversation.load_conversation_state(symai_path)
414
446
  state.stateful_conversation = conversation
415
447
  except Exception:
416
- with ConsoleStyle('error') as console:
417
- console.print('No conversation state found. Please start a new conversation.')
448
+ with ConsoleStyle("error") as console:
449
+ console.print("No conversation state found. Please start a new conversation.")
418
450
  return conversation, previous_kwargs, None, True
419
451
  if plugin is None:
420
452
  return conversation, previous_kwargs, None, False
421
453
  with Loader(desc="Inference ...", end=""):
422
- trimmed_query = query[1:].strip('\'"')
454
+ trimmed_query = query[1:].strip("'\"")
423
455
  answer = conversation(trimmed_query).value
424
456
  cmd = f"symrun {plugin} '{answer}' --disable-pbar"
425
457
  cmd_out = run_shell_command(cmd, auto_query_on_error=True)
@@ -431,10 +463,10 @@ def _process_followup_conversation(query, conversation, conversation_cls, symai_
431
463
 
432
464
 
433
465
  def _handle_piped_query(query, conversation, state):
434
- cmds = query.split('|')
466
+ cmds = query.split("|")
435
467
  if len(cmds) > 2:
436
468
  msg = (
437
- 'Cannot disambiguate commands that have more than 1 pipes. Please provide correct order of commands. '
469
+ "Cannot disambiguate commands that have more than 1 pipes. Please provide correct order of commands. "
438
470
  'Supported are: query | file [file ...] (e.g. "what do these files have in common?" | file1 [file2 ...]) '
439
471
  'and query | cmd (e.g. "what flags can I use with rg?" | rg --help)'
440
472
  )
@@ -460,7 +492,7 @@ def _handle_piped_query(query, conversation, state):
460
492
 
461
493
 
462
494
  def _select_function_for_query(query, conversation, state):
463
- if '|' in query:
495
+ if "|" in query:
464
496
  return _handle_piped_query(query, conversation, state)
465
497
  if _is_stateful_query(query):
466
498
  return conversation, query
@@ -479,8 +511,8 @@ def query_language_model(query: str, res=None, *args, **kwargs):
479
511
  previous_kwargs = state.previous_kwargs
480
512
  conversation_cls = state.conversation_type
481
513
  home_path = HOME_PATH
482
- symai_path = home_path / '.conversation_state'
483
- plugin = SYMSH_CONFIG.get('plugin_prefix')
514
+ symai_path = home_path / ".conversation_state"
515
+ plugin = SYMSH_CONFIG.get("plugin_prefix")
484
516
 
485
517
  query, kwargs, previous_kwargs = _extract_query_kwargs(query, previous_kwargs, kwargs)
486
518
 
@@ -512,7 +544,8 @@ def query_language_model(query: str, res=None, *args, **kwargs):
512
544
  state.previous_kwargs = previous_kwargs
513
545
  return msg
514
546
 
515
- def retrieval_augmented_indexing(query: str, index_name = None, *_args, **_kwargs):
547
+
548
+ def retrieval_augmented_indexing(query: str, index_name=None, *_args, **_kwargs):
516
549
  state = _shell_state
517
550
  sep = os.path.sep
518
551
  path = query
@@ -520,35 +553,35 @@ def retrieval_augmented_indexing(query: str, index_name = None, *_args, **_kwarg
520
553
 
521
554
  # check if path contains overwrite flag
522
555
  overwrite = False
523
- if path.startswith('!'):
556
+ if path.startswith("!"):
524
557
  overwrite = True
525
558
  path = path[1:]
526
559
 
527
560
  # check if request use of specific index
528
- use_index_name = False
529
- if path.startswith('index:'):
561
+ use_index_name = False
562
+ if path.startswith("index:"):
530
563
  use_index_name = True
531
564
  # continue conversation with specific index
532
- index_name = path.split('index:')[-1].strip()
565
+ index_name = path.split("index:")[-1].strip()
533
566
  else:
534
567
  parse_arxiv = False
535
568
 
536
569
  # check if path contains arxiv flag
537
- if path.startswith('arxiv:'):
570
+ if path.startswith("arxiv:"):
538
571
  parse_arxiv = True
539
572
 
540
573
  # check if path contains git flag
541
- if path.startswith('git@'):
574
+ if path.startswith("git@"):
542
575
  overwrite = True
543
- repo_path = home_path / 'temp'
576
+ repo_path = home_path / "temp"
544
577
  with Loader(desc="Cloning repo ...", end=""):
545
578
  cloner = RepositoryCloner(repo_path=str(repo_path))
546
579
  url = path[4:]
547
- if 'http' not in url:
548
- url = 'https://' + url
549
- url = url.replace('.com:', '.com/')
580
+ if "http" not in url:
581
+ url = "https://" + url
582
+ url = url.replace(".com:", ".com/")
550
583
  # if ends with '.git' then remove it
551
- if url.endswith('.git'):
584
+ if url.endswith(".git"):
552
585
  url = url[:-4]
553
586
  path = cloner(url)
554
587
 
@@ -560,79 +593,90 @@ def retrieval_augmented_indexing(query: str, index_name = None, *_args, **_kwarg
560
593
  arxiv = ArxivPdfParser()
561
594
  pdf_file = arxiv(file)
562
595
  if pdf_file is not None:
563
- file = file |'\n'| pdf_file
596
+ file = file | "\n" | pdf_file
564
597
 
565
598
  index_name = path.split(sep)[-1] if index_name is None else index_name
566
599
  index_name = Indexer.replace_special_chars(index_name)
567
- UserMessage(f'Indexing {index_name} ...', style="extensity")
600
+ UserMessage(f"Indexing {index_name} ...", style="extensity")
568
601
 
569
602
  # creates index if not exists
570
603
  DocumentRetriever(index_name=index_name, file=file, overwrite=overwrite)
571
604
 
572
- symai_path = home_path / '.conversation_state'
605
+ symai_path = home_path / ".conversation_state"
573
606
  symai_path.parent.mkdir(parents=True, exist_ok=True)
574
- stateful_conversation = state.retrieval_conversation_type(auto_print=False, index_name=index_name)
607
+ stateful_conversation = state.retrieval_conversation_type(
608
+ auto_print=False, index_name=index_name
609
+ )
575
610
  state.stateful_conversation = stateful_conversation
576
611
  Conversation.save_conversation_state(stateful_conversation, symai_path)
577
612
  if use_index_name:
578
- message = 'New session '
613
+ message = "New session "
579
614
  else:
580
- message = f'Repository {url} cloned and ' if query.startswith('git@') or query.startswith('git:') else f'Directory {path} '
581
- return f'{message}successfully indexed: {index_name}'
615
+ message = (
616
+ f"Repository {url} cloned and "
617
+ if query.startswith("git@") or query.startswith("git:")
618
+ else f"Directory {path} "
619
+ )
620
+ return f"{message}successfully indexed: {index_name}"
621
+
582
622
 
583
623
  def search_engine(query: str, res=None, *_args, **_kwargs):
584
- search = Interface('serpapi')
624
+ search = Interface("serpapi")
585
625
  with Loader(desc="Searching ...", end=""):
586
- search_query = Symbol(query).extract('search engine optimized query')
626
+ search_query = Symbol(query).extract("search engine optimized query")
587
627
  res = search(search_query)
588
628
  with Loader(desc="Inference ...", end=""):
589
629
  func = _shell_state.function_type(query)
590
630
  msg = func(res, payload=res)
591
631
  # write a temp dump file with the query and results
592
632
  home_path = HOME_PATH
593
- symai_path = home_path / '.search_dump'
633
+ symai_path = home_path / ".search_dump"
594
634
  symai_path.parent.mkdir(parents=True, exist_ok=True)
595
- with symai_path.open('w') as f:
596
- f.write(f'[SEARCH_QUERY]:\n{search_query}\n[RESULTS]\n{res}\n[MESSAGE]\n{msg}')
635
+ with symai_path.open("w") as f:
636
+ f.write(f"[SEARCH_QUERY]:\n{search_query}\n[RESULTS]\n{res}\n[MESSAGE]\n{msg}")
597
637
  return msg
598
638
 
639
+
599
640
  def set_default_module(cmd: str):
600
- if cmd.startswith('set-plugin'):
601
- module = cmd.split('set-plugin')[-1].strip()
602
- SYMSH_CONFIG['plugin_prefix'] = module
603
- with config_path.open('w') as f:
641
+ if cmd.startswith("set-plugin"):
642
+ module = cmd.split("set-plugin")[-1].strip()
643
+ SYMSH_CONFIG["plugin_prefix"] = module
644
+ with config_path.open("w") as f:
604
645
  json.dump(SYMSH_CONFIG, f, indent=4)
605
646
  msg = f"Default plugin set to '{module}'"
606
- elif cmd == 'unset-plugin':
607
- SYMSH_CONFIG['plugin_prefix'] = None
608
- with config_path.open('w') as f:
647
+ elif cmd == "unset-plugin":
648
+ SYMSH_CONFIG["plugin_prefix"] = None
649
+ with config_path.open("w") as f:
609
650
  json.dump(SYMSH_CONFIG, f, indent=4)
610
651
  msg = "Default plugin unset"
611
- elif cmd == 'get-plugin':
652
+ elif cmd == "get-plugin":
612
653
  msg = f"Default plugin is '{SYMSH_CONFIG['plugin_prefix']}'"
613
654
 
614
- with ConsoleStyle('success') as console:
655
+ with ConsoleStyle("success") as console:
615
656
  console.print(msg)
616
657
 
658
+
617
659
  def handle_error(cmd, res, message, auto_query_on_error):
618
- msg = Symbol(cmd) | f'\n{res!s}'
619
- if 'command not found' in str(res) or 'not recognized as an internal or external command' in str(res):
620
- return res.stderr.decode('utf-8')
660
+ msg = Symbol(cmd) | f"\n{res!s}"
661
+ if "command not found" in str(
662
+ res
663
+ ) or "not recognized as an internal or external command" in str(res):
664
+ return res.stderr.decode("utf-8")
621
665
  stderr = res.stderr
622
666
  if stderr and auto_query_on_error:
623
- rsp = stderr.decode('utf-8')
667
+ rsp = stderr.decode("utf-8")
624
668
  UserMessage(rsp, style="alert")
625
669
  msg = msg | f"\n{rsp}"
626
- if 'usage:' in rsp:
670
+ if "usage:" in rsp:
627
671
  try:
628
- cmd = cmd.split('usage: ')[-1].split(' ')[0]
672
+ cmd = cmd.split("usage: ")[-1].split(" ")[0]
629
673
  # get man page result for command
630
- res = subprocess.run(f'man -P cat {cmd}',
631
- check=False, shell=True,
632
- stdout=subprocess.PIPE)
674
+ res = subprocess.run(
675
+ f"man -P cat {cmd}", check=False, shell=True, stdout=subprocess.PIPE
676
+ )
633
677
  stdout = res.stdout
634
678
  if stdout:
635
- rsp = stdout.decode('utf-8')[:500]
679
+ rsp = stdout.decode("utf-8")[:500]
636
680
  msg = msg | f"\n{rsp}"
637
681
  except Exception:
638
682
  pass
@@ -640,29 +684,34 @@ def handle_error(cmd, res, message, auto_query_on_error):
640
684
  return query_language_model(msg)
641
685
  stdout = res.stdout
642
686
  if stdout:
643
- message = stderr.decode('utf-8')
687
+ message = stderr.decode("utf-8")
644
688
  return message
645
689
 
690
+
646
691
  # run shell command
647
- def run_shell_command(cmd: str, prev=None, auto_query_on_error: bool=False, stdout=None, stderr=None):
692
+ def run_shell_command(
693
+ cmd: str, prev=None, auto_query_on_error: bool = False, stdout=None, stderr=None
694
+ ):
648
695
  if prev is not None:
649
- cmd = prev + ' && ' + cmd
696
+ cmd = prev + " && " + cmd
650
697
  message = None
651
698
  conda_env = get_exec_prefix()
652
699
  # copy default_env
653
700
  new_env = default_env.copy()
654
- if _shell_state.exec_prefix != 'default':
701
+ if _shell_state.exec_prefix != "default":
655
702
  # remove current env from PATH
656
703
  new_env["PATH"] = new_env["PATH"].replace(sys.exec_prefix, conda_env)
657
704
  # Execute the command
658
705
  try:
659
706
  stdout = subprocess.PIPE if auto_query_on_error else stdout
660
707
  stderr = subprocess.PIPE if auto_query_on_error else stderr
661
- res = subprocess.run(cmd, check=False, shell=True, stdout=stdout, stderr=stderr, env=new_env)
708
+ res = subprocess.run(
709
+ cmd, check=False, shell=True, stdout=stdout, stderr=stderr, env=new_env
710
+ )
662
711
  if res and stdout and res.stdout:
663
- message = res.stdout.decode('utf-8')
712
+ message = res.stdout.decode("utf-8")
664
713
  elif res and stderr and res.stderr:
665
- message = res.stderr.decode('utf-8')
714
+ message = res.stderr.decode("utf-8")
666
715
  except FileNotFoundError as e:
667
716
  return e
668
717
  except PermissionError as e:
@@ -674,46 +723,59 @@ def run_shell_command(cmd: str, prev=None, auto_query_on_error: bool=False, stdo
674
723
  # If command not found, then try to query language model
675
724
  return handle_error(cmd, res, message, auto_query_on_error)
676
725
 
726
+
677
727
  def is_llm_request(cmd: str):
678
- return cmd.startswith('"') or cmd.startswith('."') or cmd.startswith('!"') or cmd.startswith('?"') or\
679
- cmd.startswith("'") or cmd.startswith(".'") or cmd.startswith("!'") or cmd.startswith("?'") or\
680
- cmd.startswith('`') or cmd.startswith('.`') or cmd.startswith('!`') or cmd.startswith('?`') or\
681
- cmd.startswith('!(')
728
+ return (
729
+ cmd.startswith('"')
730
+ or cmd.startswith('."')
731
+ or cmd.startswith('!"')
732
+ or cmd.startswith('?"')
733
+ or cmd.startswith("'")
734
+ or cmd.startswith(".'")
735
+ or cmd.startswith("!'")
736
+ or cmd.startswith("?'")
737
+ or cmd.startswith("`")
738
+ or cmd.startswith(".`")
739
+ or cmd.startswith("!`")
740
+ or cmd.startswith("?`")
741
+ or cmd.startswith("!(")
742
+ )
743
+
682
744
 
683
745
  def map_nt_cmd(cmd: str, map_nt_cmd_enabled: bool = True):
684
- if os.name.lower() == 'nt' and map_nt_cmd_enabled and not is_llm_request(cmd):
746
+ if os.name.lower() == "nt" and map_nt_cmd_enabled and not is_llm_request(cmd):
685
747
  # Mapping command replacements with regex for commands with variants
686
748
  cmd_mappings = {
687
- r'\bls\b(-[a-zA-Z]*)?' : r'dir \1', # Maps 'ls' with or without arguments
688
- r'\bmv\b\s+(.*)' : r'move \1', # Maps 'mv' with any arguments
689
- r'\bcp\b\s+(.*)' : r'copy \1', # Maps 'cp' with any arguments
690
- r'\btouch\b\s+(.*)' : r'type nul > \1', # Maps 'touch filename' to 'type nul > filename'
691
- r'\brm\b\s+(-rf)?' : r'del \1', # Maps 'rm' and 'rm -rf'
692
- r'\bdiff\b\s+(.*)' : r'fc \1', # Maps 'diff' with any arguments
693
- r'\bgrep\b\s+(.*)' : r'find \1', # Maps 'grep' with any arguments
694
- r'\bpwd\b' : 'chdir', # pwd has no arguments
695
- r'\bdate\b' : 'time', # date has no arguments
696
- r'\bmkdir\b\s+(.*)' : r'md \1', # Maps 'mkdir' with any arguments
697
- r'\bwhich\b\s+(.*)' : r'where \1', # Maps 'which' with any arguments
698
- r'\b(vim|nano)\b\s+(.*)' : r'notepad \2', # Maps 'vim' or 'nano' with any arguments
699
- r'\b(mke2fs|mformat)\b\s+(.*)' : r'format \2', # Maps 'mke2fs' or 'mformat' with any arguments
700
- r'\b(rm\s+-rf|rmdir)\b' : 'rmdir /s /q', # Matches 'rm -rf' or 'rmdir'
701
- r'\bkill\b\s+(.*)' : r'taskkill \1', # Maps 'kill' with any arguments
702
- r'\bps\b\s*(.*)?' : r'tasklist \1', # Maps 'ps' with any or no arguments
703
- r'\bexport\b\s+(.*)' : r'set \1', # Maps 'export' with any arguments
704
- r'\b(chown|chmod)\b\s+(.*)' : r'attrib +r \2', # Maps 'chown' or 'chmod' with any arguments
705
- r'\btraceroute\b\s+(.*)' : r'tracert \1', # Maps 'traceroute' with any arguments
706
- r'\bcron\b\s+(.*)' : r'at \1', # Maps 'cron' with any arguments
707
- r'\bcat\b\s+(.*)' : r'type \1', # Maps 'cat' with any arguments
708
- r'\bdu\s+-s\b' : 'chkdsk', # du -s has no arguments, chkdsk is closest in functionality
709
- r'\bls\s+-R\b' : 'tree', # ls -R has no arguments
749
+ r"\bls\b(-[a-zA-Z]*)?": r"dir \1", # Maps 'ls' with or without arguments
750
+ r"\bmv\b\s+(.*)": r"move \1", # Maps 'mv' with any arguments
751
+ r"\bcp\b\s+(.*)": r"copy \1", # Maps 'cp' with any arguments
752
+ r"\btouch\b\s+(.*)": r"type nul > \1", # Maps 'touch filename' to 'type nul > filename'
753
+ r"\brm\b\s+(-rf)?": r"del \1", # Maps 'rm' and 'rm -rf'
754
+ r"\bdiff\b\s+(.*)": r"fc \1", # Maps 'diff' with any arguments
755
+ r"\bgrep\b\s+(.*)": r"find \1", # Maps 'grep' with any arguments
756
+ r"\bpwd\b": "chdir", # pwd has no arguments
757
+ r"\bdate\b": "time", # date has no arguments
758
+ r"\bmkdir\b\s+(.*)": r"md \1", # Maps 'mkdir' with any arguments
759
+ r"\bwhich\b\s+(.*)": r"where \1", # Maps 'which' with any arguments
760
+ r"\b(vim|nano)\b\s+(.*)": r"notepad \2", # Maps 'vim' or 'nano' with any arguments
761
+ r"\b(mke2fs|mformat)\b\s+(.*)": r"format \2", # Maps 'mke2fs' or 'mformat' with any arguments
762
+ r"\b(rm\s+-rf|rmdir)\b": "rmdir /s /q", # Matches 'rm -rf' or 'rmdir'
763
+ r"\bkill\b\s+(.*)": r"taskkill \1", # Maps 'kill' with any arguments
764
+ r"\bps\b\s*(.*)?": r"tasklist \1", # Maps 'ps' with any or no arguments
765
+ r"\bexport\b\s+(.*)": r"set \1", # Maps 'export' with any arguments
766
+ r"\b(chown|chmod)\b\s+(.*)": r"attrib +r \2", # Maps 'chown' or 'chmod' with any arguments
767
+ r"\btraceroute\b\s+(.*)": r"tracert \1", # Maps 'traceroute' with any arguments
768
+ r"\bcron\b\s+(.*)": r"at \1", # Maps 'cron' with any arguments
769
+ r"\bcat\b\s+(.*)": r"type \1", # Maps 'cat' with any arguments
770
+ r"\bdu\s+-s\b": "chkdsk", # du -s has no arguments, chkdsk is closest in functionality
771
+ r"\bls\s+-R\b": "tree", # ls -R has no arguments
710
772
  }
711
773
 
712
774
  # Remove 1:1 mappings
713
775
  direct_mappings = {
714
- 'clear': 'cls',
715
- 'man' : 'help',
716
- 'mem' : 'free',
776
+ "clear": "cls",
777
+ "man": "help",
778
+ "mem": "free",
717
779
  }
718
780
 
719
781
  cmd_mappings.update(direct_mappings)
@@ -724,93 +786,95 @@ def map_nt_cmd(cmd: str, map_nt_cmd_enabled: bool = True):
724
786
  original_cmd = cmd
725
787
  cmd = re.sub(linux_cmd, windows_cmd, cmd)
726
788
  if cmd != original_cmd:
727
- UserMessage(f'symsh >> command "{original_cmd}" mapped to "{cmd}"\n', style="extensity")
789
+ UserMessage(
790
+ f'symsh >> command "{original_cmd}" mapped to "{cmd}"\n', style="extensity"
791
+ )
728
792
 
729
793
  return cmd
730
794
 
731
795
 
732
796
  def _handle_plugin_commands(cmd: str):
733
- if cmd.startswith('set-plugin') or cmd == 'unset-plugin' or cmd == 'get-plugin':
797
+ if cmd.startswith("set-plugin") or cmd == "unset-plugin" or cmd == "get-plugin":
734
798
  return set_default_module(cmd)
735
799
  return None
736
800
 
737
801
 
738
802
  def _handle_chained_llm_commands(cmd: str, res, auto_query_on_error: bool):
739
- if '" && ' not in cmd and "' && " not in cmd and '` && ' not in cmd:
803
+ if '" && ' not in cmd and "' && " not in cmd and "` && " not in cmd:
740
804
  return None
741
805
  if not is_llm_request(cmd):
742
806
  return run_shell_command(cmd, prev=res, auto_query_on_error=auto_query_on_error)
743
- cmds = cmd.split(' && ')
807
+ cmds = cmd.split(" && ")
744
808
  if not is_llm_request(cmds[0]):
745
- return ValueError('The first command must be a LLM request.')
809
+ return ValueError("The first command must be a LLM request.")
746
810
  first_res = query_language_model(cmds[0], res=res)
747
- rest = ' && '.join(cmds[1:])
748
- if len(cmds) > 1 and '$1' in cmds[1]:
749
- first_res_str = str(first_res).replace('\n', r'\\n')
750
- rest = rest.replace('$1', f'"{first_res_str}"')
811
+ rest = " && ".join(cmds[1:])
812
+ if len(cmds) > 1 and "$1" in cmds[1]:
813
+ first_res_str = str(first_res).replace("\n", r"\\n")
814
+ rest = rest.replace("$1", f'"{first_res_str}"')
751
815
  first_res = None
752
816
  return run_shell_command(rest, prev=first_res, auto_query_on_error=auto_query_on_error)
753
817
 
754
818
 
755
819
  def _handle_llm_or_search(cmd: str, res):
756
- if cmd.startswith('?"') or cmd.startswith("?'") or cmd.startswith('?`'):
820
+ if cmd.startswith('?"') or cmd.startswith("?'") or cmd.startswith("?`"):
757
821
  query = cmd[1:]
758
822
  return search_engine(query, res=res)
759
- if is_llm_request(cmd) or '...' in cmd:
823
+ if is_llm_request(cmd) or "..." in cmd:
760
824
  return query_language_model(cmd, res=res)
761
825
  return None
762
826
 
763
827
 
764
828
  def _handle_retrieval_commands(cmd: str):
765
- if cmd.startswith('*'):
829
+ if cmd.startswith("*"):
766
830
  return retrieval_augmented_indexing(cmd[1:])
767
831
  return None
768
832
 
769
833
 
770
834
  def _handle_man_command(cmd: str):
771
- if cmd.startswith('man symsh'):
835
+ if cmd.startswith("man symsh"):
772
836
  pkg_path = Path(__file__).resolve().parent
773
- symsh_path = pkg_path / 'symsh.md'
837
+ symsh_path = pkg_path / "symsh.md"
774
838
  with symsh_path.open(encoding="utf8") as file_ptr:
775
839
  return file_ptr.read()
776
840
  return None
777
841
 
778
842
 
779
843
  def _handle_conda_commands(cmd: str, state, res, auto_query_on_error: bool):
780
- if cmd.startswith('conda activate'):
844
+ if cmd.startswith("conda activate"):
781
845
  env = Path(sys.exec_prefix)
782
846
  env_base = env.parent
783
- req_env = cmd.split(' ')[2]
847
+ req_env = cmd.split(" ")[2]
784
848
  env_path = env_base / req_env
785
849
  if not env_path.exists():
786
- return f'Environment {req_env} does not exist!'
850
+ return f"Environment {req_env} does not exist!"
787
851
  state.previous_prefix = state.exec_prefix
788
852
  state.exec_prefix = str(env_path)
789
853
  return state.exec_prefix
790
- if cmd.startswith('conda deactivate'):
854
+ if cmd.startswith("conda deactivate"):
791
855
  prev_prefix = state.previous_prefix
792
856
  if prev_prefix is not None:
793
857
  state.exec_prefix = prev_prefix
794
- if prev_prefix == 'default':
858
+ if prev_prefix == "default":
795
859
  state.previous_prefix = None
796
860
  return get_exec_prefix()
797
- if cmd.startswith('conda'):
861
+ if cmd.startswith("conda"):
798
862
  env = Path(get_exec_prefix())
799
863
  try:
800
864
  env_base = env.parents[1]
801
865
  except IndexError:
802
866
  env_base = env.parent
803
- cmd_rewritten = cmd.replace('conda', str(env_base / "condabin" / "conda"))
867
+ cmd_rewritten = cmd.replace("conda", str(env_base / "condabin" / "conda"))
804
868
  return run_shell_command(cmd_rewritten, prev=res, auto_query_on_error=auto_query_on_error)
805
869
  return None
806
870
 
807
871
 
808
872
  def _handle_directory_navigation(cmd: str):
809
873
  sep = os.path.sep
810
- if cmd.startswith('cd'):
874
+ if cmd.startswith("cd"):
811
875
  try:
812
876
  cmd_expanded = FileReader.expand_user_path(cmd)
813
- path = ' '.join(cmd_expanded.split(' ')[1:])
877
+ path = " ".join(cmd_expanded.split(" ")[1:])
814
878
  if path.endswith(sep):
815
879
  path = path[:-1]
816
880
  return os.chdir(path)
@@ -830,16 +894,16 @@ def _handle_directory_navigation(cmd: str):
830
894
 
831
895
 
832
896
  def _handle_ll_alias(cmd: str, res):
833
- if not cmd.startswith('ll'):
897
+ if not cmd.startswith("ll"):
834
898
  return None
835
- if os.name == 'nt':
836
- rewritten = cmd.replace('ll', 'dir')
899
+ if os.name == "nt":
900
+ rewritten = cmd.replace("ll", "dir")
837
901
  return run_shell_command(rewritten, prev=res)
838
- rewritten = cmd.replace('ll', 'ls -la')
902
+ rewritten = cmd.replace("ll", "ls -la")
839
903
  return run_shell_command(rewritten, prev=res)
840
904
 
841
905
 
842
- def process_command(cmd: str, res=None, auto_query_on_error: bool=False):
906
+ def process_command(cmd: str, res=None, auto_query_on_error: bool = False):
843
907
  state = _shell_state
844
908
 
845
909
  # map commands to windows if needed
@@ -878,14 +942,15 @@ def process_command(cmd: str, res=None, auto_query_on_error: bool=False):
878
942
 
879
943
  return run_shell_command(cmd, prev=res, auto_query_on_error=auto_query_on_error)
880
944
 
945
+
881
946
  def save_conversation():
882
947
  home_path = HOME_PATH
883
- symai_path = home_path / '.conversation_state'
948
+ symai_path = home_path / ".conversation_state"
884
949
  Conversation.save_conversation_state(_shell_state.stateful_conversation, symai_path)
885
950
 
886
951
 
887
952
  def _is_exit_command(cmd: str) -> bool:
888
- return cmd in ['quit', 'exit', 'q']
953
+ return cmd in ["quit", "exit", "q"]
889
954
 
890
955
 
891
956
  def _format_working_directory():
@@ -898,8 +963,8 @@ def _format_working_directory():
898
963
  prev_paths = sep.join(paths[:-1])
899
964
  last_path = paths[-1]
900
965
  if len(paths) > 1:
901
- return f'{prev_paths}{sep}<b>{last_path}</b>'
902
- return f'<b>{last_path}</b>'
966
+ return f"{prev_paths}{sep}<b>{last_path}</b>"
967
+ return f"<b>{last_path}</b>"
903
968
 
904
969
 
905
970
  def _build_prompt(git_branch, conda_env, cur_working_dir_str):
@@ -919,15 +984,20 @@ def _handle_exit(state):
919
984
  if state.stateful_conversation is not None:
920
985
  save_conversation()
921
986
  if not state.use_styles:
922
- UserMessage('Goodbye!', style="extensity")
987
+ UserMessage("Goodbye!", style="extensity")
923
988
  else:
924
- func = _shell_state.function_type('Give short goodbye')
925
- UserMessage(func('bye'), style="extensity")
989
+ func = _shell_state.function_type("Give short goodbye")
990
+ UserMessage(func("bye"), style="extensity")
926
991
  os._exit(0)
927
992
 
928
993
 
929
994
  # Function to listen for user input and execute commands
930
- def listen(session: PromptSession, word_comp: WordCompleter, auto_query_on_error: bool=False, verbose: bool=False):
995
+ def listen(
996
+ session: PromptSession,
997
+ word_comp: WordCompleter,
998
+ auto_query_on_error: bool = False,
999
+ verbose: bool = False,
1000
+ ):
931
1001
  state = _shell_state
932
1002
  with patch_stdout():
933
1003
  while True:
@@ -937,39 +1007,43 @@ def listen(session: PromptSession, word_comp: WordCompleter, auto_query_on_error
937
1007
  cur_working_dir_str = _format_working_directory()
938
1008
  prompt = _build_prompt(git_branch, conda_env, cur_working_dir_str)
939
1009
  cmd = session.prompt(prompt)
940
- if cmd.strip() == '':
1010
+ if cmd.strip() == "":
941
1011
  continue
942
1012
 
943
1013
  if _is_exit_command(cmd):
944
1014
  _handle_exit(state)
945
1015
  msg = process_command(cmd, auto_query_on_error=auto_query_on_error)
946
1016
  if msg is not None:
947
- with ConsoleStyle('code') as console:
1017
+ with ConsoleStyle("code") as console:
948
1018
  console.print(msg)
949
1019
 
950
1020
  # Append the command to the word completer list
951
1021
  word_comp.words.append(cmd)
952
1022
 
953
1023
  except KeyboardInterrupt:
954
- UserMessage('', style="alert")
1024
+ UserMessage("", style="alert")
955
1025
  except Exception as e:
956
1026
  UserMessage(str(e), style="alert")
957
1027
  if verbose:
958
1028
  traceback.print_exc()
959
1029
 
1030
+
960
1031
  def create_session(history, merged_completer):
961
- colors = SYMSH_CONFIG['colors']
1032
+ colors = SYMSH_CONFIG["colors"]
962
1033
 
963
1034
  # Load style
964
1035
  style = Style.from_dict(colors)
965
1036
 
966
1037
  # Session for the auto-completion
967
- return PromptSession(history=history,
968
- completer=merged_completer,
969
- complete_style=CompleteStyle.MULTI_COLUMN,
970
- reserve_space_for_menu=5,
971
- style=style,
972
- key_bindings=bindings)
1038
+ return PromptSession(
1039
+ history=history,
1040
+ completer=merged_completer,
1041
+ complete_style=CompleteStyle.MULTI_COLUMN,
1042
+ reserve_space_for_menu=5,
1043
+ style=style,
1044
+ key_bindings=bindings,
1045
+ )
1046
+
973
1047
 
974
1048
  def create_completer():
975
1049
  # Load history
@@ -983,10 +1057,11 @@ def create_completer():
983
1057
  merged_completer = MergedCompleter(custom_completer, word_comp)
984
1058
  return history, word_comp, merged_completer
985
1059
 
1060
+
986
1061
  def run(auto_query_on_error=False, conversation_style=None, verbose=False):
987
1062
  state = _shell_state
988
- if conversation_style is not None and conversation_style != '':
989
- UserMessage(f'Loading style: {conversation_style}', style="extensity")
1063
+ if conversation_style is not None and conversation_style != "":
1064
+ UserMessage(f"Loading style: {conversation_style}", style="extensity")
990
1065
  styles_ = Import.load_module_class(conversation_style)
991
1066
  (
992
1067
  state.function_type,
@@ -995,24 +1070,33 @@ def run(auto_query_on_error=False, conversation_style=None, verbose=False):
995
1070
  ) = styles_
996
1071
  state.use_styles = True
997
1072
 
998
- if SYMSH_CONFIG['show-splash-screen']:
1073
+ if SYMSH_CONFIG["show-splash-screen"]:
999
1074
  show_intro_menu()
1000
1075
  # set show splash screen to false
1001
- SYMSH_CONFIG['show-splash-screen'] = False
1076
+ SYMSH_CONFIG["show-splash-screen"] = False
1002
1077
  # save config
1003
- _config_path = HOME_PATH / 'symsh.config.json'
1004
- with _config_path.open('w') as f:
1078
+ _config_path = HOME_PATH / "symsh.config.json"
1079
+ with _config_path.open("w") as f:
1005
1080
  json.dump(SYMSH_CONFIG, f, indent=4)
1006
- if 'plugin_prefix' not in SYMSH_CONFIG:
1007
- SYMSH_CONFIG['plugin_prefix'] = None
1081
+ if "plugin_prefix" not in SYMSH_CONFIG:
1082
+ SYMSH_CONFIG["plugin_prefix"] = None
1008
1083
 
1009
1084
  history, word_comp, merged_completer = create_completer()
1010
1085
  session = create_session(history, merged_completer)
1011
1086
  listen(session, word_comp, auto_query_on_error=auto_query_on_error, verbose=verbose)
1012
1087
 
1013
- if __name__ == '__main__':
1014
- parser = argparse.ArgumentParser(description='SymSH: Symbolic Shell')
1015
- parser.add_argument('--auto-query-on-error', action='store_true', help='Automatically query the language model on error.')
1016
- parser.add_argument('--verbose', action='store_true', help='Print verbose errors.')
1088
+
1089
+ if __name__ == "__main__":
1090
+ parser = argparse.ArgumentParser(description="SymSH: Symbolic Shell")
1091
+ parser.add_argument(
1092
+ "--auto-query-on-error",
1093
+ action="store_true",
1094
+ help="Automatically query the language model on error.",
1095
+ )
1096
+ parser.add_argument("--verbose", action="store_true", help="Print verbose errors.")
1017
1097
  args = parser.parse_args()
1018
- run(auto_query_on_error=args.auto_query_on_error, conversation_style=args.conversation_style, verbose=args.verbose)
1098
+ run(
1099
+ auto_query_on_error=args.auto_query_on_error,
1100
+ conversation_style=args.conversation_style,
1101
+ verbose=args.verbose,
1102
+ )