@oracle-agent/oracle 0.4.2 → 0.6.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.
- package/README.md +76 -18
- package/SETUP.md +13 -0
- package/bin/oracle-data-mcp.mjs +1 -1
- package/bin/oracle.mjs +33 -0
- package/docs/cli.md +47 -0
- package/docs/connectors.md +13 -0
- package/package.json +6 -2
- package/profiles/oracle/SOUL.md +10 -4
- package/profiles/robinhood-agent/SOUL.md +5 -3
- package/protocols/templates/safe-erc20/README.md +9 -0
- package/public/agent-connect/app.js +16 -13
- package/public/oracle-splash/assets/hero/orb.avif +0 -0
- package/public/oracle-splash/assets/hero/orb.webm +0 -0
- package/public/oracle-splash/assets/llms/claude.svg +1 -0
- package/public/oracle-splash/assets/llms/codex.svg +1 -0
- package/public/oracle-splash/assets/llms/cursor.svg +17 -0
- package/public/oracle-splash/assets/llms/deepseek.svg +1 -0
- package/public/oracle-splash/assets/llms/gemini.svg +1 -0
- package/public/oracle-splash/assets/llms/grok.svg +8 -0
- package/public/oracle-splash/assets/llms/hermes.png +0 -0
- package/public/oracle-splash/assets/llms/kimi.svg +1 -0
- package/public/oracle-splash/assets/llms/manus.svg +18 -0
- package/public/oracle-splash/assets/llms/openclaw.svg +22 -0
- package/public/oracle-splash/assets/llms/perplexity.svg +1 -0
- package/public/oracle-splash/assets/llms/qwen.svg +1 -0
- package/public/oracle-splash/assets/llms/windsurf.svg +1 -0
- package/public/oracle-splash/assets/protocols/abcdex.png +0 -0
- package/public/oracle-splash/assets/protocols/across.png +0 -0
- package/public/oracle-splash/assets/protocols/aerodrome.png +0 -0
- package/public/oracle-splash/assets/protocols/balancer.png +0 -0
- package/public/oracle-splash/assets/protocols/cowswap.png +0 -0
- package/public/oracle-splash/assets/protocols/curve.png +0 -0
- package/public/oracle-splash/assets/protocols/dreamcash.png +0 -0
- package/public/oracle-splash/assets/protocols/felix.png +0 -0
- package/public/oracle-splash/assets/protocols/gmx.png +0 -0
- package/public/oracle-splash/assets/protocols/hyena.png +0 -0
- package/public/oracle-splash/assets/protocols/hyperbeat.png +0 -0
- package/public/oracle-splash/assets/protocols/hyperliquid.png +0 -0
- package/public/oracle-splash/assets/protocols/hyperswap.png +0 -0
- package/public/oracle-splash/assets/protocols/jupiter.png +0 -0
- package/public/oracle-splash/assets/protocols/kinetiq.png +0 -0
- package/public/oracle-splash/assets/protocols/lfj.png +0 -0
- package/public/oracle-splash/assets/protocols/lifi.png +0 -0
- package/public/oracle-splash/assets/protocols/magic-eden.png +0 -0
- package/public/oracle-splash/assets/protocols/morpho.png +0 -0
- package/public/oracle-splash/assets/protocols/odos.png +0 -0
- package/public/oracle-splash/assets/protocols/oneinch.png +0 -0
- package/public/oracle-splash/assets/protocols/opensea.png +0 -0
- package/public/oracle-splash/assets/protocols/pancakeswap.png +0 -0
- package/public/oracle-splash/assets/protocols/paragon.png +0 -0
- package/public/oracle-splash/assets/protocols/paraswap.png +0 -0
- package/public/oracle-splash/assets/protocols/pendle.png +0 -0
- package/public/oracle-splash/assets/protocols/polymarket.png +0 -0
- package/public/oracle-splash/assets/protocols/quickswap.png +0 -0
- package/public/oracle-splash/assets/protocols/relay.png +0 -0
- package/public/oracle-splash/assets/protocols/stargate.png +0 -0
- package/public/oracle-splash/assets/protocols/tradexyz.png +0 -0
- package/public/oracle-splash/assets/protocols/uniswap.png +0 -0
- package/public/oracle-splash/assets/protocols/velodrome.png +0 -0
- package/public/oracle-splash/assets/protocols/ventuals.png +0 -0
- package/public/oracle-splash/assets/wordmarks/across.svg +4 -0
- package/public/oracle-splash/assets/wordmarks/aerodrome.svg +23 -0
- package/public/oracle-splash/assets/wordmarks/balancer.svg +3 -0
- package/public/oracle-splash/assets/wordmarks/cowswap.svg +1 -0
- package/public/oracle-splash/assets/wordmarks/gmx.svg +13 -0
- package/public/oracle-splash/assets/wordmarks/hyena.svg +7 -0
- package/public/oracle-splash/assets/wordmarks/hyperliquid.svg +21 -0
- package/public/oracle-splash/assets/wordmarks/hyperswap.svg +13 -0
- package/public/oracle-splash/assets/wordmarks/jupiter.svg +35 -0
- package/public/oracle-splash/assets/wordmarks/lifi.png +0 -0
- package/public/oracle-splash/assets/wordmarks/magic-eden.svg +1 -0
- package/public/oracle-splash/assets/wordmarks/morpho.svg +1 -0
- package/public/oracle-splash/assets/wordmarks/odos.svg +3 -0
- package/public/oracle-splash/assets/wordmarks/oneinch.svg +6 -0
- package/public/oracle-splash/assets/wordmarks/opensea.svg +1 -0
- package/public/oracle-splash/assets/wordmarks/outcome.svg +16 -0
- package/public/oracle-splash/assets/wordmarks/pancakeswap.svg +18 -0
- package/public/oracle-splash/assets/wordmarks/paraswap.svg +16 -0
- package/public/oracle-splash/assets/wordmarks/pendle.png +0 -0
- package/public/oracle-splash/assets/wordmarks/polymarket.png +0 -0
- package/public/oracle-splash/assets/wordmarks/quickswap.png +0 -0
- package/public/oracle-splash/assets/wordmarks/relay.svg +8 -0
- package/public/oracle-splash/assets/wordmarks/stargate.svg +15 -0
- package/public/oracle-splash/assets/wordmarks/uniswap.svg +18 -0
- package/public/oracle-splash/assets/wordmarks/velodrome.svg +84 -0
- package/public/oracle-splash/assets/wordmarks/xyz.svg +12 -0
- package/public/oracle-splash/index.html +784 -498
- package/scripts/check-doc-drift.mjs +111 -0
- package/skills/oracle-chat/SKILL.md +61 -0
- package/skills/oracle-chat/chain.SKILL.md +31 -0
- package/skills/oracle-chat/setup.SKILL.md +37 -0
- package/skins/oracle.yaml +52 -0
- package/src/cli/chain-catalog.mjs +215 -0
- package/src/cli/chain-state.mjs +74 -0
- package/src/cli/commands/chain.mjs +143 -0
- package/src/cli/commands/chat.mjs +268 -0
- package/src/cli/commands/credential.mjs +9 -0
- package/src/cli/commands/data-mcp.mjs +18 -0
- package/src/cli/commands/data.mjs +73 -0
- package/src/cli/commands/doctor.mjs +131 -0
- package/src/cli/commands/help.mjs +7 -0
- package/src/cli/commands/init.mjs +27 -0
- package/src/cli/commands/mcp.mjs +142 -0
- package/src/cli/commands/model.mjs +37 -0
- package/src/cli/commands/prepare.mjs +12 -0
- package/src/cli/commands/public.mjs +20 -0
- package/src/cli/commands/route.mjs +12 -0
- package/src/cli/commands/runner.mjs +9 -0
- package/src/cli/commands/scan.mjs +12 -0
- package/src/cli/commands/setup.mjs +283 -0
- package/src/cli/commands/sign.mjs +21 -0
- package/src/cli/commands/signer.mjs +9 -0
- package/src/cli/commands/upgrade.mjs +12 -0
- package/src/cli/commands/vault.mjs +9 -0
- package/src/cli/commands/version.mjs +22 -0
- package/src/cli/first-run.mjs +23 -0
- package/src/cli/kernel.mjs +237 -0
- package/src/cli/mcp-targets/chatgpt.mjs +51 -0
- package/src/cli/mcp-targets/claude-code.mjs +39 -0
- package/src/cli/mcp-targets/claude-desktop.mjs +24 -0
- package/src/cli/mcp-targets/codex.mjs +41 -0
- package/src/cli/mcp-targets/shared.mjs +61 -0
- package/src/cli/messaging-platforms.mjs +209 -0
- package/src/cli/operator-dispatch.mjs +258 -0
- package/src/cli/oracle-harness.py +148 -0
- package/src/cli/paths.mjs +83 -0
- package/src/cli/setup-state.mjs +168 -0
- package/src/cli/spawn-child.mjs +43 -0
- package/src/public-api/connect-agent.mjs +29 -10
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const PACKAGE_ROOT = path.resolve(HERE, "../..");
|
|
9
|
+
|
|
10
|
+
export const OPERATOR_BIN_NAMES = Object.freeze([
|
|
11
|
+
"oracle-vault",
|
|
12
|
+
"oracle-credential",
|
|
13
|
+
"oracle-signer",
|
|
14
|
+
"oracle-runner",
|
|
15
|
+
"oracle-agentic-init",
|
|
16
|
+
"oracle-agentic-doctor",
|
|
17
|
+
"oracle-control-mcp",
|
|
18
|
+
"oracle-operator-caps",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
export const SIGN_NOUNS = Object.freeze([
|
|
22
|
+
"vault",
|
|
23
|
+
"credential",
|
|
24
|
+
"signer",
|
|
25
|
+
"runner",
|
|
26
|
+
"sign",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export const OPERATOR_NOT_INSTALLED_MESSAGE = `oracle: signing lives in @oracle-agent/operator, which is not installed.
|
|
30
|
+
|
|
31
|
+
The public 'oracle' package is prepare-only and never handles keys.
|
|
32
|
+
To enable local signing on THIS machine:
|
|
33
|
+
|
|
34
|
+
npm i -g @oracle-agent/operator # or add it to this project
|
|
35
|
+
oracle sign init # provision keys + policy (interactive)
|
|
36
|
+
|
|
37
|
+
Nothing about your read/research setup is affected.
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export const SIGN_HINT =
|
|
41
|
+
"hint: run 'oracle sign init' to provision keys, or 'oracle sign doctor --json' for detail.";
|
|
42
|
+
|
|
43
|
+
function whichOnPath(binName) {
|
|
44
|
+
const pathEnv = process.env.PATH || "";
|
|
45
|
+
const parts = pathEnv.split(path.delimiter).filter(Boolean);
|
|
46
|
+
const exts = process.platform === "win32" ? ["", ".cmd", ".exe", ".bat"] : [""];
|
|
47
|
+
for (const dir of parts) {
|
|
48
|
+
for (const ext of exts) {
|
|
49
|
+
const candidate = path.join(dir, binName + ext);
|
|
50
|
+
try {
|
|
51
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// ignore
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function binsFromDir(binDir) {
|
|
63
|
+
const bins = {};
|
|
64
|
+
for (const name of OPERATOR_BIN_NAMES) {
|
|
65
|
+
const candidates = [
|
|
66
|
+
path.join(binDir, name),
|
|
67
|
+
path.join(binDir, `${name}.mjs`),
|
|
68
|
+
path.join(binDir, `${name}.js`),
|
|
69
|
+
];
|
|
70
|
+
for (const c of candidates) {
|
|
71
|
+
if (fs.existsSync(c) && fs.statSync(c).isFile()) {
|
|
72
|
+
bins[name] = c;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return bins;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function findPackageRoot(entryPath) {
|
|
81
|
+
let dir = path.dirname(entryPath);
|
|
82
|
+
while (dir !== path.dirname(dir)) {
|
|
83
|
+
const pkgJson = path.join(dir, "package.json");
|
|
84
|
+
if (fs.existsSync(pkgJson)) {
|
|
85
|
+
try {
|
|
86
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
|
|
87
|
+
if (pkg.name === "@oracle-agent/operator") return { root: dir, pkg };
|
|
88
|
+
} catch {
|
|
89
|
+
// continue upward
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
dir = path.dirname(dir);
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function tryResolvePackage(fromPaths) {
|
|
98
|
+
for (const from of fromPaths) {
|
|
99
|
+
try {
|
|
100
|
+
const req = createRequire(path.join(from, "package.json"));
|
|
101
|
+
let found;
|
|
102
|
+
try {
|
|
103
|
+
const pkgJson = req.resolve("@oracle-agent/operator/package.json");
|
|
104
|
+
found = {
|
|
105
|
+
root: path.dirname(pkgJson),
|
|
106
|
+
pkg: JSON.parse(fs.readFileSync(pkgJson, "utf8")),
|
|
107
|
+
};
|
|
108
|
+
} catch {
|
|
109
|
+
const entry = req.resolve("@oracle-agent/operator");
|
|
110
|
+
found = findPackageRoot(entry);
|
|
111
|
+
}
|
|
112
|
+
if (!found) continue;
|
|
113
|
+
const { root, pkg } = found;
|
|
114
|
+
const binField = pkg.bin || {};
|
|
115
|
+
const bins = {};
|
|
116
|
+
for (const [name, rel] of Object.entries(binField)) {
|
|
117
|
+
bins[name] = path.resolve(root, rel);
|
|
118
|
+
}
|
|
119
|
+
const extra = binsFromDir(path.join(root, "bin"));
|
|
120
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
121
|
+
if (!bins[k]) bins[k] = v;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
binDir: path.join(root, "bin"),
|
|
126
|
+
version: pkg.version || "unknown",
|
|
127
|
+
bins,
|
|
128
|
+
source: `require:${from}`,
|
|
129
|
+
};
|
|
130
|
+
} catch {
|
|
131
|
+
// continue
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function tryCapsOnPath() {
|
|
138
|
+
const capsBin = whichOnPath("oracle-operator-caps");
|
|
139
|
+
if (!capsBin) return null;
|
|
140
|
+
const r = spawnSync(capsBin, [], {
|
|
141
|
+
encoding: "utf8",
|
|
142
|
+
timeout: 5000,
|
|
143
|
+
env: process.env,
|
|
144
|
+
});
|
|
145
|
+
if (r.status !== 0 || !r.stdout) return null;
|
|
146
|
+
try {
|
|
147
|
+
const report = JSON.parse(r.stdout);
|
|
148
|
+
if (!report || typeof report !== "object" || !report.bins) return null;
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
binDir: path.dirname(Object.values(report.bins)[0] || capsBin),
|
|
152
|
+
version: report.version || "unknown",
|
|
153
|
+
bins: report.bins,
|
|
154
|
+
source: "caps-path",
|
|
155
|
+
controlMcp: report.controlMcp,
|
|
156
|
+
};
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function tryPathProbe() {
|
|
163
|
+
const bins = {};
|
|
164
|
+
for (const name of OPERATOR_BIN_NAMES) {
|
|
165
|
+
const hit = whichOnPath(name);
|
|
166
|
+
if (hit) bins[name] = hit;
|
|
167
|
+
}
|
|
168
|
+
if (!Object.keys(bins).length) return null;
|
|
169
|
+
return {
|
|
170
|
+
ok: true,
|
|
171
|
+
binDir: path.dirname(Object.values(bins)[0]),
|
|
172
|
+
version: "unknown",
|
|
173
|
+
bins,
|
|
174
|
+
source: "path-probe",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function resolveOperator() {
|
|
179
|
+
const envDir = process.env.ORACLE_OPERATOR_BIN_DIR;
|
|
180
|
+
if (envDir) {
|
|
181
|
+
const abs = path.resolve(envDir);
|
|
182
|
+
if (!fs.existsSync(abs)) return { ok: false };
|
|
183
|
+
const bins = binsFromDir(abs);
|
|
184
|
+
if (!Object.keys(bins).length) return { ok: false };
|
|
185
|
+
let version = "unknown";
|
|
186
|
+
const pkgCandidate = path.resolve(abs, "../package.json");
|
|
187
|
+
if (fs.existsSync(pkgCandidate)) {
|
|
188
|
+
try {
|
|
189
|
+
version = JSON.parse(fs.readFileSync(pkgCandidate, "utf8")).version || version;
|
|
190
|
+
} catch {
|
|
191
|
+
// ignore
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { ok: true, binDir: abs, version, bins, source: "env:ORACLE_OPERATOR_BIN_DIR" };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const fromPkg =
|
|
198
|
+
tryResolvePackage([process.cwd(), PACKAGE_ROOT]) ||
|
|
199
|
+
tryCapsOnPath() ||
|
|
200
|
+
tryPathProbe();
|
|
201
|
+
return fromPkg || { ok: false };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function printOperatorNotInstalled(stream = process.stderr) {
|
|
205
|
+
stream.write(OPERATOR_NOT_INSTALLED_MESSAGE);
|
|
206
|
+
if (!OPERATOR_NOT_INSTALLED_MESSAGE.endsWith("\n")) stream.write("\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function dispatchOperator(binName, argv = [], opts = {}) {
|
|
210
|
+
const resolved = resolveOperator();
|
|
211
|
+
if (!resolved.ok) {
|
|
212
|
+
printOperatorNotInstalled(opts.stderr || process.stderr);
|
|
213
|
+
return 3;
|
|
214
|
+
}
|
|
215
|
+
const binPath = resolved.bins[binName];
|
|
216
|
+
if (!binPath) {
|
|
217
|
+
const err = opts.stderr || process.stderr;
|
|
218
|
+
err.write(`oracle: operator bin '${binName}' not found in resolved operator (${resolved.source})\n`);
|
|
219
|
+
return 3;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const stdio = opts.stdio || "inherit";
|
|
223
|
+
const isJs = /\.(mjs|cjs|js)$/.test(binPath);
|
|
224
|
+
const command = isJs ? process.execPath : binPath;
|
|
225
|
+
const args = isJs ? [binPath, ...argv] : [...argv];
|
|
226
|
+
const r = spawnSync(command, args, {
|
|
227
|
+
stdio,
|
|
228
|
+
env: opts.env || process.env,
|
|
229
|
+
cwd: opts.cwd || process.cwd(),
|
|
230
|
+
encoding: stdio === "pipe" ? "utf8" : undefined,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (r.error) {
|
|
234
|
+
const err = opts.stderr || process.stderr;
|
|
235
|
+
err.write(`oracle: failed to spawn ${binName}: ${r.error.message}\n`);
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const code = typeof r.status === "number" ? r.status : 1;
|
|
240
|
+
if (code !== 0 && opts.appendHintOnError) {
|
|
241
|
+
const err = opts.stderr || process.stderr;
|
|
242
|
+
err.write(SIGN_HINT + "\n");
|
|
243
|
+
}
|
|
244
|
+
if (stdio === "pipe") {
|
|
245
|
+
return { code, stdout: r.stdout || "", stderr: r.stderr || "" };
|
|
246
|
+
}
|
|
247
|
+
return code;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export default {
|
|
251
|
+
resolveOperator,
|
|
252
|
+
dispatchOperator,
|
|
253
|
+
printOperatorNotInstalled,
|
|
254
|
+
OPERATOR_NOT_INSTALLED_MESSAGE,
|
|
255
|
+
SIGN_HINT,
|
|
256
|
+
SIGN_NOUNS,
|
|
257
|
+
OPERATOR_BIN_NAMES,
|
|
258
|
+
};
|
|
@@ -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())
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
export const PACKAGE_ROOT = path.resolve(HERE, "../..");
|
|
8
|
+
|
|
9
|
+
export function homeDir() {
|
|
10
|
+
return process.env.ORACLE_FAKE_HOME || os.homedir();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function oracleConfigDir() {
|
|
14
|
+
return path.join(homeDir(), ".config", "oracle");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function oracleExecEnvPath() {
|
|
18
|
+
return path.join(oracleConfigDir(), "exec.env");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function hermesRoot() {
|
|
22
|
+
if (process.env.HERMES_HOME) return path.resolve(process.env.HERMES_HOME);
|
|
23
|
+
return path.join(homeDir(), ".hermes");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function claudeCodeUserConfigPath() {
|
|
27
|
+
return path.join(homeDir(), ".claude.json");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function claudeCodeProjectMcpPath(cwd = process.cwd()) {
|
|
31
|
+
return path.join(cwd, ".mcp.json");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function claudeDesktopConfigPath() {
|
|
35
|
+
if (process.platform === "darwin") {
|
|
36
|
+
return path.join(homeDir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
37
|
+
}
|
|
38
|
+
return path.join(homeDir(), ".config", "Claude", "claude_desktop_config.json");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function codexConfigPath() {
|
|
42
|
+
return path.join(homeDir(), ".codex", "config.toml");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function chatgptConnectorDir() {
|
|
46
|
+
return path.join(oracleConfigDir(), "connectors");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function chatgptOpenApiPath() {
|
|
50
|
+
return path.join(chatgptConnectorDir(), "chatgpt-openapi.json");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function activeChainPath() {
|
|
54
|
+
return path.join(oracleConfigDir(), "active-chain.json");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function packageBin(name) {
|
|
58
|
+
return path.join(PACKAGE_ROOT, "bin", name);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function ensureDir(dir) {
|
|
62
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
63
|
+
return dir;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const paths = {
|
|
67
|
+
homeDir,
|
|
68
|
+
oracleConfigDir,
|
|
69
|
+
oracleExecEnvPath,
|
|
70
|
+
hermesRoot,
|
|
71
|
+
activeChainPath,
|
|
72
|
+
claudeCodeUserConfigPath,
|
|
73
|
+
claudeCodeProjectMcpPath,
|
|
74
|
+
claudeDesktopConfigPath,
|
|
75
|
+
codexConfigPath,
|
|
76
|
+
chatgptConnectorDir,
|
|
77
|
+
chatgptOpenApiPath,
|
|
78
|
+
packageBin,
|
|
79
|
+
ensureDir,
|
|
80
|
+
PACKAGE_ROOT,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default paths;
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
4
|
+
|
|
5
|
+
export function spawnChild(command, args = [], options = {}, label = command) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const child = spawn(command, args, options);
|
|
8
|
+
let settled = false;
|
|
9
|
+
let forwardedSignal = null;
|
|
10
|
+
|
|
11
|
+
const handlers = new Map(
|
|
12
|
+
FORWARDED_SIGNALS.map((signal) => [signal, () => {
|
|
13
|
+
forwardedSignal ||= signal;
|
|
14
|
+
if (child.exitCode === null && child.signalCode === null) child.kill(signal);
|
|
15
|
+
}]),
|
|
16
|
+
);
|
|
17
|
+
for (const [signal, handler] of handlers) process.on(signal, handler);
|
|
18
|
+
|
|
19
|
+
const cleanup = () => {
|
|
20
|
+
for (const [signal, handler] of handlers) process.off(signal, handler);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
child.once("error", (error) => {
|
|
24
|
+
if (settled) return;
|
|
25
|
+
settled = true;
|
|
26
|
+
cleanup();
|
|
27
|
+
process.stderr.write(`oracle: could not start ${label}: ${error.message}\n`);
|
|
28
|
+
resolve(1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
child.once("exit", (code, signal) => {
|
|
32
|
+
if (settled) return;
|
|
33
|
+
settled = true;
|
|
34
|
+
cleanup();
|
|
35
|
+
const propagated = forwardedSignal || signal;
|
|
36
|
+
if (propagated) {
|
|
37
|
+
process.kill(process.pid, propagated);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
resolve(code ?? 1);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|