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 +5 -1
- autosh/plugins/__init__.py +30 -14
- autosh/plugins/calc.py +6 -3
- autosh/plugins/cli.py +38 -31
- autosh/plugins/clock.py +2 -3
- autosh/plugins/code.py +22 -18
- autosh/plugins/search.py +16 -8
- autosh/plugins/web.py +2 -3
- autosh/session.py +63 -100
- {autosh-0.0.3.dist-info → autosh-0.0.4.dist-info}/METADATA +4 -2
- autosh-0.0.4.dist-info/RECORD +17 -0
- autosh/md/__init__.py +0 -8
- autosh/md/inline_text.py +0 -214
- autosh/md/printer.py +0 -301
- autosh/md/state.py +0 -136
- autosh/md/stream.py +0 -107
- autosh-0.0.3.dist-info/RECORD +0 -22
- {autosh-0.0.3.dist-info → autosh-0.0.4.dist-info}/WHEEL +0 -0
- {autosh-0.0.3.dist-info → autosh-0.0.4.dist-info}/entry_points.txt +0 -0
- {autosh-0.0.3.dist-info → autosh-0.0.4.dist-info}/licenses/LICENSE +0 -0
autosh/session.py
CHANGED
@@ -1,17 +1,24 @@
|
|
1
1
|
import asyncio
|
2
2
|
from pathlib import Path
|
3
3
|
import sys
|
4
|
-
from agentia import
|
5
|
-
|
6
|
-
|
4
|
+
from agentia import (
|
5
|
+
Agent,
|
6
|
+
UserMessage,
|
7
|
+
Event,
|
8
|
+
ToolCallEvent,
|
9
|
+
MessageStream,
|
10
|
+
ChatCompletion,
|
11
|
+
UserConsentEvent,
|
12
|
+
)
|
7
13
|
from agentia.plugins import PluginInitError
|
14
|
+
from neongrid.loading import Loading
|
8
15
|
|
9
16
|
from autosh.config import CLI_OPTIONS, CONFIG
|
10
|
-
|
17
|
+
import neongrid as ng
|
11
18
|
from .plugins import create_plugins
|
12
19
|
import rich
|
13
20
|
import platform
|
14
|
-
from rich.prompt import
|
21
|
+
from rich.prompt import Confirm
|
15
22
|
|
16
23
|
|
17
24
|
INSTRUCTIONS = f"""
|
@@ -79,13 +86,13 @@ class Session:
|
|
79
86
|
* -h, --help Show this message and exit.
|
80
87
|
""",
|
81
88
|
)
|
82
|
-
agent.history.add(self.
|
89
|
+
agent.history.add(self.__get_argv_message())
|
83
90
|
completion = agent.chat_completion(prompt, stream=True)
|
84
91
|
async for stream in completion:
|
85
92
|
await self.__render_streamed_markdown(stream)
|
86
93
|
sys.exit(0)
|
87
94
|
|
88
|
-
def
|
95
|
+
def __get_argv_message(self):
|
89
96
|
args = str(CLI_OPTIONS.args)
|
90
97
|
if not CLI_OPTIONS.script:
|
91
98
|
cmd = Path(sys.argv[0]).name
|
@@ -112,7 +119,7 @@ class Session:
|
|
112
119
|
# Execute the prompt
|
113
120
|
loading = self.__create_loading_indicator()
|
114
121
|
CLI_OPTIONS.prompt = prompt
|
115
|
-
self.agent.history.add(self.
|
122
|
+
self.agent.history.add(self.__get_argv_message())
|
116
123
|
if CLI_OPTIONS.stdin_has_data():
|
117
124
|
self.agent.history.add(
|
118
125
|
UserMessage(
|
@@ -134,13 +141,8 @@ class Session:
|
|
134
141
|
role="user",
|
135
142
|
)
|
136
143
|
)
|
137
|
-
completion = self.agent.chat_completion(prompt, stream=True)
|
138
|
-
|
139
|
-
if not loading:
|
140
|
-
loading = self.__create_loading_indicator()
|
141
|
-
if await self.__render_streamed_markdown(stream, loading=loading):
|
142
|
-
print()
|
143
|
-
loading = None
|
144
|
+
completion = self.agent.chat_completion(prompt, stream=True, events=True)
|
145
|
+
await self.__process_completion(completion, loading)
|
144
146
|
|
145
147
|
async def exec_from_stdin(self):
|
146
148
|
if sys.stdin.isatty():
|
@@ -157,104 +159,65 @@ class Session:
|
|
157
159
|
prompt = f.read()
|
158
160
|
await self.exec_prompt(prompt)
|
159
161
|
|
162
|
+
async def __process_event(self, e: Event):
|
163
|
+
if isinstance(e, UserConsentEvent):
|
164
|
+
e.response = await self.__confirm(e.message)
|
165
|
+
if isinstance(e, ToolCallEvent) and e.result is None:
|
166
|
+
if banner := (e.metadata or {}).get("banner"):
|
167
|
+
banner(e.arguments)
|
168
|
+
|
169
|
+
async def __confirm(self, message: str) -> bool:
|
170
|
+
if CLI_OPTIONS.yes:
|
171
|
+
return True
|
172
|
+
result = Confirm.ask(
|
173
|
+
f"[magenta]{message}[/magenta]", default=True, case_sensitive=False
|
174
|
+
)
|
175
|
+
if not CLI_OPTIONS.quiet:
|
176
|
+
rich.print()
|
177
|
+
return result
|
178
|
+
|
179
|
+
async def __process_completion(
|
180
|
+
self, completion: ChatCompletion[Event | MessageStream], loading: Loading
|
181
|
+
):
|
182
|
+
async for stream in completion:
|
183
|
+
await loading.finish()
|
184
|
+
|
185
|
+
if isinstance(stream, Event):
|
186
|
+
await self.__process_event(stream)
|
187
|
+
else:
|
188
|
+
print()
|
189
|
+
await self.__render_streamed_markdown(stream)
|
190
|
+
print()
|
191
|
+
|
192
|
+
loading = self.__create_loading_indicator()
|
193
|
+
|
194
|
+
await loading.finish()
|
195
|
+
|
160
196
|
async def run_repl(self):
|
161
|
-
console = rich.console.Console()
|
162
197
|
while True:
|
163
198
|
try:
|
164
|
-
prompt =
|
199
|
+
prompt = (
|
200
|
+
await ng.input("> ", sync=False, persist="/tmp/autosh-history")
|
201
|
+
).strip()
|
165
202
|
if prompt in ["exit", "quit"]:
|
166
203
|
break
|
167
204
|
if len(prompt) == 0:
|
168
205
|
continue
|
169
|
-
loading = self.__create_loading_indicator(
|
170
|
-
completion = self.agent.chat_completion(
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
if await self.__render_streamed_markdown(stream, loading=loading):
|
175
|
-
print()
|
176
|
-
loading = None
|
206
|
+
loading = self.__create_loading_indicator()
|
207
|
+
completion = self.agent.chat_completion(
|
208
|
+
prompt, stream=True, events=True
|
209
|
+
)
|
210
|
+
await self.__process_completion(completion, loading)
|
177
211
|
except KeyboardInterrupt:
|
178
212
|
break
|
179
213
|
|
180
|
-
def __create_loading_indicator(self
|
181
|
-
return (
|
182
|
-
asyncio.create_task(self.__loading(newline))
|
183
|
-
if sys.stdout.isatty()
|
184
|
-
else None
|
185
|
-
)
|
186
|
-
|
187
|
-
async def __loading(self, newline: bool = False):
|
188
|
-
chars = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
189
|
-
char_width = 1
|
190
|
-
msg = "Loading..."
|
191
|
-
count = 0
|
192
|
-
print("\x1b[2m", end="", flush=True)
|
193
|
-
while True:
|
194
|
-
try:
|
195
|
-
print(chars[count], end="", flush=True)
|
196
|
-
print(" " + msg, end="", flush=True)
|
197
|
-
count += 1
|
198
|
-
await asyncio.sleep(0.1)
|
199
|
-
length = char_width + len(msg) + 1
|
200
|
-
print("\b" * length, end="", flush=True)
|
201
|
-
print(" " * length, end="", flush=True)
|
202
|
-
print("\b" * length, end="", flush=True)
|
203
|
-
if count == len(chars):
|
204
|
-
count = 0
|
205
|
-
except asyncio.CancelledError:
|
206
|
-
length = char_width + len(msg) + 1
|
207
|
-
print("\b" * length, end="", flush=True)
|
208
|
-
print(" " * length, end="", flush=True)
|
209
|
-
print("\b" * length, end="", flush=True)
|
210
|
-
print("\x1b[0m", end="", flush=True)
|
211
|
-
if newline:
|
212
|
-
print()
|
213
|
-
break
|
214
|
+
def __create_loading_indicator(self):
|
215
|
+
return ng.loading.kana()
|
214
216
|
|
215
|
-
async def __render_streamed_markdown(
|
216
|
-
self, stream: MessageStream, loading: asyncio.Task[None] | None = None
|
217
|
-
):
|
217
|
+
async def __render_streamed_markdown(self, stream: MessageStream):
|
218
218
|
if sys.stdout.isatty():
|
219
|
-
|
220
|
-
chunks = aiter(stream)
|
221
|
-
buf = ""
|
222
|
-
while len(buf) < 8:
|
223
|
-
try:
|
224
|
-
buf += await anext(chunks)
|
225
|
-
except StopAsyncIteration:
|
226
|
-
if len(buf) == 0:
|
227
|
-
if loading:
|
228
|
-
loading.cancel()
|
229
|
-
await loading
|
230
|
-
return False
|
231
|
-
break
|
232
|
-
if loading:
|
233
|
-
loading.cancel()
|
234
|
-
await loading
|
235
|
-
|
236
|
-
content = {"v": ""}
|
237
|
-
|
238
|
-
async def gen():
|
239
|
-
content["v"] = buf
|
240
|
-
if buf:
|
241
|
-
yield buf
|
242
|
-
while True:
|
243
|
-
try:
|
244
|
-
s = await anext(chunks)
|
245
|
-
content["v"] += s
|
246
|
-
for c in s:
|
247
|
-
yield c
|
248
|
-
except StopAsyncIteration:
|
249
|
-
break
|
250
|
-
|
251
|
-
await stream_md(gen())
|
219
|
+
await ng.stream.markdown(aiter(stream))
|
252
220
|
return True
|
253
221
|
else:
|
254
|
-
has_content = False
|
255
222
|
async for chunk in stream:
|
256
|
-
if chunk == "":
|
257
|
-
continue
|
258
|
-
has_content = True
|
259
223
|
print(chunk, end="", flush=True)
|
260
|
-
return has_content
|
@@ -1,12 +1,14 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: autosh
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.4
|
4
4
|
Summary: Add your description here
|
5
5
|
License-File: LICENSE
|
6
6
|
Requires-Python: >=3.13
|
7
|
-
Requires-Dist: agentia>=0.0.
|
7
|
+
Requires-Dist: agentia>=0.0.7
|
8
8
|
Requires-Dist: asyncio>=3.4.3
|
9
9
|
Requires-Dist: markdownify>=1.1.0
|
10
|
+
Requires-Dist: neongrid
|
11
|
+
Requires-Dist: prompt-toolkit>=3.0.51
|
10
12
|
Requires-Dist: pydantic>=2.11.3
|
11
13
|
Requires-Dist: python-dotenv>=1.1.0
|
12
14
|
Requires-Dist: rich>=14.0.0
|
@@ -0,0 +1,17 @@
|
|
1
|
+
autosh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
autosh/config-template.toml,sha256=iLdCBHIK0czWgNHtwAgvuhV670aiNc-IOmaPHP12i0Y,269
|
3
|
+
autosh/config.py,sha256=7DXUSsqHm8dPsXln-NTu8D0_Uwcs2h4sT8pFtQM8tso,2654
|
4
|
+
autosh/main.py,sha256=bgWkYpU9cmyU2AyWt4o8Mcj6LH_UGYSYrFx4Qkl8vqk,5117
|
5
|
+
autosh/session.py,sha256=fOJySGU9abjFReNOyvQf-s9rFQdGy8Pv01GgplqrRuw,7943
|
6
|
+
autosh/plugins/__init__.py,sha256=oUtO7M1AuZKshbaS-kO0fbV4CYal0uOvg-GpYVFE16I,2996
|
7
|
+
autosh/plugins/calc.py,sha256=x_fZW3kkStlAAQEhejcD2_MQnpqCd5ghU72CNSjqIsg,672
|
8
|
+
autosh/plugins/cli.py,sha256=G4WqTWeCi7OxdB36lAUiR1OBdtSvxmKRcQGGLdMcxms,9230
|
9
|
+
autosh/plugins/clock.py,sha256=gIrYBz3GmnAJTujzd3B8ewuThXpF-OvWYBugcZMYGTQ,570
|
10
|
+
autosh/plugins/code.py,sha256=HK7cd7gNit2yZAukaCnTGXb6BAh31cPh0EHCx6msOWc,2457
|
11
|
+
autosh/plugins/search.py,sha256=aWbIDJ7E5m-Xrw2A1DB6w53oGiFe1Uc3EuZikIH5eTs,2984
|
12
|
+
autosh/plugins/web.py,sha256=LxE7ed7IS9B4spWCm-3aBMLfC9ghcNnmJzMiD6ayLdc,2648
|
13
|
+
autosh-0.0.4.dist-info/METADATA,sha256=cv69lLqKHEFwz5AZWEmmL0scvhzYv8-ECPdEiRzOaus,1959
|
14
|
+
autosh-0.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
15
|
+
autosh-0.0.4.dist-info/entry_points.txt,sha256=BV7bzUnxG6Z5InEkrfajGCxjooYORC5tZDDZctOPenQ,67
|
16
|
+
autosh-0.0.4.dist-info/licenses/LICENSE,sha256=BnLDJsIJe-Dm18unR9DOoSv7QOfAz6LeIQc1yHAjxp0,1066
|
17
|
+
autosh-0.0.4.dist-info/RECORD,,
|
autosh/md/__init__.py
DELETED
autosh/md/inline_text.py
DELETED
@@ -1,214 +0,0 @@
|
|
1
|
-
from dataclasses import dataclass, field
|
2
|
-
from autosh.md.printer import StreamedMarkdownPrinter
|
3
|
-
from autosh.md.state import State
|
4
|
-
|
5
|
-
|
6
|
-
@dataclass
|
7
|
-
class Keyword:
|
8
|
-
token: str
|
9
|
-
|
10
|
-
def is_bold(self) -> bool:
|
11
|
-
return self.token[0] in ["*", "_"] and len(self.token) == 2
|
12
|
-
|
13
|
-
def is_italic(self) -> bool:
|
14
|
-
return self.token[0] in ["*", "_"] and len(self.token) == 1
|
15
|
-
|
16
|
-
def is_bold_and_italic(self) -> bool:
|
17
|
-
return self.token[0] in ["*", "_"] and len(self.token) > 2
|
18
|
-
|
19
|
-
def is_bold_or_italic(self) -> bool:
|
20
|
-
return self.token[0] in ["*", "_"]
|
21
|
-
|
22
|
-
|
23
|
-
class InvalidState(Exception):
|
24
|
-
def __init__(self, token: str):
|
25
|
-
super().__init__(token)
|
26
|
-
self.token = token
|
27
|
-
|
28
|
-
|
29
|
-
@dataclass
|
30
|
-
class InlineScope:
|
31
|
-
state: State
|
32
|
-
|
33
|
-
stack: list[tuple[Keyword, State]] = field(default_factory=list)
|
34
|
-
|
35
|
-
@property
|
36
|
-
def last(self) -> str | None:
|
37
|
-
return self.stack[-1][0].token if len(self.stack) > 0 else None
|
38
|
-
|
39
|
-
@property
|
40
|
-
def strike(self) -> bool:
|
41
|
-
return self.last == "~~"
|
42
|
-
|
43
|
-
@property
|
44
|
-
def code(self) -> bool:
|
45
|
-
return self.last == "`"
|
46
|
-
|
47
|
-
def enter(self, kind: Keyword):
|
48
|
-
match kind:
|
49
|
-
case "`":
|
50
|
-
state = self.state._enter_scope(dim=True)
|
51
|
-
case "~~":
|
52
|
-
state = self.state._enter_scope(strike=True)
|
53
|
-
case _ if kind.is_bold():
|
54
|
-
state = self.state._enter_scope(bold=True)
|
55
|
-
case _ if kind.is_italic():
|
56
|
-
state = self.state._enter_scope(italic=True)
|
57
|
-
case _ if kind.is_bold_and_italic():
|
58
|
-
state = self.state._enter_scope(bold=True, italic=True)
|
59
|
-
case _:
|
60
|
-
state = self.state._enter_scope()
|
61
|
-
self.stack.append((kind, state))
|
62
|
-
|
63
|
-
def exit(self):
|
64
|
-
t = self.stack.pop()
|
65
|
-
state = t[1]
|
66
|
-
self.state._exit_scope(state)
|
67
|
-
|
68
|
-
|
69
|
-
class InlineTextPrinter:
|
70
|
-
def __init__(self, p: StreamedMarkdownPrinter, table: bool = False):
|
71
|
-
self.p = p
|
72
|
-
self.terminator = ["\n", None]
|
73
|
-
if table:
|
74
|
-
self.terminator.append("|")
|
75
|
-
|
76
|
-
def emit(self, s: str):
|
77
|
-
self.p.emit(s)
|
78
|
-
|
79
|
-
def peek(self):
|
80
|
-
return self.p.peek()
|
81
|
-
|
82
|
-
async def consume(self, n: int = 1):
|
83
|
-
return await self.p.consume(n)
|
84
|
-
|
85
|
-
async def check(self, s: str, eof: bool | None = None) -> bool:
|
86
|
-
return await self.p.check(s, eof)
|
87
|
-
|
88
|
-
async def parse_inline_unformatted(self):
|
89
|
-
# Parse until another "`" or a newline, or EOF
|
90
|
-
while not self.peek() in self.terminator:
|
91
|
-
c = await self.p.consume()
|
92
|
-
self.emit(c)
|
93
|
-
|
94
|
-
async def parse_inline_code(self):
|
95
|
-
self.emit("`")
|
96
|
-
# Parse until another "`" or a newline, or EOF
|
97
|
-
while not await self.check("`"):
|
98
|
-
c = await self.p.consume()
|
99
|
-
if c in self.terminator:
|
100
|
-
return
|
101
|
-
self.emit(c)
|
102
|
-
if c == "\\":
|
103
|
-
c = await self.p.consume()
|
104
|
-
if c in self.terminator:
|
105
|
-
return
|
106
|
-
self.emit(c)
|
107
|
-
self.emit("`")
|
108
|
-
await self.p.consume()
|
109
|
-
|
110
|
-
async def get_next_token(self) -> str | Keyword | None:
|
111
|
-
c = self.peek()
|
112
|
-
if c in self.terminator:
|
113
|
-
return None
|
114
|
-
if c == "`":
|
115
|
-
await self.consume()
|
116
|
-
return Keyword("`")
|
117
|
-
if c == "*":
|
118
|
-
s = ""
|
119
|
-
while self.peek() == "*":
|
120
|
-
s += c
|
121
|
-
await self.consume()
|
122
|
-
return Keyword(s)
|
123
|
-
if c == "_":
|
124
|
-
s = ""
|
125
|
-
while self.peek() == "_":
|
126
|
-
s += c
|
127
|
-
await self.consume()
|
128
|
-
return Keyword(s)
|
129
|
-
if c == "~" and await self.check("~~"):
|
130
|
-
s = ""
|
131
|
-
while self.peek() == "~":
|
132
|
-
s += c
|
133
|
-
await self.consume()
|
134
|
-
return Keyword(s)
|
135
|
-
if c == " " or c == "\t":
|
136
|
-
s = ""
|
137
|
-
while self.peek() == " " or self.peek() == "\t":
|
138
|
-
s += c
|
139
|
-
await self.consume()
|
140
|
-
return s
|
141
|
-
s = c or ""
|
142
|
-
if c == "\\":
|
143
|
-
await self.consume()
|
144
|
-
c = self.peek()
|
145
|
-
if c in self.terminator:
|
146
|
-
return None if len(s) == 0 else s
|
147
|
-
s += c or ""
|
148
|
-
await self.consume()
|
149
|
-
else:
|
150
|
-
await self.consume()
|
151
|
-
if len(s) == 0:
|
152
|
-
return None
|
153
|
-
return s
|
154
|
-
|
155
|
-
async def parse_inline(self, consume_trailing_newline: bool = True):
|
156
|
-
last_is_space = False
|
157
|
-
start = True
|
158
|
-
scope = InlineScope(self.p.state)
|
159
|
-
|
160
|
-
while True:
|
161
|
-
t = await self.get_next_token()
|
162
|
-
curr_is_space = False
|
163
|
-
|
164
|
-
if t is None:
|
165
|
-
if consume_trailing_newline:
|
166
|
-
if self.peek() == "\n":
|
167
|
-
await self.consume()
|
168
|
-
self.emit("\n")
|
169
|
-
return
|
170
|
-
elif isinstance(t, str):
|
171
|
-
# Space or normal text
|
172
|
-
if t[0] == " ":
|
173
|
-
curr_is_space = True
|
174
|
-
self.emit(" ")
|
175
|
-
else:
|
176
|
-
self.emit(t)
|
177
|
-
elif t.token == "`":
|
178
|
-
# Inline code
|
179
|
-
scope.enter(t)
|
180
|
-
with self.p.state.style(dim=True):
|
181
|
-
await self.parse_inline_code()
|
182
|
-
scope.exit()
|
183
|
-
elif t.token == "~~":
|
184
|
-
# Strike through
|
185
|
-
if not scope.strike:
|
186
|
-
scope.enter(t)
|
187
|
-
self.emit("~~")
|
188
|
-
else:
|
189
|
-
self.emit("~~")
|
190
|
-
scope.exit()
|
191
|
-
elif (
|
192
|
-
t.is_bold_or_italic()
|
193
|
-
and (last_is_space or start)
|
194
|
-
and scope.last != t.token
|
195
|
-
):
|
196
|
-
# Start bold or italics
|
197
|
-
scope.enter(t)
|
198
|
-
self.emit(t.token)
|
199
|
-
elif (
|
200
|
-
t.is_bold_or_italic()
|
201
|
-
and self.peek() in [" ", "\t", *self.terminator]
|
202
|
-
and scope.last == t.token
|
203
|
-
):
|
204
|
-
# End bold or italics
|
205
|
-
self.emit(t.token)
|
206
|
-
scope.exit()
|
207
|
-
else:
|
208
|
-
# print(
|
209
|
-
# "Invalid token:", t.token, f"[{self.peek()}]", scope.last == t.token
|
210
|
-
# )
|
211
|
-
raise InvalidState(t.token)
|
212
|
-
|
213
|
-
last_is_space = curr_is_space
|
214
|
-
start = False
|