autosh 0.0.3__py3-none-any.whl → 0.0.4__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.
autosh/main.py CHANGED
@@ -1,9 +1,11 @@
1
1
  import os
2
+
3
+ os.environ["AGENTIA_DISABLE_PLUGINS"] = "1"
4
+
2
5
  from pathlib import Path
3
6
  import rich
4
7
  import typer
5
8
  import asyncio
6
- import dotenv
7
9
  from rich.columns import Columns
8
10
  from rich.panel import Panel
9
11
  import argparse
@@ -26,6 +28,8 @@ app = typer.Typer(
26
28
  async def start_session(prompt: str | None, args: list[str]):
27
29
  CLI_OPTIONS.args = args
28
30
  session = Session()
31
+ os.environ["OPENROUTER_HAS_REASONING"] = "false"
32
+ os.environ["OPENROUTER_INCLUDE_REASONING"] = "false"
29
33
  await session.init()
30
34
  piped_stdin = not sys.stdin.isatty()
31
35
  if piped_stdin and not CLI_OPTIONS.yes:
@@ -1,3 +1,4 @@
1
+ from typing import Any, Callable
1
2
  import rich
2
3
  from rich.prompt import Confirm
3
4
  from rich.panel import Panel
@@ -5,40 +6,55 @@ from rich.console import RenderableType
5
6
  from autosh.config import CLI_OPTIONS, CONFIG
6
7
 
7
8
 
8
- def banner(tag: str, text: str | None = None, dim: str | None = None):
9
+ def __print_simple_banner(tag: str, text: str | None = None, dim: str | None = None):
9
10
  if CLI_OPTIONS.quiet:
10
11
  return
11
- s = f"[bold magenta]{tag}[/bold magenta]"
12
+ s = f"\n[bold on magenta] {tag} [/bold on magenta]"
12
13
  if text:
13
14
  s += f" [italic magenta]{text}[/italic magenta]"
14
15
  if dim:
15
16
  s += f" [italic dim]{dim}[/italic dim]"
16
- s += "\n"
17
17
  rich.print(s)
18
18
 
19
19
 
20
- def confirm(message: str):
21
- if CLI_OPTIONS.yes:
22
- return True
23
- result = Confirm.ask(
24
- f"[magenta]{message}[/magenta]", default=True, case_sensitive=False
20
+ def simple_banner(
21
+ tag: str | Callable[[Any], str],
22
+ text: Callable[[Any], str] | None = None,
23
+ dim: Callable[[Any], str] | None = None,
24
+ ):
25
+ return lambda x: __print_simple_banner(
26
+ tag if isinstance(tag, str) else tag(x),
27
+ text(x) if text else None,
28
+ dim(x) if dim else None,
25
29
  )
26
- if not CLI_OPTIONS.quiet:
27
- rich.print()
28
- return result
29
30
 
30
31
 
31
- def cmd_preview_panel(title: str, content: RenderableType, short: str | None = None):
32
+ def __print_code_preview_banner(
33
+ title: str, content: RenderableType, short: str | None = None
34
+ ):
32
35
  if CLI_OPTIONS.quiet:
33
36
  if short and not CLI_OPTIONS.yes:
34
- rich.print(f"[magenta]{short}[/magenta]\n")
37
+ rich.print(f"\n[magenta]{short}[/magenta]\n")
35
38
  return
36
39
  panel = Panel.fit(content, title=f"[magenta]{title}[/magenta]", title_align="left")
40
+ rich.print()
37
41
  rich.print(panel)
38
42
  rich.print()
39
43
 
40
44
 
41
- def cmd_result_panel(
45
+ def code_preview_banner(
46
+ title: str | Callable[[Any], str],
47
+ short: str | Callable[[Any], str],
48
+ content: Callable[[Any], RenderableType],
49
+ ):
50
+ return lambda x: __print_code_preview_banner(
51
+ title=title if isinstance(title, str) else title(x),
52
+ content=content(x),
53
+ short=short if isinstance(short, str) else short(x),
54
+ )
55
+
56
+
57
+ def code_result_panel(
42
58
  title: str,
43
59
  out: str | None = None,
44
60
  err: str | None = None,
autosh/plugins/calc.py CHANGED
@@ -1,12 +1,16 @@
1
1
  from agentia.plugins import tool, Plugin
2
2
  from typing import Annotated
3
- from . import banner
3
+ from . import simple_banner
4
4
 
5
5
 
6
6
  class CalculatorPlugin(Plugin):
7
7
  NAME = "calc"
8
8
 
9
- @tool
9
+ @tool(
10
+ metadata={
11
+ "banner": simple_banner("CALC", dim=lambda a: a.get("expression", ""))
12
+ }
13
+ )
10
14
  def evaluate(
11
15
  self,
12
16
  expression: Annotated[
@@ -16,7 +20,6 @@ class CalculatorPlugin(Plugin):
16
20
  """
17
21
  Execute a math expression and return the result. The expression must be an valid python expression that can be execuated by `eval()`.
18
22
  """
19
- banner("CALC", expression)
20
23
 
21
24
  result = eval(expression)
22
25
  return result
autosh/plugins/cli.py CHANGED
@@ -1,14 +1,14 @@
1
1
  import os
2
2
  import sys
3
3
  from typing import Annotated
4
- from agentia.plugins import Plugin, tool
4
+ from agentia import Plugin, tool, UserConsentEvent
5
5
  import rich
6
6
  import subprocess
7
7
  from enum import StrEnum
8
8
 
9
9
  from autosh.config import CLI_OPTIONS
10
10
 
11
- from . import banner, confirm, cmd_result_panel, cmd_preview_panel
11
+ from . import code_result_panel, code_preview_banner, simple_banner
12
12
 
13
13
 
14
14
  class Color(StrEnum):
@@ -59,22 +59,20 @@ class CLIPlugin(Plugin):
59
59
  rich.print(text, file=sys.stderr if stderr else sys.stdout, end=end)
60
60
  return "DONE. You can continue and no need to repeat the text"
61
61
 
62
- @tool
62
+ @tool(metadata={"banner": simple_banner("CWD", dim=lambda a: a.get("path", ""))})
63
63
  def chdir(self, path: Annotated[str, "The path to the new working directory"]):
64
64
  """
65
65
  Changes the current working directory of the terminal to another directory.
66
66
  """
67
- banner("CWD", path)
68
67
  if not os.path.exists(path):
69
68
  raise FileNotFoundError(f"Path `{path}` does not exist.")
70
69
  os.chdir(path)
71
70
 
72
- @tool
71
+ @tool(metadata={"banner": simple_banner("GET ARGV")})
73
72
  def get_argv(self):
74
73
  """
75
74
  Get the command line arguments.
76
75
  """
77
- banner("GET ARGV")
78
76
  if not CLI_OPTIONS.script:
79
77
  return CLI_OPTIONS.args
80
78
  return {
@@ -82,28 +80,34 @@ class CLIPlugin(Plugin):
82
80
  "args": CLI_OPTIONS.args,
83
81
  }
84
82
 
85
- @tool
83
+ @tool(metadata={"banner": simple_banner("GET ENV", dim=lambda a: a.get("key", ""))})
86
84
  def get_env(self, key: Annotated[str, "The environment variable to get"]):
87
85
  """
88
86
  Get an environment variable.
89
87
  """
90
- banner("GET ENV", key)
91
88
  if key not in os.environ:
92
89
  raise KeyError(f"Environment variable `{key}` does not exist.")
93
90
  return os.environ[key]
94
91
 
95
- @tool
92
+ @tool(metadata={"banner": simple_banner("GET ALL ENVS")})
96
93
  def get_all_envs(self):
97
94
  """
98
95
  Get all environment variables.
99
96
  """
100
- banner("GET ALL ENVS")
101
97
  envs = {}
102
98
  for key, value in os.environ.items():
103
99
  envs[key] = value
104
100
  return {"envs": envs}
105
101
 
106
- @tool
102
+ @tool(
103
+ metadata={
104
+ "banner": simple_banner(
105
+ tag=lambda a: "SET ENV" if a.get("value") else "DEL ENV",
106
+ text=lambda a: a.get("key", ""),
107
+ dim=lambda a: a.get("value", "") or "",
108
+ ),
109
+ }
110
+ )
107
111
  def update_env(
108
112
  self,
109
113
  key: Annotated[str, "The environment variable to set"],
@@ -116,15 +120,13 @@ class CLIPlugin(Plugin):
116
120
  Set or delete an environment variable.
117
121
  """
118
122
  if value is None:
119
- banner("DEL ENV", key)
120
123
  if key in os.environ:
121
124
  del os.environ[key]
122
125
  else:
123
- banner("SET ENV", key, value)
124
126
  os.environ[key] = value
125
127
  return f"DONE"
126
128
 
127
- @tool
129
+ @tool(metadata={"banner": simple_banner("READ", dim=lambda a: a.get("path", ""))})
128
130
  def read(
129
131
  self,
130
132
  path: Annotated[str, "The path to the file to read"],
@@ -132,7 +134,6 @@ class CLIPlugin(Plugin):
132
134
  """
133
135
  Read a file and print its content.
134
136
  """
135
- banner("READ", path)
136
137
  if not os.path.exists(path):
137
138
  raise FileNotFoundError(f"File `{path}` does not exist.")
138
139
  if not os.path.isfile(path):
@@ -141,7 +142,15 @@ class CLIPlugin(Plugin):
141
142
  content = f.read()
142
143
  return content
143
144
 
144
- @tool
145
+ @tool(
146
+ metadata={
147
+ "banner": simple_banner(
148
+ tag=lambda a: "WRITE" if not a.get("append") else "APPEND",
149
+ text=lambda a: a.get("path", ""),
150
+ dim=lambda a: f"({len(a.get('content', ''))} bytes)",
151
+ ),
152
+ }
153
+ )
145
154
  def write(
146
155
  self,
147
156
  path: Annotated[str, "The path to the file to write"],
@@ -154,8 +163,6 @@ class CLIPlugin(Plugin):
154
163
  """
155
164
  Write or append text content to a file.
156
165
  """
157
- banner("WRITE" if not append else "APPEND", path, f"({len(content)} bytes)")
158
-
159
166
  if not create and not os.path.exists(path):
160
167
  raise FileNotFoundError(f"File `{path}` does not exist.")
161
168
  if not create and not os.path.isfile(path):
@@ -164,7 +171,7 @@ class CLIPlugin(Plugin):
164
171
  raise FileExistsError(
165
172
  f"No, you cannot overwrite the script file `{path}`. You're likely writing to it by mistake."
166
173
  )
167
- if not confirm("Write file?"):
174
+ if not (yield UserConsentEvent("Write file?")):
168
175
  return {"error": "The user declined the write operation."}
169
176
  flag = "a" if append else "w"
170
177
  if create:
@@ -200,7 +207,15 @@ class CLIPlugin(Plugin):
200
207
  raise RuntimeError("No piped input from stdin")
201
208
  return sys.stdin.read()
202
209
 
203
- @tool
210
+ @tool(
211
+ metadata={
212
+ "banner": code_preview_banner(
213
+ title="Run Command",
214
+ short=lambda a: f"[magenta][bold]➜[/bold] [italic]{a.get("command", "")}[/italic][/magenta]",
215
+ content=lambda a: f"[magenta][bold]➜[/bold] [italic]{a.get("command", "")}[/italic][/magenta]\n\n[dim]{a.get("explanation", "")}[/dim]",
216
+ )
217
+ }
218
+ )
204
219
  def exec(
205
220
  self,
206
221
  command: Annotated[
@@ -220,15 +235,8 @@ class CLIPlugin(Plugin):
220
235
  cmd = ["bash", "-c", command]
221
236
  return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
222
237
 
223
- # Print the command and explanation
224
- cmd_preview_panel(
225
- title="Run Command",
226
- content=f"[magenta][bold]➜[/bold] [italic]{command}[/italic][/magenta]\n\n[dim]{explanation}[/dim]",
227
- short=f"[magenta][bold]➜[/bold] [italic]{command}[/italic][/magenta]",
228
- )
229
-
230
238
  # Ask for confirmation
231
- if not confirm("Execute this command?"):
239
+ if not (yield UserConsentEvent("Execute this command?")):
232
240
  return {"error": "The user declined to execute the command."}
233
241
 
234
242
  # Execute the command
@@ -245,7 +253,7 @@ class CLIPlugin(Plugin):
245
253
  title = f"[red][bold]✘[/bold] Command Failed [{proc_result.returncode}][/red]"
246
254
  else:
247
255
  title = "[green][bold]✔[/bold] Command Finished[/green]"
248
- cmd_result_panel(title, out, err)
256
+ code_result_panel(title, out, err)
249
257
 
250
258
  result = {
251
259
  "stdout": proc_result.stdout.decode("utf-8"),
@@ -255,10 +263,9 @@ class CLIPlugin(Plugin):
255
263
  }
256
264
  return result
257
265
 
258
- @tool
266
+ @tool(metadata={"banner": simple_banner("EXIT")})
259
267
  def exit(self, exitcode: Annotated[int, "The exit code of this shell session"] = 0):
260
268
  """
261
269
  Exit the current shell session with an optional exit code.
262
270
  """
263
- banner("EXIT", str(exitcode))
264
271
  sys.exit(exitcode)
autosh/plugins/clock.py CHANGED
@@ -2,14 +2,13 @@ import datetime
2
2
  from agentia.plugins import tool, Plugin
3
3
  import tzlocal
4
4
 
5
- from autosh.plugins import banner
5
+ from autosh.plugins import simple_banner
6
6
 
7
7
 
8
8
  class ClockPlugin(Plugin):
9
- @tool
9
+ @tool(metadata={"banner": simple_banner("GET TIME")})
10
10
  def get_current_time(self):
11
11
  """Get the current UTC time in ISO format"""
12
- banner("GET TIME")
13
12
  utc = datetime.datetime.now(datetime.timezone.utc).isoformat()
14
13
  local = datetime.datetime.now().isoformat()
15
14
  timezone = tzlocal.get_localzone_name()
autosh/plugins/code.py CHANGED
@@ -1,15 +1,32 @@
1
- from agentia.plugins import tool, Plugin
1
+ from agentia import tool, Plugin, UserConsentEvent
2
2
  from typing import Annotated
3
3
  import traceback
4
4
  from rich.syntax import Syntax
5
5
  from rich.console import group
6
6
  from contextlib import redirect_stdout, redirect_stderr
7
7
  import io
8
- from . import confirm, cmd_result_panel, cmd_preview_panel
8
+ from . import code_preview_banner, code_result_panel
9
+
10
+
11
+ @group()
12
+ def code_with_explanation(code: str, explanation: str):
13
+ yield Syntax(code.strip(), "python")
14
+ yield "\n[dim]───[/dim]\n"
15
+ yield f"[dim]{explanation}[/dim]"
9
16
 
10
17
 
11
18
  class CodePlugin(Plugin):
12
- @tool
19
+ @tool(
20
+ metadata={
21
+ "banner": code_preview_banner(
22
+ title="Run Python",
23
+ short="[bold]RUN[/bold] [italic]Python Code[/italic]",
24
+ content=lambda a: code_with_explanation(
25
+ a.get("python_code", ""), a.get("explanation", "")
26
+ ),
27
+ )
28
+ }
29
+ )
13
30
  def execute(
14
31
  self,
15
32
  python_code: Annotated[str, "The python code to run."],
@@ -22,20 +39,7 @@ class CodePlugin(Plugin):
22
39
  The python code must be a valid python source file that accepts no inputs.
23
40
  Print results to stdout or stderr.
24
41
  """
25
-
26
- @group()
27
- def code_with_explanation():
28
- yield Syntax(python_code.strip(), "python")
29
- yield "\n[dim]───[/dim]\n"
30
- yield f"[dim]{explanation}[/dim]"
31
-
32
- cmd_preview_panel(
33
- title="Run Python",
34
- content=code_with_explanation(),
35
- short=f"[bold]RUN[/bold] [italic]Python Code[/italic]",
36
- )
37
-
38
- if not confirm("Execute this code?"):
42
+ if not (yield UserConsentEvent("Execute this code?")):
39
43
  return {"error": "The user declined to execute the command."}
40
44
 
41
45
  out = io.StringIO()
@@ -64,5 +68,5 @@ class CodePlugin(Plugin):
64
68
  "traceback": repr(traceback.format_exc()),
65
69
  }
66
70
 
67
- cmd_result_panel(title, o, e)
71
+ code_result_panel(title, o, e)
68
72
  return result
autosh/plugins/search.py CHANGED
@@ -3,7 +3,7 @@ from typing import Annotated, override
3
3
  import os
4
4
  from tavily import TavilyClient
5
5
 
6
- from autosh.plugins import banner
6
+ from autosh.plugins import simple_banner
7
7
 
8
8
 
9
9
  class SearchPlugin(Plugin):
@@ -17,7 +17,11 @@ class SearchPlugin(Plugin):
17
17
  raise ValueError("Please set the TAVILY_API_KEY environment variable.")
18
18
  self.__tavily = TavilyClient(api_key=key)
19
19
 
20
- @tool
20
+ @tool(
21
+ metadata={
22
+ "banner": simple_banner("WEB SEARCH", dim=lambda a: a.get("query", "")),
23
+ }
24
+ )
21
25
  async def web_search(
22
26
  self,
23
27
  query: Annotated[
@@ -29,8 +33,6 @@ class SearchPlugin(Plugin):
29
33
  Returning the top related search results in json format.
30
34
  When necessary, you need to combine this tool with the get_webpage_content tools (if available), to browse the web in depth by jumping through links.
31
35
  """
32
- banner("WEB SEARCH", dim=query)
33
-
34
36
  tavily_results = self.__tavily.search(
35
37
  query=query,
36
38
  search_depth="advanced",
@@ -41,7 +43,11 @@ class SearchPlugin(Plugin):
41
43
  )
42
44
  return tavily_results
43
45
 
44
- @tool
46
+ @tool(
47
+ metadata={
48
+ "banner": simple_banner("NEWS SEARCH", dim=lambda a: a.get("query", "")),
49
+ }
50
+ )
45
51
  async def news_search(
46
52
  self,
47
53
  query: Annotated[
@@ -52,7 +58,6 @@ class SearchPlugin(Plugin):
52
58
  Perform news search on the given query.
53
59
  Returning the top related results in json format.
54
60
  """
55
- banner("NEWS SEARCH", dim=query)
56
61
 
57
62
  tavily_results = self.__tavily.search(
58
63
  query=query,
@@ -65,7 +70,11 @@ class SearchPlugin(Plugin):
65
70
  )
66
71
  return tavily_results
67
72
 
68
- @tool
73
+ @tool(
74
+ metadata={
75
+ "banner": simple_banner("FINANCE SEARCH", dim=lambda a: a.get("query", ""))
76
+ }
77
+ )
69
78
  async def finance_search(
70
79
  self,
71
80
  query: Annotated[
@@ -76,7 +85,6 @@ class SearchPlugin(Plugin):
76
85
  Search for finance-related news and information on the given query.
77
86
  Returning the top related results in json format.
78
87
  """
79
- banner("FINANCE SEARCH", dim=query)
80
88
 
81
89
  tavily_results = self.__tavily.search(
82
90
  query=query,
autosh/plugins/web.py CHANGED
@@ -7,7 +7,7 @@ from markdownify import markdownify
7
7
  import uuid
8
8
  from tavily import TavilyClient
9
9
 
10
- from autosh.plugins import banner
10
+ from autosh.plugins import simple_banner
11
11
 
12
12
 
13
13
  class WebPlugin(Plugin):
@@ -47,7 +47,7 @@ class WebPlugin(Plugin):
47
47
  md = markdownify(res.text)
48
48
  return {"content": md}
49
49
 
50
- @tool
50
+ @tool(metadata={"banner": simple_banner("BROWSE", dim=lambda a: a.get("url", ""))})
51
51
  def get_webpage_content(
52
52
  self,
53
53
  url: Annotated[str, "The URL of the web page to get the content of"],
@@ -57,7 +57,6 @@ class WebPlugin(Plugin):
57
57
  You can always use this tool to directly access web content or access external sites.
58
58
  Use it at any time when you think you may need to access the internet.
59
59
  """
60
- banner("BROWSE", dim=url)
61
60
 
62
61
  result = self.__tavily.extract(
63
62
  urls=url,