@oracle-agent/oracle 0.5.0 → 0.7.0

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 (93) hide show
  1. package/README.md +10 -1
  2. package/bin/oracle-data-mcp.mjs +32 -0
  3. package/docs/cli.md +28 -0
  4. package/package.json +3 -3
  5. package/profiles/oracle/SOUL.md +10 -4
  6. package/public/oracle-splash/assets/hero/orb.avif +0 -0
  7. package/public/oracle-splash/assets/hero/orb.webm +0 -0
  8. package/public/oracle-splash/assets/llms/claude.svg +1 -0
  9. package/public/oracle-splash/assets/llms/codex.svg +1 -0
  10. package/public/oracle-splash/assets/llms/cursor-icon.svg +12 -0
  11. package/public/oracle-splash/assets/llms/cursor.svg +17 -0
  12. package/public/oracle-splash/assets/llms/deepseek.svg +1 -0
  13. package/public/oracle-splash/assets/llms/gemini.svg +1 -0
  14. package/public/oracle-splash/assets/llms/grok.svg +8 -0
  15. package/public/oracle-splash/assets/llms/hermes.png +0 -0
  16. package/public/oracle-splash/assets/llms/kimi-icon.svg +1 -0
  17. package/public/oracle-splash/assets/llms/kimi.svg +1 -0
  18. package/public/oracle-splash/assets/llms/manus.svg +18 -0
  19. package/public/oracle-splash/assets/llms/openclaw.svg +22 -0
  20. package/public/oracle-splash/assets/llms/perplexity.svg +1 -0
  21. package/public/oracle-splash/assets/llms/qwen.svg +1 -0
  22. package/public/oracle-splash/assets/llms/windsurf.svg +1 -0
  23. package/public/oracle-splash/assets/protocols/abcdex.png +0 -0
  24. package/public/oracle-splash/assets/protocols/across.png +0 -0
  25. package/public/oracle-splash/assets/protocols/aerodrome.png +0 -0
  26. package/public/oracle-splash/assets/protocols/balancer.png +0 -0
  27. package/public/oracle-splash/assets/protocols/cowswap.png +0 -0
  28. package/public/oracle-splash/assets/protocols/curve.png +0 -0
  29. package/public/oracle-splash/assets/protocols/dreamcash.png +0 -0
  30. package/public/oracle-splash/assets/protocols/felix.png +0 -0
  31. package/public/oracle-splash/assets/protocols/gmx.png +0 -0
  32. package/public/oracle-splash/assets/protocols/hyena.png +0 -0
  33. package/public/oracle-splash/assets/protocols/hyperbeat.png +0 -0
  34. package/public/oracle-splash/assets/protocols/hyperliquid.png +0 -0
  35. package/public/oracle-splash/assets/protocols/hyperswap.png +0 -0
  36. package/public/oracle-splash/assets/protocols/jupiter.png +0 -0
  37. package/public/oracle-splash/assets/protocols/kinetiq.png +0 -0
  38. package/public/oracle-splash/assets/protocols/lfj.png +0 -0
  39. package/public/oracle-splash/assets/protocols/lifi.png +0 -0
  40. package/public/oracle-splash/assets/protocols/magic-eden.png +0 -0
  41. package/public/oracle-splash/assets/protocols/morpho.png +0 -0
  42. package/public/oracle-splash/assets/protocols/odos.png +0 -0
  43. package/public/oracle-splash/assets/protocols/oneinch.png +0 -0
  44. package/public/oracle-splash/assets/protocols/opensea.png +0 -0
  45. package/public/oracle-splash/assets/protocols/pancakeswap.png +0 -0
  46. package/public/oracle-splash/assets/protocols/paragon.png +0 -0
  47. package/public/oracle-splash/assets/protocols/paraswap.png +0 -0
  48. package/public/oracle-splash/assets/protocols/pendle.png +0 -0
  49. package/public/oracle-splash/assets/protocols/polymarket.png +0 -0
  50. package/public/oracle-splash/assets/protocols/quickswap.png +0 -0
  51. package/public/oracle-splash/assets/protocols/relay.png +0 -0
  52. package/public/oracle-splash/assets/protocols/stargate.png +0 -0
  53. package/public/oracle-splash/assets/protocols/tradexyz.png +0 -0
  54. package/public/oracle-splash/assets/protocols/uniswap.png +0 -0
  55. package/public/oracle-splash/assets/protocols/velodrome.png +0 -0
  56. package/public/oracle-splash/assets/protocols/ventuals.png +0 -0
  57. package/public/oracle-splash/assets/wordmarks/cowswap.svg +1 -1
  58. package/public/oracle-splash/assets/wordmarks/curve.png +0 -0
  59. package/public/oracle-splash/assets/wordmarks/gmx.svg +1 -1
  60. package/public/oracle-splash/assets/wordmarks/hyena.svg +7 -0
  61. package/public/oracle-splash/assets/wordmarks/kinetiq.svg +1 -0
  62. package/public/oracle-splash/assets/wordmarks/lfj.webp +0 -0
  63. package/public/oracle-splash/assets/wordmarks/magic-eden.svg +1 -1
  64. package/public/oracle-splash/assets/wordmarks/morpho.svg +1 -1
  65. package/public/oracle-splash/assets/wordmarks/odos.svg +1 -1
  66. package/public/oracle-splash/assets/wordmarks/opensea.svg +1 -1
  67. package/public/oracle-splash/assets/wordmarks/outcome.svg +16 -0
  68. package/public/oracle-splash/assets/wordmarks/paragon.svg +9 -0
  69. package/public/oracle-splash/assets/wordmarks/paraswap.svg +1 -1
  70. package/public/oracle-splash/assets/wordmarks/xyz.svg +12 -0
  71. package/public/oracle-splash/index.html +156 -184
  72. package/skills/oracle-chat/SKILL.md +61 -0
  73. package/skills/oracle-chat/chain.SKILL.md +31 -0
  74. package/skills/oracle-chat/setup.SKILL.md +37 -0
  75. package/skins/oracle.yaml +52 -0
  76. package/src/cli/chain-catalog.mjs +215 -0
  77. package/src/cli/chain-state.mjs +74 -0
  78. package/src/cli/commands/chain.mjs +143 -0
  79. package/src/cli/commands/chat.mjs +268 -0
  80. package/src/cli/commands/model.mjs +37 -0
  81. package/src/cli/commands/setup.mjs +283 -0
  82. package/src/cli/first-run.mjs +3 -0
  83. package/src/cli/kernel.mjs +42 -3
  84. package/src/cli/messaging-platforms.mjs +209 -0
  85. package/src/cli/oracle-harness.py +148 -0
  86. package/src/cli/paths.mjs +5 -0
  87. package/src/cli/setup-state.mjs +168 -0
  88. package/src/data/catalog.mjs +11 -0
  89. package/src/data/desk-data.mjs +6 -0
  90. package/src/data/providers/rfq.mjs +15 -0
  91. package/src/index.mjs +7 -0
  92. package/src/rfq/intent.mjs +180 -0
  93. package/src/rfq/sources.mjs +181 -0
@@ -0,0 +1,209 @@
1
+ // Hermes messaging platforms Oracle can configure.
2
+ // Values stay lowercase. Secrets stay in Hermes profile .env, never printed.
3
+
4
+ export const MESSAGING_PLATFORMS = Object.freeze([
5
+ {
6
+ key: "telegram",
7
+ name: "telegram",
8
+ hermes: ["gateway", "setup"],
9
+ required: ["TELEGRAM_BOT_TOKEN"],
10
+ optional: ["TELEGRAM_ALLOWED_USERS", "TELEGRAM_PROXY"],
11
+ note: "dms · groups · topics",
12
+ },
13
+ {
14
+ key: "discord",
15
+ name: "discord",
16
+ hermes: ["gateway", "setup"],
17
+ required: ["DISCORD_BOT_TOKEN"],
18
+ optional: ["DISCORD_ALLOWED_USERS"],
19
+ note: "dms · channels · threads",
20
+ },
21
+ {
22
+ key: "slack",
23
+ name: "slack",
24
+ hermes: ["gateway", "setup"],
25
+ required: ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"],
26
+ optional: ["SLACK_ALLOWED_USERS"],
27
+ note: "socket mode",
28
+ },
29
+ {
30
+ key: "whatsapp",
31
+ name: "whatsapp",
32
+ hermes: ["whatsapp"],
33
+ required: [],
34
+ optional: ["WHATSAPP_ALLOWED_USERS", "WHATSAPP_MODE"],
35
+ note: "qr bridge",
36
+ },
37
+ {
38
+ key: "whatsapp-cloud",
39
+ name: "whatsapp cloud",
40
+ hermes: ["whatsapp-cloud"],
41
+ required: [],
42
+ optional: [],
43
+ note: "business cloud api",
44
+ },
45
+ {
46
+ key: "signal",
47
+ name: "signal",
48
+ hermes: ["gateway", "setup"],
49
+ required: ["SIGNAL_HTTP_URL", "SIGNAL_ACCOUNT"],
50
+ optional: ["SIGNAL_ALLOWED_USERS"],
51
+ note: "signal-cli rest bridge",
52
+ },
53
+ {
54
+ key: "matrix",
55
+ name: "matrix",
56
+ hermes: ["gateway", "setup"],
57
+ required: ["MATRIX_HOMESERVER", "MATRIX_ACCESS_TOKEN", "MATRIX_USER_ID"],
58
+ optional: ["MATRIX_ALLOWED_USERS"],
59
+ note: "rooms · dms",
60
+ },
61
+ {
62
+ key: "mattermost",
63
+ name: "mattermost",
64
+ hermes: ["gateway", "setup"],
65
+ required: ["MATTERMOST_URL", "MATTERMOST_TOKEN"],
66
+ optional: ["MATTERMOST_ALLOWED_USERS"],
67
+ note: "channels · dms",
68
+ },
69
+ {
70
+ key: "email",
71
+ name: "email",
72
+ hermes: ["gateway", "setup"],
73
+ required: ["EMAIL_ADDRESS", "EMAIL_PASSWORD", "EMAIL_IMAP_HOST", "EMAIL_SMTP_HOST"],
74
+ optional: [],
75
+ note: "imap/smtp mailbox",
76
+ },
77
+ {
78
+ key: "bluebubbles",
79
+ name: "bluebubbles",
80
+ hermes: ["gateway", "setup"],
81
+ required: [],
82
+ optional: [],
83
+ note: "imessage via bluebubbles",
84
+ },
85
+ {
86
+ key: "homeassistant",
87
+ name: "homeassistant",
88
+ hermes: ["gateway", "setup"],
89
+ required: ["HASS_URL", "HASS_TOKEN"],
90
+ optional: [],
91
+ note: "smart home",
92
+ },
93
+ {
94
+ key: "webhook",
95
+ name: "webhook",
96
+ hermes: ["gateway", "setup"],
97
+ required: [],
98
+ optional: [],
99
+ note: "http ingress",
100
+ },
101
+ {
102
+ key: "api-server",
103
+ name: "api server",
104
+ hermes: ["gateway", "setup"],
105
+ required: [],
106
+ optional: [],
107
+ note: "local http api",
108
+ },
109
+ {
110
+ key: "dingtalk",
111
+ name: "dingtalk",
112
+ hermes: ["gateway", "setup"],
113
+ required: [],
114
+ optional: [],
115
+ note: "dingtalk bot",
116
+ },
117
+ {
118
+ key: "feishu",
119
+ name: "feishu",
120
+ hermes: ["gateway", "setup"],
121
+ required: [],
122
+ optional: [],
123
+ note: "feishu / lark",
124
+ },
125
+ {
126
+ key: "wecom",
127
+ name: "wecom",
128
+ hermes: ["gateway", "setup"],
129
+ required: [],
130
+ optional: [],
131
+ note: "wecom",
132
+ },
133
+ {
134
+ key: "weixin",
135
+ name: "weixin",
136
+ hermes: ["gateway", "setup"],
137
+ required: [],
138
+ optional: [],
139
+ note: "weixin",
140
+ },
141
+ {
142
+ key: "qqbot",
143
+ name: "qqbot",
144
+ hermes: ["gateway", "setup"],
145
+ required: [],
146
+ optional: [],
147
+ note: "qq bot",
148
+ },
149
+ ]);
150
+
151
+ function normalize(raw) {
152
+ return String(raw || "")
153
+ .trim()
154
+ .toLowerCase()
155
+ .replace(/^\/+/, "")
156
+ .replace(/\s+/g, "-");
157
+ }
158
+
159
+ export function listMessagingPlatforms() {
160
+ return MESSAGING_PLATFORMS.slice();
161
+ }
162
+
163
+ export function findMessagingPlatform(query) {
164
+ const q = normalize(query);
165
+ if (!q) return null;
166
+ for (const p of MESSAGING_PLATFORMS) {
167
+ if (p.key === q) return p;
168
+ if (normalize(p.name) === q) return p;
169
+ }
170
+ return null;
171
+ }
172
+
173
+ export function renderSetupMenu({ profile = "oracle", statuses = {} } = {}) {
174
+ const lines = [
175
+ "oracle setup",
176
+ "",
177
+ "one oracle. many models. hermes messaging underneath.",
178
+ `profile: ${profile}`,
179
+ "",
180
+ "messaging platforms:",
181
+ "",
182
+ ];
183
+ for (const p of MESSAGING_PLATFORMS) {
184
+ const st = statuses[p.key] || "unset";
185
+ lines.push(` ${p.key.padEnd(16)} ${String(st).padEnd(8)} ${p.note}`);
186
+ }
187
+ lines.push("");
188
+ lines.push("commands:");
189
+ lines.push(" oracle setup this menu");
190
+ lines.push(" oracle setup messaging open hermes gateway setup");
191
+ lines.push(" oracle setup telegram configure telegram bot token");
192
+ lines.push(" oracle setup discord configure discord bot token");
193
+ lines.push(" oracle setup status show configured platforms (no secrets)");
194
+ lines.push(" oracle setup gateway start/status hermes gateway");
195
+ lines.push(" /setup same menu inside chat");
196
+ lines.push("");
197
+ lines.push("notes:");
198
+ lines.push(" tokens stay in the hermes profile .env on this machine");
199
+ lines.push(" oracle never prints bot tokens or app secrets");
200
+ lines.push(" after setup: hermes -p oracle gateway restart");
201
+ return lines.join("\n") + "\n";
202
+ }
203
+
204
+ export default {
205
+ MESSAGING_PLATFORMS,
206
+ listMessagingPlatforms,
207
+ findMessagingPlatform,
208
+ renderSetupMenu,
209
+ };
@@ -0,0 +1,148 @@
1
+ import importlib
2
+ import importlib.abc
3
+ import importlib.machinery
4
+ import re
5
+ import sys
6
+
7
+ LOGO = (
8
+ " _",
9
+ " ___ _ _ __ _ __| |___",
10
+ "/ _ \\ '_/ _` / _| / -_)",
11
+ "\\___/_| \\__,_\\__|_\\___|",
12
+ )
13
+
14
+ SILENT_STDERR = {
15
+ "[anthropic_billing_bypass] Bypass installed",
16
+ "[anthropic_billing_bypass] Transport unwrap hook installed",
17
+ }
18
+
19
+
20
+ class OracleStderr:
21
+ def __init__(self, wrapped):
22
+ self.wrapped = wrapped
23
+
24
+ def write(self, data):
25
+ kept = "".join(
26
+ line
27
+ for line in data.splitlines(keepends=True)
28
+ if line.rstrip("\r\n") not in SILENT_STDERR
29
+ )
30
+ if kept:
31
+ self.wrapped.write(kept)
32
+ return len(data)
33
+
34
+ def flush(self):
35
+ return self.wrapped.flush()
36
+
37
+ def __getattr__(self, name):
38
+ return getattr(self.wrapped, name)
39
+
40
+
41
+ def patch_cli(module):
42
+ hermes_cli = getattr(module, "HermesCLI")
43
+ original_layout = hermes_cli._build_tui_layout_children
44
+ original_emit = hermes_cli._emit_stream_text
45
+ original_flush = hermes_cli._flush_stream
46
+ original_cprint = getattr(module, "_cprint")
47
+ frame_filter = [0]
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)
57
+
58
+ def show_banner(self):
59
+ self.console.clear()
60
+ self._console_print()
61
+ for line in LOGO:
62
+ self._console_print(f"[#e6edf3]{line}[/]")
63
+ self._console_print()
64
+
65
+ def prompt_fragments(self):
66
+ if self._approval_state or getattr(self, "_slash_confirm_state", None):
67
+ marker = "!"
68
+ elif self._clarify_state or self._clarify_freetext:
69
+ marker = "?"
70
+ elif self._command_running or self._agent_running:
71
+ marker = "..."
72
+ else:
73
+ marker = ">"
74
+ return [("class:prompt", f"oracle {marker} ")]
75
+
76
+ def layout_children(self, **kwargs):
77
+ kwargs["status_bar"] = None
78
+ kwargs["input_rule_top"] = None
79
+ kwargs["input_rule_bot"] = None
80
+ return original_layout(self, **kwargs)
81
+
82
+ def emit_stream_text(self, text):
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
91
+
92
+ def flush_stream(self):
93
+ frame_filter[0] += 1
94
+ try:
95
+ return original_flush(self)
96
+ finally:
97
+ frame_filter[0] -= 1
98
+
99
+ hermes_cli.show_banner = show_banner
100
+ hermes_cli._get_tui_prompt_fragments = prompt_fragments
101
+ hermes_cli._build_tui_layout_children = layout_children
102
+ hermes_cli._emit_stream_text = emit_stream_text
103
+ hermes_cli._flush_stream = flush_stream
104
+ tips = importlib.import_module("hermes_cli.tips")
105
+
106
+ def no_tip():
107
+ raise LookupError("oracle quiet startup")
108
+
109
+ setattr(tips, "get_random_tip", no_tip)
110
+
111
+
112
+ class OracleLoader(importlib.abc.Loader):
113
+ def __init__(self, wrapped):
114
+ self.wrapped = wrapped
115
+
116
+ def create_module(self, spec):
117
+ if hasattr(self.wrapped, "create_module"):
118
+ return self.wrapped.create_module(spec)
119
+ return None
120
+
121
+ def exec_module(self, module):
122
+ self.wrapped.exec_module(module)
123
+ patch_cli(module)
124
+
125
+
126
+ class OracleFinder(importlib.abc.MetaPathFinder):
127
+ def find_spec(self, fullname, path, target=None):
128
+ if fullname != "cli":
129
+ return None
130
+ try:
131
+ sys.meta_path.remove(self)
132
+ except ValueError:
133
+ pass
134
+ spec = importlib.machinery.PathFinder.find_spec(fullname, path)
135
+ if spec and spec.loader:
136
+ spec.loader = OracleLoader(spec.loader)
137
+ return spec
138
+
139
+
140
+ def main():
141
+ sys.stderr = OracleStderr(sys.stderr)
142
+ sys.meta_path.insert(0, OracleFinder())
143
+ hermes_main = getattr(importlib.import_module("hermes_cli.main"), "main")
144
+ return hermes_main()
145
+
146
+
147
+ if __name__ == "__main__":
148
+ raise SystemExit(main())
package/src/cli/paths.mjs CHANGED
@@ -50,6 +50,10 @@ export function chatgptOpenApiPath() {
50
50
  return path.join(chatgptConnectorDir(), "chatgpt-openapi.json");
51
51
  }
52
52
 
53
+ export function activeChainPath() {
54
+ return path.join(oracleConfigDir(), "active-chain.json");
55
+ }
56
+
53
57
  export function packageBin(name) {
54
58
  return path.join(PACKAGE_ROOT, "bin", name);
55
59
  }
@@ -64,6 +68,7 @@ export const paths = {
64
68
  oracleConfigDir,
65
69
  oracleExecEnvPath,
66
70
  hermesRoot,
71
+ activeChainPath,
67
72
  claudeCodeUserConfigPath,
68
73
  claudeCodeProjectMcpPath,
69
74
  claudeDesktopConfigPath,
@@ -0,0 +1,168 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import {
5
+ listMessagingPlatforms,
6
+ findMessagingPlatform,
7
+ renderSetupMenu,
8
+ } from "./messaging-platforms.mjs";
9
+ import { hermesRoot, ensureDir, oracleConfigDir } from "./paths.mjs";
10
+
11
+ const SECRETISH = /(TOKEN|SECRET|PASSWORD|KEY|AUTH|PRIVATE|MNEMONIC|SEED)/i;
12
+
13
+ export function profileEnvPath(profile = "oracle") {
14
+ return path.join(hermesRoot(), "profiles", profile, ".env");
15
+ }
16
+
17
+ export function parseDotEnv(text) {
18
+ const out = {};
19
+ for (const line of String(text || "").split(/\r?\n/)) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed || trimmed.startsWith("#")) continue;
22
+ const eq = trimmed.indexOf("=");
23
+ if (eq <= 0) continue;
24
+ const key = trimmed.slice(0, eq).trim();
25
+ let val = trimmed.slice(eq + 1).trim();
26
+ if (
27
+ (val.startsWith('"') && val.endsWith('"')) ||
28
+ (val.startsWith("'") && val.endsWith("'"))
29
+ ) {
30
+ val = val.slice(1, -1);
31
+ }
32
+ out[key] = val;
33
+ }
34
+ return out;
35
+ }
36
+
37
+ export function readProfileEnv(profile = "oracle") {
38
+ const p = profileEnvPath(profile);
39
+ if (!fs.existsSync(p)) return {};
40
+ return parseDotEnv(fs.readFileSync(p, "utf8"));
41
+ }
42
+
43
+ export function writeProfileEnvKey(profile, key, value) {
44
+ if (!key || SECRETISH.test(key) === false && /[^A-Z0-9_]/.test(key)) {
45
+ // keep simple env keys
46
+ }
47
+ if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
48
+ const err = new Error(`invalid env key '${key}'`);
49
+ err.code = "BAD_KEY";
50
+ throw err;
51
+ }
52
+ if (value == null || String(value).trim() === "") {
53
+ const err = new Error(`empty value for ${key}`);
54
+ err.code = "EMPTY";
55
+ throw err;
56
+ }
57
+ const p = profileEnvPath(profile);
58
+ ensureDir(path.dirname(p));
59
+ let text = fs.existsSync(p) ? fs.readFileSync(p, "utf8") : "";
60
+ const lines = text ? text.split(/\r?\n/) : [];
61
+ const next = [];
62
+ let replaced = false;
63
+ const assignment = `${key}=${String(value)}`;
64
+ for (const line of lines) {
65
+ if (line.startsWith(`${key}=`)) {
66
+ next.push(assignment);
67
+ replaced = true;
68
+ } else if (line.length || next.length) {
69
+ next.push(line);
70
+ }
71
+ }
72
+ if (!replaced) {
73
+ if (next.length && next[next.length - 1] !== "") next.push("");
74
+ next.push(assignment);
75
+ }
76
+ while (next.length && next[next.length - 1] === "") next.pop();
77
+ next.push("");
78
+ fs.writeFileSync(p, next.join("\n"), { mode: 0o600 });
79
+ try {
80
+ fs.chmodSync(p, 0o600);
81
+ } catch {
82
+ // best effort on platforms that ignore mode
83
+ }
84
+ return { path: p, key, set: true };
85
+ }
86
+
87
+ export function platformStatus(profile = "oracle") {
88
+ const env = readProfileEnv(profile);
89
+ const statuses = {};
90
+ for (const p of listMessagingPlatforms()) {
91
+ if (!p.required.length) {
92
+ statuses[p.key] = "manual";
93
+ continue;
94
+ }
95
+ const missing = p.required.filter((k) => !String(env[k] || "").trim());
96
+ statuses[p.key] = missing.length ? "unset" : "ready";
97
+ }
98
+ return { profile, statuses, envPath: profileEnvPath(profile) };
99
+ }
100
+
101
+ export function renderStatus(profile = "oracle") {
102
+ const { statuses } = platformStatus(profile);
103
+ return renderSetupMenu({ profile, statuses });
104
+ }
105
+
106
+ export function setPlatformToken(profile, platformKey, token, extra = {}) {
107
+ const platform = findMessagingPlatform(platformKey);
108
+ if (!platform) {
109
+ const err = new Error(`unknown platform '${platformKey}'`);
110
+ err.code = "UNKNOWN_PLATFORM";
111
+ throw err;
112
+ }
113
+ if (!platform.required.length) {
114
+ const err = new Error(
115
+ `${platform.key} uses hermes interactive setup (oracle setup messaging)`,
116
+ );
117
+ err.code = "NEEDS_INTERACTIVE";
118
+ throw err;
119
+ }
120
+ const primary = platform.required[0];
121
+ writeProfileEnvKey(profile, primary, token);
122
+ for (const [k, v] of Object.entries(extra || {})) {
123
+ if (v != null && String(v).trim()) writeProfileEnvKey(profile, k, v);
124
+ }
125
+ return {
126
+ platform: platform.key,
127
+ set: platform.required,
128
+ profile,
129
+ path: profileEnvPath(profile),
130
+ };
131
+ }
132
+
133
+ export function setupStatePath() {
134
+ return path.join(oracleConfigDir(), "setup-state.json");
135
+ }
136
+
137
+ export function touchSetupState(patch = {}) {
138
+ ensureDir(oracleConfigDir());
139
+ const p = setupStatePath();
140
+ let cur = {};
141
+ if (fs.existsSync(p)) {
142
+ try {
143
+ cur = JSON.parse(fs.readFileSync(p, "utf8"));
144
+ } catch {
145
+ cur = {};
146
+ }
147
+ }
148
+ const next = {
149
+ ...cur,
150
+ ...patch,
151
+ updatedAt: new Date().toISOString(),
152
+ host: os.hostname(),
153
+ };
154
+ fs.writeFileSync(p, JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
155
+ return next;
156
+ }
157
+
158
+ export default {
159
+ profileEnvPath,
160
+ parseDotEnv,
161
+ readProfileEnv,
162
+ writeProfileEnvKey,
163
+ platformStatus,
164
+ renderStatus,
165
+ setPlatformToken,
166
+ setupStatePath,
167
+ touchSetupState,
168
+ };
@@ -526,3 +526,14 @@ registerProvider({
526
526
  baseEnv: ["PARASWAP_API_URL"],
527
527
  description: "ParaSwap/Velora public price routes + validated transaction builder",
528
528
  });
529
+
530
+ registerProvider({
531
+ id: "rfq",
532
+ venue: "intent-rfq",
533
+ chainIds: [1, 10, 56, 137, 8453, 42161, 43114],
534
+ auth: "none",
535
+ ops: ["health", "intent", "quote"],
536
+ execution: "prepare",
537
+ baseEnv: ["ZEROX_API_KEY", "ONEINCH_API_KEY"],
538
+ description: "Cross-chain RFQ intent normalizer and firm-quote fanout over reviewed venues. Prepares artifacts only, never signs or broadcasts.",
539
+ });
@@ -44,6 +44,7 @@ import * as portfolio from "./providers/portfolio.mjs";
44
44
  import * as portfolioHistory from "./providers/portfolio-history.mjs";
45
45
  import * as nftPortfolio from "./providers/nft-portfolio.mjs";
46
46
  import * as paraswap from "./providers/paraswap.mjs";
47
+ import * as rfq from "./providers/rfq.mjs";
47
48
 
48
49
  const OPS = {
49
50
  "hl-info": {
@@ -356,6 +357,11 @@ const OPS = {
356
357
  quote: (o, a = {}) => paraswap.paraswapPrice(a, o),
357
358
  prepare: (o, a = {}) => paraswap.paraswapPrepare(a, o),
358
359
  },
360
+ rfq: {
361
+ health: (o) => rfq.rfqHealth(o),
362
+ intent: (o, a = {}) => rfq.rfqIntent(a, o),
363
+ quote: (o, a = {}) => rfq.rfqQuote(a, o),
364
+ },
359
365
  };
360
366
 
361
367
  /**
@@ -0,0 +1,15 @@
1
+ import { normalizeRfqIntent } from "../../rfq/intent.mjs";
2
+ import { requestRfqQuotes } from "../../rfq/sources.mjs";
3
+
4
+ export function rfqIntent(args = {}, opts = {}) {
5
+ return normalizeRfqIntent(args, opts);
6
+ }
7
+
8
+ export async function rfqQuote(args = {}, opts = {}) {
9
+ const intent = args.intent?.kind === "rfq-intent" ? args.intent : normalizeRfqIntent(args, opts);
10
+ return requestRfqQuotes(intent, opts);
11
+ }
12
+
13
+ export async function rfqHealth() {
14
+ return { ok: true, provider: "rfq", mode: "intent-rfq", execution: "prepare" };
15
+ }
package/src/index.mjs CHANGED
@@ -38,6 +38,13 @@ export {
38
38
  } from "./scanner/index.mjs";
39
39
 
40
40
  export { bestSwapRoute, bestBridgeRoute, QUALITY } from "./router/index.mjs";
41
+ export {
42
+ normalizeRfqIntent,
43
+ normalizeFirmQuote,
44
+ hashRfqIntent,
45
+ hashFirmQuote,
46
+ } from "./rfq/intent.mjs";
47
+ export { sourceCandidates, executeRfqCandidates, rankRfqQuotes, requestRfqQuotes } from "./rfq/sources.mjs";
41
48
  export { validateNftMintGasWar, assertNftMintGasWar, NftGasWarLimitError } from "./nft-gas-war-guard.mjs";
42
49
  export { simulateSell, assertSellable, SELL_SIM } from "./sell-simulation.mjs";
43
50
  export { capabilityStatus, assertCapability, autonomousTradingEnabled, posture } from "./capability-posture.mjs";