@oracle-agent/oracle 0.8.0 → 0.9.1
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.
- package/README.md +6 -4
- package/SETUP.md +1 -0
- package/docs/cli.md +2 -2
- package/package.json +1 -1
- package/protocols/templates/safe-erc20/README.md +9 -0
- package/public/oracle-splash/assets/llms/gemini-icon.svg +1 -0
- package/public/oracle-splash/assets/wordmarks/hop-icon.webp +0 -0
- package/public/oracle-splash/assets/wordmarks/satflow-icon.png +0 -0
- package/public/oracle-splash/assets/wordmarks/zerox-icon.webp +0 -0
- package/public/oracle-splash/favicon.ico +0 -0
- package/public/oracle-splash/favicon.svg +6 -0
- package/public/oracle-splash/index.html +105 -69
- package/skills/oracle-chat/setup.SKILL.md +2 -2
- package/skins/oracle.yaml +45 -41
- package/src/cli/commands/bootstrap.mjs +87 -0
- package/src/cli/commands/chat.mjs +140 -25
- package/src/cli/commands/doctor.mjs +5 -10
- package/src/cli/commands/model.mjs +19 -13
- package/src/cli/commands/setup.mjs +11 -19
- package/src/cli/first-run.mjs +2 -2
- package/src/cli/kernel.mjs +5 -4
- package/src/cli/messaging-platforms.mjs +1 -1
- package/src/cli/oracle-harness.py +271 -69
- package/src/cli/runtime.mjs +351 -0
|
@@ -1,23 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
1
2
|
import importlib
|
|
2
3
|
import importlib.abc
|
|
3
4
|
import importlib.machinery
|
|
4
|
-
import
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
5
9
|
import sys
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
11
|
+
|
|
12
|
+
WORDMARK = (
|
|
13
|
+
" ████ ",
|
|
14
|
+
" ░░███ ",
|
|
15
|
+
" ██████ ████████ ██████ ██████ ░███ ██████ ",
|
|
16
|
+
" ███░░███░░███░░███ ░░░░░███ ███░░███ ░███ ███░░███",
|
|
17
|
+
"░███ ░███ ░███ ░░░ ███████ ░███ ░░░ ░███ ░███████ ",
|
|
18
|
+
"░███ ░███ ░███ ███░░███ ░███ ███ ░███ ░███░░░ ",
|
|
19
|
+
"░░██████ █████ ░░████████░░██████ █████░░██████ ",
|
|
20
|
+
" ░░░░░░ ░░░░░ ░░░░░░░░ ░░░░░░ ░░░░░ ░░░░░░ ",
|
|
12
21
|
)
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
SILENT_OUTPUT = {
|
|
15
24
|
"[anthropic_billing_bypass] Bypass installed",
|
|
16
25
|
"[anthropic_billing_bypass] Transport unwrap hook installed",
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
def _oracle_chain(command):
|
|
30
|
+
node = os.environ.get("ORACLE_NODE_BIN")
|
|
31
|
+
entry = os.environ.get("ORACLE_CLI_ENTRY")
|
|
32
|
+
if not node or not entry:
|
|
33
|
+
return False, "oracle chain is unavailable in this session"
|
|
34
|
+
|
|
35
|
+
parts = command.strip().split()[1:]
|
|
36
|
+
args = parts or ["list"]
|
|
37
|
+
try:
|
|
38
|
+
result = subprocess.run(
|
|
39
|
+
[node, entry, "chain", *args],
|
|
40
|
+
capture_output=True,
|
|
41
|
+
text=True,
|
|
42
|
+
timeout=15,
|
|
43
|
+
check=False,
|
|
44
|
+
)
|
|
45
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
46
|
+
return False, f"oracle chain failed: {exc}"
|
|
47
|
+
|
|
48
|
+
output = "\n".join(
|
|
49
|
+
part.strip() for part in (result.stdout, result.stderr) if part.strip()
|
|
50
|
+
)
|
|
51
|
+
if result.returncode != 0:
|
|
52
|
+
return False, output or "oracle chain failed"
|
|
53
|
+
|
|
54
|
+
state = subprocess.run(
|
|
55
|
+
[node, entry, "chain", "show", "--json"],
|
|
56
|
+
capture_output=True,
|
|
57
|
+
text=True,
|
|
58
|
+
timeout=15,
|
|
59
|
+
check=False,
|
|
60
|
+
)
|
|
61
|
+
if state.returncode in (0, 1):
|
|
62
|
+
try:
|
|
63
|
+
active = json.loads(state.stdout).get("active") or {}
|
|
64
|
+
if active:
|
|
65
|
+
os.environ["ORACLE_ACTIVE_CHAIN"] = str(active.get("key", ""))
|
|
66
|
+
os.environ["ORACLE_ACTIVE_CHAIN_ID"] = str(active.get("chainId", ""))
|
|
67
|
+
os.environ["ORACLE_ACTIVE_AGENT"] = str(active.get("agent", ""))
|
|
68
|
+
else:
|
|
69
|
+
os.environ.pop("ORACLE_ACTIVE_CHAIN", None)
|
|
70
|
+
os.environ.pop("ORACLE_ACTIVE_CHAIN_ID", None)
|
|
71
|
+
os.environ.pop("ORACLE_ACTIVE_AGENT", None)
|
|
72
|
+
except (TypeError, ValueError):
|
|
73
|
+
pass
|
|
74
|
+
return True, output
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class OracleOutput:
|
|
21
78
|
def __init__(self, wrapped):
|
|
22
79
|
self.wrapped = wrapped
|
|
23
80
|
|
|
@@ -25,8 +82,12 @@ class OracleStderr:
|
|
|
25
82
|
kept = "".join(
|
|
26
83
|
line
|
|
27
84
|
for line in data.splitlines(keepends=True)
|
|
28
|
-
if line.rstrip("\r\n") not in
|
|
85
|
+
if line.rstrip("\r\n") not in SILENT_OUTPUT
|
|
29
86
|
)
|
|
87
|
+
kept = kept.replace("hermes model", "oracle model")
|
|
88
|
+
kept = kept.replace("hermes --resume", "oracle --resume")
|
|
89
|
+
kept = kept.replace("hermes -c", "oracle -c")
|
|
90
|
+
kept = kept.replace(" in ~/.hermes/.env", " through oracle model")
|
|
30
91
|
if kept:
|
|
31
92
|
self.wrapped.write(kept)
|
|
32
93
|
return len(data)
|
|
@@ -39,74 +100,213 @@ class OracleStderr:
|
|
|
39
100
|
|
|
40
101
|
|
|
41
102
|
def patch_cli(module):
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def cprint(text="", *args, **kwargs):
|
|
50
|
-
if frame_filter[0] and isinstance(text, str):
|
|
51
|
-
plain = re.sub(r"\x1b\[[0-9;]*m", "", text).lstrip("\n")
|
|
52
|
-
if plain.startswith("\u256d\u2500oracle") or plain.startswith("\u2570"):
|
|
53
|
-
return None
|
|
54
|
-
return original_cprint(text, *args, **kwargs)
|
|
55
|
-
|
|
56
|
-
setattr(module, "_cprint", cprint)
|
|
103
|
+
cls = getattr(module, "HermesCLI", None)
|
|
104
|
+
if cls is None or getattr(cls, "_oracle_patched", False):
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
original_style = getattr(cls, "_build_tui_style_dict", None)
|
|
108
|
+
original_process_command = cls.process_command
|
|
57
109
|
|
|
58
110
|
def show_banner(self):
|
|
111
|
+
from rich.align import Align
|
|
112
|
+
from rich.console import Group
|
|
113
|
+
from rich.panel import Panel
|
|
114
|
+
from rich.text import Text
|
|
115
|
+
from rich import box
|
|
116
|
+
|
|
59
117
|
self.console.clear()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
118
|
+
width = shutil.get_terminal_size((100, 28)).columns
|
|
119
|
+
body = Text(justify="center")
|
|
120
|
+
if width >= 72:
|
|
121
|
+
colors = (
|
|
122
|
+
"#B8F0FF",
|
|
123
|
+
"#B8F0FF",
|
|
124
|
+
"#ACDEEF",
|
|
125
|
+
"#A5D9EB",
|
|
126
|
+
"#9FCBDD",
|
|
127
|
+
"#9FCBDD",
|
|
128
|
+
"#ACDEEF",
|
|
129
|
+
"#B8F0FF",
|
|
130
|
+
)
|
|
131
|
+
for index, line in enumerate(WORDMARK):
|
|
132
|
+
body.append(line.rstrip(), style=f"bold {colors[index]}")
|
|
133
|
+
if index != len(WORDMARK) - 1:
|
|
134
|
+
body.append("\n")
|
|
135
|
+
else:
|
|
136
|
+
body.append("oracle", style="bold #B8F0FF")
|
|
137
|
+
|
|
138
|
+
body.append("\n\nTHE FUTURE IS AGENTIC", style="bold #EAF2F8")
|
|
139
|
+
body.append(" / ", style="#52606D")
|
|
140
|
+
model = str(getattr(self, "model", "") or "choose model")
|
|
141
|
+
body.append(model, style="#91A2B1")
|
|
142
|
+
|
|
143
|
+
panel_width = max(36, min(width - 4, 76))
|
|
144
|
+
panel = Panel(
|
|
145
|
+
Group(Align.center(body)),
|
|
146
|
+
width=panel_width,
|
|
147
|
+
padding=(1, 2),
|
|
148
|
+
border_style="#3B4A56",
|
|
149
|
+
box=box.ROUNDED,
|
|
150
|
+
subtitle="[dim #6F7F8D]/model /chain /setup[/]",
|
|
151
|
+
subtitle_align="right",
|
|
152
|
+
)
|
|
153
|
+
self.console.print(Align.center(panel))
|
|
154
|
+
self.console.print()
|
|
64
155
|
|
|
65
156
|
def prompt_fragments(self):
|
|
66
|
-
if
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
first = not self._stream_box_opened
|
|
84
|
-
if first:
|
|
85
|
-
original_cprint()
|
|
86
|
-
frame_filter[0] += 1
|
|
87
|
-
try:
|
|
88
|
-
return original_emit(self, text)
|
|
89
|
-
finally:
|
|
90
|
-
frame_filter[0] -= 1
|
|
157
|
+
if getattr(self, "_voice_recording", False):
|
|
158
|
+
return [("class:voice-recording", "recording ")]
|
|
159
|
+
if getattr(self, "_voice_processing", False):
|
|
160
|
+
return [("class:voice-processing", "transcribing ")]
|
|
161
|
+
if getattr(self, "_sudo_state", None) or getattr(self, "_secret_state", None):
|
|
162
|
+
return [("class:sudo-prompt", "secure › ")]
|
|
163
|
+
if getattr(self, "_approval_state", None) or getattr(self, "_slash_confirm_state", None):
|
|
164
|
+
return [("class:prompt-working", "confirm › ")]
|
|
165
|
+
if getattr(self, "_clarify_freetext", False):
|
|
166
|
+
return [("class:clarify-selected", "answer › ")]
|
|
167
|
+
if getattr(self, "_clarify_state", None):
|
|
168
|
+
return [("class:prompt-working", "choose › ")]
|
|
169
|
+
if getattr(self, "_command_running", False):
|
|
170
|
+
return [("class:prompt-working", "working ")]
|
|
171
|
+
if getattr(self, "_agent_running", False):
|
|
172
|
+
return [("class:prompt-working", "thinking ")]
|
|
173
|
+
return [("class:prompt", " Ask Oracle › ")]
|
|
91
174
|
|
|
92
|
-
def
|
|
93
|
-
|
|
175
|
+
def status_fragments(self):
|
|
176
|
+
if not getattr(self, "_status_bar_visible", True) or getattr(self, "_model_picker_state", None):
|
|
177
|
+
return []
|
|
94
178
|
try:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
179
|
+
snapshot = self._get_status_bar_snapshot()
|
|
180
|
+
width = self._get_tui_terminal_width()
|
|
181
|
+
model = snapshot.get("model_short") or "model"
|
|
182
|
+
percent = snapshot.get("context_percent")
|
|
183
|
+
context = f"{percent}% context" if percent is not None else "context ready"
|
|
184
|
+
duration = snapshot.get("duration") or "0s"
|
|
185
|
+
if width < 58:
|
|
186
|
+
return [
|
|
187
|
+
("class:status-bar", " "),
|
|
188
|
+
("class:status-bar-strong", model),
|
|
189
|
+
("class:status-bar-dim", f" · {duration} "),
|
|
190
|
+
]
|
|
191
|
+
return [
|
|
192
|
+
("class:status-bar", " "),
|
|
193
|
+
("class:status-bar-brand", "oracle"),
|
|
194
|
+
("class:status-bar-dim", " / "),
|
|
195
|
+
("class:status-bar-strong", model),
|
|
196
|
+
("class:status-bar-dim", " / "),
|
|
197
|
+
("class:status-bar-context", context),
|
|
198
|
+
("class:status-bar-dim", " / "),
|
|
199
|
+
("class:status-bar-dim", f"{duration} "),
|
|
200
|
+
]
|
|
201
|
+
except Exception:
|
|
202
|
+
return [("class:status-bar-brand", " oracle ")]
|
|
98
203
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
204
|
+
def layout_children(
|
|
205
|
+
self,
|
|
206
|
+
*,
|
|
207
|
+
sudo_widget,
|
|
208
|
+
secret_widget,
|
|
209
|
+
approval_widget,
|
|
210
|
+
slash_confirm_widget=None,
|
|
211
|
+
clarify_widget,
|
|
212
|
+
model_picker_widget=None,
|
|
213
|
+
spinner_widget=None,
|
|
214
|
+
spacer,
|
|
215
|
+
status_bar,
|
|
216
|
+
input_rule_top,
|
|
217
|
+
image_bar,
|
|
218
|
+
input_area,
|
|
219
|
+
input_rule_bot,
|
|
220
|
+
voice_status_bar,
|
|
221
|
+
completions_menu,
|
|
222
|
+
):
|
|
223
|
+
from prompt_toolkit.layout.containers import HSplit, VSplit, Window
|
|
224
|
+
from prompt_toolkit.widgets import Frame
|
|
225
|
+
from prompt_toolkit.widgets.base import Border
|
|
105
226
|
|
|
106
|
-
|
|
107
|
-
|
|
227
|
+
Border.TOP_LEFT = "╭"
|
|
228
|
+
Border.TOP_RIGHT = "╮"
|
|
229
|
+
Border.BOTTOM_LEFT = "╰"
|
|
230
|
+
Border.BOTTOM_RIGHT = "╯"
|
|
108
231
|
|
|
109
|
-
|
|
232
|
+
composer = VSplit(
|
|
233
|
+
[
|
|
234
|
+
Window(width=1),
|
|
235
|
+
Frame(
|
|
236
|
+
HSplit([item for item in (image_bar, input_area) if item is not None]),
|
|
237
|
+
style="class:oracle-composer",
|
|
238
|
+
),
|
|
239
|
+
Window(width=1),
|
|
240
|
+
]
|
|
241
|
+
)
|
|
242
|
+
return [
|
|
243
|
+
item
|
|
244
|
+
for item in (
|
|
245
|
+
Window(height=0),
|
|
246
|
+
sudo_widget,
|
|
247
|
+
secret_widget,
|
|
248
|
+
approval_widget,
|
|
249
|
+
slash_confirm_widget,
|
|
250
|
+
clarify_widget,
|
|
251
|
+
model_picker_widget,
|
|
252
|
+
spinner_widget,
|
|
253
|
+
spacer,
|
|
254
|
+
composer,
|
|
255
|
+
completions_menu,
|
|
256
|
+
voice_status_bar,
|
|
257
|
+
status_bar,
|
|
258
|
+
)
|
|
259
|
+
if item is not None
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
def build_style(self):
|
|
263
|
+
style = original_style(self) if original_style else {}
|
|
264
|
+
style.update(
|
|
265
|
+
{
|
|
266
|
+
"frame": "bg:#0B0E11 #EAF2F8",
|
|
267
|
+
"frame.border": "#455867",
|
|
268
|
+
"frame.label": "bold #B8F0FF",
|
|
269
|
+
"oracle-composer": "bg:#0B0E11 #EAF2F8",
|
|
270
|
+
"input-area": "bg:#0B0E11 #EAF2F8",
|
|
271
|
+
"prompt": "bold #B8F0FF",
|
|
272
|
+
"prompt-working": "bold #91C2D7",
|
|
273
|
+
"status-bar": "bg:#11161B #91A2B1",
|
|
274
|
+
"status-bar-brand": "bg:#11161B bold #B8F0FF",
|
|
275
|
+
"status-bar-strong": "bg:#11161B #EAF2F8",
|
|
276
|
+
"status-bar-context": "bg:#11161B #A5D9EB",
|
|
277
|
+
"status-bar-dim": "bg:#11161B #60717F",
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
return style
|
|
281
|
+
|
|
282
|
+
def process_command(self, command):
|
|
283
|
+
parts = command.strip().lower().split(maxsplit=1)
|
|
284
|
+
if parts and parts[0] == "/chain":
|
|
285
|
+
ok, output = _oracle_chain(command)
|
|
286
|
+
prefix = "" if ok else "error: "
|
|
287
|
+
lines = output.splitlines() or [""]
|
|
288
|
+
for index, line in enumerate(lines):
|
|
289
|
+
module._cprint(f"{prefix if index == 0 else ''}{line}")
|
|
290
|
+
return True
|
|
291
|
+
return original_process_command(self, command)
|
|
292
|
+
|
|
293
|
+
setattr(cls, "show_banner", show_banner)
|
|
294
|
+
setattr(cls, "_get_tui_prompt_fragments", prompt_fragments)
|
|
295
|
+
setattr(cls, "_get_status_bar_fragments", status_fragments)
|
|
296
|
+
setattr(cls, "_build_tui_layout_children", layout_children)
|
|
297
|
+
setattr(cls, "_build_tui_style_dict", build_style)
|
|
298
|
+
setattr(cls, "process_command", process_command)
|
|
299
|
+
setattr(cls, "_oracle_patched", True)
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
tips = importlib.import_module("hermes_cli.tips")
|
|
303
|
+
|
|
304
|
+
def no_tip():
|
|
305
|
+
raise LookupError("oracle quiet startup")
|
|
306
|
+
|
|
307
|
+
setattr(tips, "get_random_tip", no_tip)
|
|
308
|
+
except Exception:
|
|
309
|
+
pass
|
|
110
310
|
|
|
111
311
|
|
|
112
312
|
class OracleLoader(importlib.abc.Loader):
|
|
@@ -138,9 +338,11 @@ class OracleFinder(importlib.abc.MetaPathFinder):
|
|
|
138
338
|
|
|
139
339
|
|
|
140
340
|
def main():
|
|
141
|
-
sys.
|
|
341
|
+
sys.stdout = OracleOutput(sys.stdout)
|
|
342
|
+
sys.stderr = OracleOutput(sys.stderr)
|
|
142
343
|
sys.meta_path.insert(0, OracleFinder())
|
|
143
|
-
|
|
344
|
+
from hermes_cli.main import main as hermes_main
|
|
345
|
+
|
|
144
346
|
return hermes_main()
|
|
145
347
|
|
|
146
348
|
|