autosh 0.0.7__py3-none-any.whl → 0.0.8__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/config.py CHANGED
@@ -77,6 +77,7 @@ class CLIOptions(BaseModel):
77
77
  yes: bool = False
78
78
  quiet: bool = False
79
79
  think: bool = False
80
+ start_repl_after_prompt: bool = False
80
81
 
81
82
  prompt: str | None = None
82
83
  """The prompt to execute"""
autosh/main.py CHANGED
@@ -39,6 +39,14 @@ async def start_session(prompt: str | None, args: list[str]):
39
39
  file=sys.stderr,
40
40
  )
41
41
  sys.exit(1)
42
+ if CLI_OPTIONS.start_repl_after_prompt:
43
+ if piped_stdin or piped_stdout:
44
+ rich.print(
45
+ "[bold red]Error:[/bold red] [red]--repl is only available when not using piped stdin or stdout.[/red]",
46
+ file=sys.stderr,
47
+ )
48
+ sys.exit(1)
49
+
42
50
  if prompt:
43
51
  # No piped stdin, just run the prompt
44
52
  if Path(prompt).is_file():
@@ -73,6 +81,11 @@ def print_help():
73
81
  f"The LLM model to use. [dim]Default: {CONFIG.model} ({CONFIG.think_model} for reasoning).[/dim]",
74
82
  ],
75
83
  ["--think", "", "Use the reasoning models to think more before operating."],
84
+ [
85
+ "--repl",
86
+ "",
87
+ "Start a REPL session after executing the prompt or the script.",
88
+ ],
76
89
  ["--help", "-h", "Show this message and exit."],
77
90
  ]
78
91
 
@@ -117,6 +130,7 @@ def parse_args() -> tuple[str | None, list[str]]:
117
130
  p.add_argument("--quiet", "-q", action="store_true")
118
131
  p.add_argument("--think", action="store_true")
119
132
  p.add_argument("--model", "-m", type=str, default=None)
133
+ p.add_argument("--repl", action="store_true")
120
134
  p.add_argument("PROMPT_OR_FILE", nargs="?", default=None)
121
135
  p.add_argument("ARGS", nargs=argparse.REMAINDER)
122
136
 
@@ -133,6 +147,7 @@ def parse_args() -> tuple[str | None, list[str]]:
133
147
 
134
148
  CLI_OPTIONS.yes = args.yes
135
149
  CLI_OPTIONS.quiet = args.quiet
150
+ CLI_OPTIONS.start_repl_after_prompt = args.repl
136
151
 
137
152
  if args.model:
138
153
  if args.think:
@@ -2,7 +2,6 @@ from dataclasses import dataclass
2
2
  import sys
3
3
  from typing import Any, Callable
4
4
  import rich
5
- from rich.prompt import Confirm
6
5
  from rich.panel import Panel
7
6
  from rich.console import RenderableType
8
7
  from autosh.config import CLI_OPTIONS, CONFIG
@@ -62,56 +61,6 @@ class Banner:
62
61
  return True
63
62
 
64
63
 
65
- # def __print_simple_banner(tag: str, text: str | None = None):
66
- # if CLI_OPTIONS.quiet:
67
- # return
68
- # if not sys.stdout.isatty():
69
- # s = f"\n[TOOL] {tag}"
70
- # if text:
71
- # s += f" {text}"
72
- # print(s)
73
- # return
74
- # s = f"\n[bold on magenta] {tag} [/bold on magenta]"
75
- # if text:
76
- # s += f" [italic dim]{text}[/italic dim]"
77
- # rich.print(s)
78
-
79
-
80
- # def simple_banner(
81
- # tag: str | Callable[[Any], str],
82
- # text: Callable[[Any], str] | None = None,
83
- # text_key: str | None = None,
84
- # ):
85
- # return lambda x: __print_simple_banner(
86
- # tag if isinstance(tag, str) else tag(x),
87
- # text(x) if text else (x.get(text_key) if text_key else None),
88
- # )
89
-
90
-
91
- # def __print_code_preview_banner(
92
- # title: str, content: RenderableType, short: str | None = None
93
- # ):
94
- # if CLI_OPTIONS.quiet:
95
- # if short and not CLI_OPTIONS.yes:
96
- # rich.print(f"\n[magenta]{short}[/magenta]\n")
97
- # return
98
- # panel = Panel.fit(content, title=f"[magenta]{title}[/magenta]", title_align="left")
99
- # rich.print()
100
- # rich.print(panel)
101
-
102
-
103
- # def code_preview_banner(
104
- # title: str | Callable[[Any], str],
105
- # short: str | Callable[[Any], str],
106
- # content: Callable[[Any], RenderableType],
107
- # ):
108
- # return lambda x: __print_code_preview_banner(
109
- # title=title if isinstance(title, str) else title(x),
110
- # content=content(x),
111
- # short=short if isinstance(short, str) else short(x),
112
- # )
113
-
114
-
115
64
  def code_result_panel(
116
65
  title: str,
117
66
  out: str | None = None,
autosh/session.py CHANGED
@@ -1,4 +1,3 @@
1
- from io import StringIO
2
1
  from pathlib import Path
3
2
  import socket
4
3
  import sys
@@ -19,7 +18,6 @@ import neongrid as ng
19
18
  from .plugins import Banner, create_plugins
20
19
  import rich
21
20
  import platform
22
- from rich.prompt import Confirm
23
21
  import os
24
22
 
25
23
 
@@ -105,6 +103,46 @@ class Session:
105
103
  role="user",
106
104
  )
107
105
 
106
+ async def __process_event(self, e: Event, first: bool, repl: bool):
107
+ prefix_newline = repl or not first
108
+ if isinstance(e, UserConsentEvent):
109
+ if CLI_OPTIONS.yes:
110
+ e.response = True
111
+ return False
112
+ if prefix_newline:
113
+ print()
114
+ e.response = ng.confirm(e.message)
115
+ return True
116
+ if isinstance(e, ToolCallEvent) and e.result is None:
117
+ if (banner := (e.metadata or {}).get("banner")) and isinstance(
118
+ banner, Banner
119
+ ):
120
+ return banner.render(e.arguments, prefix_newline=prefix_newline)
121
+ return False
122
+
123
+ async def __process_run(
124
+ self, run: Run[Event | MessageStream], loading: Loading | None, repl: bool
125
+ ):
126
+ first = True
127
+ async for e in run:
128
+ if loading:
129
+ await loading.finish()
130
+
131
+ if isinstance(e, Event):
132
+ if await self.__process_event(e, first=first, repl=repl):
133
+ first = False
134
+ else:
135
+ if repl or not first:
136
+ print()
137
+ await self.__render_streamed_markdown(e)
138
+ first = False
139
+
140
+ if loading:
141
+ loading = self.__create_loading_indicator()
142
+
143
+ if loading:
144
+ await loading.finish()
145
+
108
146
  async def exec_prompt(self, prompt: str):
109
147
  # Clean up the prompt
110
148
  if prompt is not None:
@@ -145,6 +183,8 @@ class Session:
145
183
  )
146
184
  run = self.agent.run(prompt, stream=True, events=True)
147
185
  await self.__process_run(run, loading, False)
186
+ if CLI_OPTIONS.start_repl_after_prompt:
187
+ await self.run_repl(handover=True)
148
188
 
149
189
  async def exec_from_stdin(self):
150
190
  if sys.stdin.isatty():
@@ -161,45 +201,28 @@ class Session:
161
201
  prompt = f.read()
162
202
  await self.exec_prompt(prompt)
163
203
 
164
- async def __process_event(self, e: Event, first: bool, repl: bool):
165
- prefix_newline = repl or not first
166
- if isinstance(e, UserConsentEvent):
167
- if CLI_OPTIONS.yes:
168
- e.response = True
169
- return False
170
- if prefix_newline:
171
- print()
172
- e.response = ng.confirm(e.message)
173
- return True
174
- if isinstance(e, ToolCallEvent) and e.result is None:
175
- if (banner := (e.metadata or {}).get("banner")) and isinstance(
176
- banner, Banner
177
- ):
178
- return banner.render(e.arguments, prefix_newline=prefix_newline)
179
- return False
180
-
181
- async def __process_run(
182
- self, run: Run[Event | MessageStream], loading: Loading | None, repl: bool
183
- ):
184
- first = True
185
- async for e in run:
186
- if loading:
187
- await loading.finish()
188
-
189
- if isinstance(e, Event):
190
- if await self.__process_event(e, first=first, repl=repl):
191
- first = False
192
- else:
193
- if repl or not first:
204
+ async def run_repl(self, handover: bool = False):
205
+ if not handover and CONFIG.repl_banner:
206
+ rich.print(CONFIG.repl_banner)
207
+ first = not handover
208
+ while True:
209
+ try:
210
+ if not first:
194
211
  print()
195
- await self.__render_streamed_markdown(e)
196
212
  first = False
197
-
198
- if loading:
213
+ input_prompt = self.__get_input_prompt()
214
+ rich.print(input_prompt, end="", flush=True)
215
+ prompt = await ng.input("", sync=False, persist="/tmp/autosh-history")
216
+ prompt = prompt.strip()
217
+ if prompt in ["exit", "quit"]:
218
+ break
219
+ if len(prompt) == 0:
220
+ continue
199
221
  loading = self.__create_loading_indicator()
200
-
201
- if loading:
202
- await loading.finish()
222
+ run = self.agent.run(prompt, stream=True, events=True)
223
+ await self.__process_run(run, loading, True)
224
+ except KeyboardInterrupt:
225
+ break
203
226
 
204
227
  def __get_input_prompt(self):
205
228
  cwd = Path.cwd()
@@ -229,29 +252,6 @@ class Session:
229
252
  )
230
253
  return prompt
231
254
 
232
- async def run_repl(self):
233
- if CONFIG.repl_banner:
234
- rich.print(CONFIG.repl_banner)
235
- first = True
236
- while True:
237
- try:
238
- if not first:
239
- print()
240
- first = False
241
- input_prompt = self.__get_input_prompt()
242
- rich.print(input_prompt, end="", flush=True)
243
- prompt = await ng.input("", sync=False, persist="/tmp/autosh-history")
244
- prompt = prompt.strip()
245
- if prompt in ["exit", "quit"]:
246
- break
247
- if len(prompt) == 0:
248
- continue
249
- loading = self.__create_loading_indicator()
250
- run = self.agent.run(prompt, stream=True, events=True)
251
- await self.__process_run(run, loading, True)
252
- except KeyboardInterrupt:
253
- break
254
-
255
255
  def __create_loading_indicator(self):
256
256
  return ng.loading.kana()
257
257
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autosh
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: The AI-powered, noob-friendly interactive shell
5
5
  Author-email: Wenyu Zhao <wenyuzhaox@gmail.com>
6
6
  License-Expression: MIT
@@ -10,7 +10,7 @@ Requires-Python: >=3.12
10
10
  Requires-Dist: agentia>=0.0.8
11
11
  Requires-Dist: asyncio>=3.4.3
12
12
  Requires-Dist: markdownify>=1.1.0
13
- Requires-Dist: neongrid>=0.0.1
13
+ Requires-Dist: neongrid>=0.0.2
14
14
  Requires-Dist: prompt-toolkit>=3.0.51
15
15
  Requires-Dist: pydantic>=2.11.3
16
16
  Requires-Dist: python-dotenv>=1.1.0
@@ -1,17 +1,17 @@
1
1
  autosh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  autosh/config-template.toml,sha256=iLdCBHIK0czWgNHtwAgvuhV670aiNc-IOmaPHP12i0Y,269
3
- autosh/config.py,sha256=TvKXwOjdVXFOVY91M8QK7j8XFBUvWk_drsaw1v5ZTIw,3018
4
- autosh/main.py,sha256=vCrnpTXTXD6evjuePqUrNd4q-_qRDk0g0VqW_Gi-prQ,5242
5
- autosh/session.py,sha256=nVCFDEQQ61Doh7e4daMdorMsCL8LxzjWrDtwerPy1Ks,9303
6
- autosh/plugins/__init__.py,sha256=T6HAgYUIz5XpwnyqciSmjaiEKEWmeKOODcwxntNXqMM,4797
3
+ autosh/config.py,sha256=TrEcQEEfWdruQntEJwxDUoHpBBkVpK_n2FJ-3IjDYHU,3060
4
+ autosh/main.py,sha256=Fvaxg-vkpEITZRxLxOXs_sE4_tZ8P_F0PKxQzZF455o,5782
5
+ autosh/session.py,sha256=1uPLDa37tIgFjclv2lqj5VnOkoy77tqyF7nTdK261B8,9391
6
+ autosh/plugins/__init__.py,sha256=I_lDaPxoPTiAVegpCGV3LpdBLQl4dbKmWPBZjQrmGIs,3260
7
7
  autosh/plugins/calc.py,sha256=n37BxwnBZW8Fez8VeN2CRizkZlUgbVGCuip_8lGxzBs,606
8
8
  autosh/plugins/cli.py,sha256=5u96BySehoejyqTOAaJIqUx4bmymhk5xmjVVCrz-XJ0,9375
9
9
  autosh/plugins/clock.py,sha256=4a_zpEggzJilgIA1DPP7ZdGA8cbYYH6hOL3MotkzpUk,556
10
10
  autosh/plugins/code.py,sha256=jMw4YWRkmvSKZ24DiVtXjxhfbuV7qs4x6o0IJLYFszE,2423
11
11
  autosh/plugins/search.py,sha256=_ey7myAbTILi3fbWfl6gGBbkPi8txP3dIKVL27o4ZVw,2798
12
12
  autosh/plugins/web.py,sha256=7DT7adgN6FPlrmGvnHynilh_WZevjWZaHiNa0egNXHs,2618
13
- autosh-0.0.7.dist-info/METADATA,sha256=e9SUTlYaQEDoSvv3KuwlvOPqSmMuUmBge2fojjX5iWc,2165
14
- autosh-0.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- autosh-0.0.7.dist-info/entry_points.txt,sha256=BV7bzUnxG6Z5InEkrfajGCxjooYORC5tZDDZctOPenQ,67
16
- autosh-0.0.7.dist-info/licenses/LICENSE,sha256=BnLDJsIJe-Dm18unR9DOoSv7QOfAz6LeIQc1yHAjxp0,1066
17
- autosh-0.0.7.dist-info/RECORD,,
13
+ autosh-0.0.8.dist-info/METADATA,sha256=ibtORCq9rOBYc1HlS8PUi34X29dtSlOpRRIHCdamW2Y,2165
14
+ autosh-0.0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ autosh-0.0.8.dist-info/entry_points.txt,sha256=BV7bzUnxG6Z5InEkrfajGCxjooYORC5tZDDZctOPenQ,67
16
+ autosh-0.0.8.dist-info/licenses/LICENSE,sha256=BnLDJsIJe-Dm18unR9DOoSv7QOfAz6LeIQc1yHAjxp0,1066
17
+ autosh-0.0.8.dist-info/RECORD,,
File without changes