@oracle-agent/oracle 0.4.2 → 0.5.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 +66 -17
- package/SETUP.md +13 -0
- package/bin/oracle-data-mcp.mjs +1 -1
- package/bin/oracle.mjs +33 -0
- package/docs/cli.md +19 -0
- package/docs/connectors.md +13 -0
- package/package.json +7 -3
- package/profiles/robinhood-agent/SOUL.md +5 -3
- package/public/agent-connect/app.js +16 -13
- 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/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/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/index.html +789 -485
- package/scripts/check-doc-drift.mjs +111 -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/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/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 +20 -0
- package/src/cli/kernel.mjs +198 -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/operator-dispatch.mjs +258 -0
- package/src/cli/paths.mjs +78 -0
- package/src/cli/spawn-child.mjs +43 -0
- package/src/public-api/connect-agent.mjs +29 -10
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Release gate: shipped prose must not contradict shipped behavior.
|
|
3
|
+
//
|
|
4
|
+
// Why this exists. The 2026-08-01 four-model audit found the same stale claim
|
|
5
|
+
// in 20 places across both packages: docs said the unattended signer was
|
|
6
|
+
// limited to `hl` and `poly` long after six bounded surfaces had shipped. The
|
|
7
|
+
// worst instances were not READMEs — they were `profiles/oracle/SOUL.md` and
|
|
8
|
+
// `skills/oracle-action-semantics/SKILL.md`, which are read by the MODEL. An
|
|
9
|
+
// agent told that EVM daemon execution is impossible reasons incorrectly about
|
|
10
|
+
// its own authority, and no test suite can see a false sentence.
|
|
11
|
+
//
|
|
12
|
+
// This scans every doc surface that ships in the tarball, including model-facing
|
|
13
|
+
// SOUL and skill text, which is exactly where drift hides longest.
|
|
14
|
+
|
|
15
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
20
|
+
const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
|
|
21
|
+
const failures = [];
|
|
22
|
+
|
|
23
|
+
// Claims that shipped in a real release and are now false.
|
|
24
|
+
const STALE_CLAIMS = [
|
|
25
|
+
{ re: /limited\s+to\s+`?hl`?\s+and\s+`?poly`?/i, why: "says the unattended signer is limited to hl+poly" },
|
|
26
|
+
{ re: /only\s+`?hl`?\s+and\s+`?poly`?\s+are\s+enabled/i, why: "says only hl+poly are daemon-enabled" },
|
|
27
|
+
{ re: /daemon\s+surfaces\s+remain\s+`?hl`?\s+and\s+`?poly`?/i, why: "freezes the surface list at hl+poly" },
|
|
28
|
+
{ re: /policy-bounded\s+`?hl`?\s+and\s+`?poly`?\s+only/i, why: "restricts the daemon to hl+poly" },
|
|
29
|
+
{ re: /agent-facing\s+daemon\s+supports\s+only\s+policy-bounded\s+Hyperliquid\s+and\s+Polymarket\s+execution/i, why: "says the agent-facing daemon supports only Hyperliquid+Polymarket" },
|
|
30
|
+
{ re: /`?evm`?,?\s+`?btc`?,?\s+and\s+`?sol`?\s+fail\s+closed\s+at\s+the\s+daemon/i, why: "claims evm/btc/sol are refused at the daemon" },
|
|
31
|
+
{ re: /EVM,\s*BTC,?\s*and\s+Solana\s+are\s+not\s+daemon\s+surfaces/i, why: "denies evm/btc/sol are daemon surfaces" },
|
|
32
|
+
{ re: /companion\s+daemon\s+is\s+`?hl`?\s*\/\s*`?poly`?[- ]only/i, why: "describes the companion daemon as hl/poly-only" },
|
|
33
|
+
{ re: /daemon\s+supports\s+only\s+policy-bounded\s+Hyperliquid\s+and\s+Polymarket/i, why: "describes the daemon as Hyperliquid/Polymarket-only" },
|
|
34
|
+
{ re: /unattended\s+signer\s+remains\s+`?hl`?\s*\/\s*`?poly`?/i, why: "freezes the generic signer at hl/poly" },
|
|
35
|
+
{ re: /supports\s+autonomous\s+Hyperliquid\s+and\s+Polymarket\s+execution/i, why: "describes the daemon as Hyperliquid/Polymarket-only" },
|
|
36
|
+
{ re: /\(HL\s*\+\s*Poly,\s*capped\)/i, why: "documents autosign as hl/poly-only" },
|
|
37
|
+
{ re: /policy-bounded\s+HL\s*\/\s*Polymarket\s+exec/i, why: "documents the signer daemon as hl/poly-only" },
|
|
38
|
+
{ re: /policy-bounded\s+daemon\s+surfaces:\s*hl\s*\|\s*poly\s*$/i, why: "documents only two of six daemon surfaces" },
|
|
39
|
+
{ re: /ORACLE_SIGNER_SURFACES=hl,poly\s*$/i, why: "quickstart freezes ORACLE_SIGNER_SURFACES at hl,poly" },
|
|
40
|
+
{ re: /ORACLE_AUTONOMOUS_TRADING=1.*(?:or\s+run\s+the\s+signer\s+daemon|same(?:-tier)?).*oracle-signer/i, why: "frames direct exec and oracle-signer as equivalent agent paths" },
|
|
41
|
+
{ re: /(?:or\s+run\s+the\s+signer\s+daemon|same(?:-tier)?).*oracle-signer.*ORACLE_AUTONOMOUS_TRADING=1/i, why: "frames direct exec and oracle-signer as equivalent agent paths" },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Version pins that go stale the moment a release ships. The audit found the
|
|
45
|
+
// quickstart advertising 0.3.6/0.6.1 inside a 0.4.0 tarball, so a copy-paste
|
|
46
|
+
// install handed users an older wall set.
|
|
47
|
+
function checkVersionPins(rel, text) {
|
|
48
|
+
const lines = text.split("\n");
|
|
49
|
+
lines.forEach((line, i) => {
|
|
50
|
+
const pin = line.match(/@oracle-agent\/oracle@(\d+\.\d+\.\d+)/);
|
|
51
|
+
if (pin && pin[1] !== pkg.version) {
|
|
52
|
+
failures.push(
|
|
53
|
+
`${rel}:${i + 1} pins @oracle-agent/oracle@${pin[1]} but this package is ${pkg.version}\n ${line.trim().slice(0, 120)}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const latest = line.match(/Latest:.*oracle\s+`(\d+\.\d+\.\d+)`/i);
|
|
57
|
+
if (latest && latest[1] !== pkg.version) {
|
|
58
|
+
failures.push(
|
|
59
|
+
`${rel}:${i + 1} advertises oracle ${latest[1]} as latest but this package is ${pkg.version}\n ${line.trim().slice(0, 120)}`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Every markdown surface that ships to a user or a model.
|
|
66
|
+
function docFiles() {
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const rel of ["README.md", "SETUP.md", "SECURITY.md"]) {
|
|
69
|
+
if (existsSync(path.join(root, rel))) out.push(rel);
|
|
70
|
+
}
|
|
71
|
+
for (const dir of ["docs", "profiles", "skills"]) {
|
|
72
|
+
const abs = path.join(root, dir);
|
|
73
|
+
if (!existsSync(abs)) continue;
|
|
74
|
+
const walk = (d) => {
|
|
75
|
+
for (const entry of readdirSync(d)) {
|
|
76
|
+
const full = path.join(d, entry);
|
|
77
|
+
if (statSync(full).isDirectory()) walk(full);
|
|
78
|
+
else if (entry.endsWith(".md")) out.push(path.relative(root, full));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
walk(abs);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const files = docFiles();
|
|
87
|
+
for (const rel of files) {
|
|
88
|
+
const text = readFileSync(path.join(root, rel), "utf8");
|
|
89
|
+
text.split("\n").forEach((line, i) => {
|
|
90
|
+
for (const claim of STALE_CLAIMS) {
|
|
91
|
+
if (claim.re.test(line)) {
|
|
92
|
+
failures.push(`${rel}:${i + 1} ${claim.why}\n ${line.trim().slice(0, 140)}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
checkVersionPins(rel, text);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(`package version: ${pkg.version}`);
|
|
100
|
+
console.log(`doc surfaces scanned: ${files.length} (README/SETUP/SECURITY + docs/ + profiles/ + skills/)`);
|
|
101
|
+
console.log("");
|
|
102
|
+
|
|
103
|
+
if (failures.length) {
|
|
104
|
+
console.error(`FAIL — ${failures.length} doc/code drift finding(s):\n`);
|
|
105
|
+
for (const f of failures) console.error(` ${f}\n`);
|
|
106
|
+
console.error("Shipped prose contradicts shipped behavior. Model-facing SOUL/skill text counts:");
|
|
107
|
+
console.error("an agent that reads a false capability claim reasons wrongly about its own authority.");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log("PASS — no stale capability claims or version pins in shipped docs");
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
name: "credential",
|
|
3
|
+
summary: "OS credential store glue",
|
|
4
|
+
group: "sign",
|
|
5
|
+
usage: "oracle credential <get|set|delete> ...",
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
return ctx.dispatchOperator("oracle-credential", ctx.argv, { appendHintOnError: true });
|
|
8
|
+
},
|
|
9
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { spawnChild } from "../spawn-child.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "data-mcp",
|
|
6
|
+
summary: "start the read-only data MCP server",
|
|
7
|
+
group: "read",
|
|
8
|
+
usage: "oracle data-mcp",
|
|
9
|
+
async run(ctx) {
|
|
10
|
+
const bin = path.join(ctx.root, "bin", "oracle-data-mcp.mjs");
|
|
11
|
+
return spawnChild(
|
|
12
|
+
process.execPath,
|
|
13
|
+
[bin, ...ctx.argv],
|
|
14
|
+
{ stdio: "inherit", env: process.env },
|
|
15
|
+
"oracle-data-mcp",
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { spawnChild } from "../spawn-child.mjs";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_DATA_URL = "http://127.0.0.1:8787";
|
|
6
|
+
|
|
7
|
+
function usage() {
|
|
8
|
+
process.stderr.write(
|
|
9
|
+
"usage: oracle data <serve|call|catalog|health> [args]\n" +
|
|
10
|
+
" oracle data serve [--port N]\n" +
|
|
11
|
+
" oracle data call <provider> <op> [--args '<json>']\n" +
|
|
12
|
+
" oracle data catalog\n" +
|
|
13
|
+
" oracle data health\n",
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function dataFetch(pathname, { method = "GET", body } = {}) {
|
|
18
|
+
const base = (process.env.ORACLE_DATA_URL || DEFAULT_DATA_URL).replace(/\/$/, "");
|
|
19
|
+
const url = base + pathname;
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(url, {
|
|
22
|
+
method,
|
|
23
|
+
headers: body ? { "content-type": "application/json" } : undefined,
|
|
24
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
25
|
+
});
|
|
26
|
+
const text = await res.text();
|
|
27
|
+
process.stdout.write(text.endsWith("\n") ? text : text + "\n");
|
|
28
|
+
return res.ok ? 0 : 1;
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err && (err.code === "ECONNREFUSED" || /fetch failed|ECONNREFUSED/i.test(String(err)))) {
|
|
31
|
+
process.stderr.write("oracle: data server not running — start it with 'oracle data serve'\n");
|
|
32
|
+
return 4;
|
|
33
|
+
}
|
|
34
|
+
process.stderr.write(`oracle data: ${err.message || err}\n`);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
name: "data", summary: "desk data plane", group: "read",
|
|
41
|
+
usage: "oracle data serve|call|catalog|health",
|
|
42
|
+
async run(ctx) {
|
|
43
|
+
const verb = ctx.argv[0];
|
|
44
|
+
if (!verb || verb === "--help" || verb === "-h") { usage(); return verb ? 0 : 1; }
|
|
45
|
+
if (verb === "serve") {
|
|
46
|
+
let port = null;
|
|
47
|
+
try {
|
|
48
|
+
const { values } = parseArgs({ args: ctx.argv.slice(1), options: { port: { type: "string" } }, allowPositionals: true, strict: false });
|
|
49
|
+
port = values.port || null;
|
|
50
|
+
} catch {}
|
|
51
|
+
const env = { ...process.env };
|
|
52
|
+
if (port) env.ORACLE_DATA_PORT = String(port);
|
|
53
|
+
const bin = path.join(ctx.root, "bin", "desk-server.mjs");
|
|
54
|
+
return spawnChild(process.execPath, [bin], { stdio: "inherit", env }, "desk-server");
|
|
55
|
+
}
|
|
56
|
+
if (verb === "health") return dataFetch("/");
|
|
57
|
+
if (verb === "catalog") return dataFetch("/data/catalog");
|
|
58
|
+
if (verb === "call") {
|
|
59
|
+
const provider = ctx.argv[1];
|
|
60
|
+
const op = ctx.argv[2];
|
|
61
|
+
if (!provider || !op) { usage(); return 1; }
|
|
62
|
+
let argsObj = {};
|
|
63
|
+
const i = ctx.argv.indexOf("--args");
|
|
64
|
+
if (i >= 0) {
|
|
65
|
+
try { argsObj = JSON.parse(ctx.argv[i + 1] || "{}"); }
|
|
66
|
+
catch (err) { process.stderr.write(`oracle data call: invalid --args JSON: ${err.message}\n`); return 1; }
|
|
67
|
+
}
|
|
68
|
+
return dataFetch("/data/call", { method: "POST", body: { provider, op, args: argsObj } });
|
|
69
|
+
}
|
|
70
|
+
usage();
|
|
71
|
+
return 1;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { DOCTOR_SIGNING_OPTIONAL } from "../first-run.mjs";
|
|
4
|
+
|
|
5
|
+
function nodeVersionOk() {
|
|
6
|
+
const [maj, min] = process.versions.node.split(".").map(Number);
|
|
7
|
+
return maj > 20 || (maj === 20 && min >= 19);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function loadExecEnv(p) {
|
|
11
|
+
if (!fs.existsSync(p)) return {};
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const line of fs.readFileSync(p, "utf8").split(/\r?\n/)) {
|
|
14
|
+
const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
15
|
+
if (m) out[m[1]] = m[2];
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function which(bin) {
|
|
21
|
+
for (const dir of (process.env.PATH || "").split(path.delimiter)) {
|
|
22
|
+
const c = path.join(dir, bin);
|
|
23
|
+
try { if (fs.existsSync(c)) return c; } catch {}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function probeDataServer(url) {
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch(url.replace(/\/$/, "") + "/");
|
|
31
|
+
return res.ok;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
name: "doctor",
|
|
39
|
+
summary: "check read plane; checks signer too when installed",
|
|
40
|
+
group: "read",
|
|
41
|
+
usage: "oracle doctor [--json]",
|
|
42
|
+
async run(ctx) {
|
|
43
|
+
const json = ctx.argv.includes("--json");
|
|
44
|
+
const checks = [];
|
|
45
|
+
let failed = false;
|
|
46
|
+
const push = (id, status, detail, fix = null) => {
|
|
47
|
+
checks.push({ id, status, detail, fix });
|
|
48
|
+
if (status === "fail") failed = true;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
push("node", nodeVersionOk() ? "ok" : "fail", `node ${process.versions.node}`,
|
|
52
|
+
nodeVersionOk() ? null : "install node >= 20.19");
|
|
53
|
+
|
|
54
|
+
const execEnvPath = ctx.paths.oracleExecEnvPath();
|
|
55
|
+
const envFile = loadExecEnv(execEnvPath);
|
|
56
|
+
const att =
|
|
57
|
+
process.env.ORACLE_ATTESTATION_SECRET ||
|
|
58
|
+
process.env.ORACLE_STAMP_HMAC_SECRET ||
|
|
59
|
+
envFile.ORACLE_ATTESTATION_SECRET ||
|
|
60
|
+
envFile.ORACLE_STAMP_HMAC_SECRET;
|
|
61
|
+
push("attestation_secret", att ? "ok" : "warn", att ? "present" : "missing",
|
|
62
|
+
att ? null : "run oracle init --apply (writes ~/.config/oracle/exec.env)");
|
|
63
|
+
|
|
64
|
+
const dataUrl = process.env.ORACLE_DATA_URL || "http://127.0.0.1:8787";
|
|
65
|
+
const dataUp = await probeDataServer(dataUrl);
|
|
66
|
+
push("data_server", dataUp ? "ok" : "warn", dataUp ? `reachable ${dataUrl}` : `down ${dataUrl}`,
|
|
67
|
+
dataUp ? null : "oracle data serve");
|
|
68
|
+
|
|
69
|
+
const hermes = which("hermes");
|
|
70
|
+
push("hermes_cli", hermes ? "ok" : "warn", hermes ? `present ${hermes}` : "not on PATH",
|
|
71
|
+
hermes ? null : "install Hermes if you want agent lanes");
|
|
72
|
+
|
|
73
|
+
const profilesDir = path.join(ctx.paths.hermesRoot(), "profiles");
|
|
74
|
+
let laneCount = 0;
|
|
75
|
+
if (fs.existsSync(profilesDir)) {
|
|
76
|
+
laneCount = fs.readdirSync(profilesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
|
|
77
|
+
}
|
|
78
|
+
push("hermes_lanes", laneCount > 0 ? "ok" : "warn",
|
|
79
|
+
laneCount > 0 ? `${laneCount} profile dir(s)` : "no lane profiles found",
|
|
80
|
+
laneCount > 0 ? null : "oracle init --apply");
|
|
81
|
+
|
|
82
|
+
const operator = ctx.resolveOperator();
|
|
83
|
+
let operatorBlock = { installed: false };
|
|
84
|
+
if (operator.ok) {
|
|
85
|
+
const r = ctx.dispatchOperator("oracle-agentic-doctor", ["--json"], { stdio: "pipe" });
|
|
86
|
+
let detail = { installed: true, version: operator.version, source: operator.source };
|
|
87
|
+
if (r && typeof r === "object" && r.stdout) {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(r.stdout);
|
|
90
|
+
detail = {
|
|
91
|
+
installed: true,
|
|
92
|
+
version: operator.version,
|
|
93
|
+
source: operator.source,
|
|
94
|
+
signingReady: parsed.signingReady,
|
|
95
|
+
broadcastReady: parsed.broadcastReady,
|
|
96
|
+
executionReady: parsed.executionReady,
|
|
97
|
+
masterKillSwitch: parsed.masterKillSwitch,
|
|
98
|
+
raw: parsed,
|
|
99
|
+
};
|
|
100
|
+
} catch {
|
|
101
|
+
detail.doctorParseError = true;
|
|
102
|
+
detail.doctorExit = r.code;
|
|
103
|
+
}
|
|
104
|
+
} else if (typeof r === "number") {
|
|
105
|
+
detail.doctorExit = r;
|
|
106
|
+
}
|
|
107
|
+
operatorBlock = detail;
|
|
108
|
+
const signingReady = detail.signingReady === true;
|
|
109
|
+
push(
|
|
110
|
+
"signing",
|
|
111
|
+
signingReady ? "ok" : "warn",
|
|
112
|
+
signingReady
|
|
113
|
+
? `operator ${operator.version} ready via ${operator.source}`
|
|
114
|
+
: `operator ${operator.version} installed but signing is not ready`,
|
|
115
|
+
signingReady ? null : "oracle sign doctor --json",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const report = { ok: !failed, checks, operator: operatorBlock };
|
|
120
|
+
if (json) {
|
|
121
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
122
|
+
} else {
|
|
123
|
+
for (const c of checks) {
|
|
124
|
+
process.stdout.write(`${c.status.toUpperCase().padEnd(4)} ${c.id}: ${c.detail}\n`);
|
|
125
|
+
if (c.fix) process.stdout.write(` fix: ${c.fix}\n`);
|
|
126
|
+
}
|
|
127
|
+
if (!operator.ok) process.stdout.write(`INFO ${DOCTOR_SIGNING_OPTIONAL}\n`);
|
|
128
|
+
}
|
|
129
|
+
return failed ? 1 : 0;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { renderNextSteps } from "../first-run.mjs";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: "init",
|
|
7
|
+
summary: "install agent lanes + read plane (dry-run by default)",
|
|
8
|
+
group: "read",
|
|
9
|
+
usage: "oracle init [--apply --force --only <lane> --json --yes]",
|
|
10
|
+
async run(ctx) {
|
|
11
|
+
const argv = [...ctx.argv];
|
|
12
|
+
if (argv.includes("--yes") && !argv.includes("--apply")) argv.push("--apply");
|
|
13
|
+
const bin = path.join(ctx.root, "bin", "oracle-init.mjs");
|
|
14
|
+
const r = spawnSync(process.execPath, [bin, ...argv], { encoding: "utf8", env: process.env });
|
|
15
|
+
if (r.error) {
|
|
16
|
+
process.stderr.write(`oracle: failed to spawn oracle-init: ${r.error.message}\n`);
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
if (r.stdout) process.stdout.write(r.stdout);
|
|
20
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
21
|
+
const code = typeof r.status === "number" ? r.status : 1;
|
|
22
|
+
if (code === 0 && argv.includes("--apply") && !argv.includes("--json")) {
|
|
23
|
+
process.stdout.write(renderNextSteps());
|
|
24
|
+
}
|
|
25
|
+
return code;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { printOperatorNotInstalled } from "../operator-dispatch.mjs";
|
|
3
|
+
import { installClaudeCode } from "../mcp-targets/claude-code.mjs";
|
|
4
|
+
import { installClaudeDesktop } from "../mcp-targets/claude-desktop.mjs";
|
|
5
|
+
import { installCodex } from "../mcp-targets/codex.mjs";
|
|
6
|
+
import { installChatgpt } from "../mcp-targets/chatgpt.mjs";
|
|
7
|
+
import { buildServerSpecs } from "../mcp-targets/shared.mjs";
|
|
8
|
+
|
|
9
|
+
function usage() {
|
|
10
|
+
process.stderr.write(
|
|
11
|
+
"usage: oracle mcp <install|print> [target] [flags]\n" +
|
|
12
|
+
" targets: claude-code | claude-desktop | codex | chatgpt | generic\n" +
|
|
13
|
+
" flags: --with-control --project --print --json\n",
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function emitResult(result, { json = false } = {}) {
|
|
18
|
+
if (result.print && !result.instructions) {
|
|
19
|
+
process.stdout.write(JSON.stringify(result.print, null, 2) + "\n");
|
|
20
|
+
} else if (result.printText) {
|
|
21
|
+
process.stdout.write(result.printText);
|
|
22
|
+
}
|
|
23
|
+
if (result.instructions) {
|
|
24
|
+
process.stdout.write(result.instructions + "\n");
|
|
25
|
+
if (result.print && json) process.stdout.write(JSON.stringify(result.print, null, 2) + "\n");
|
|
26
|
+
}
|
|
27
|
+
if (!result.print && !result.printText && !result.instructions) {
|
|
28
|
+
const line = {
|
|
29
|
+
status: result.status,
|
|
30
|
+
path: result.path || null,
|
|
31
|
+
backup: result.backup || null,
|
|
32
|
+
method: result.method || null,
|
|
33
|
+
};
|
|
34
|
+
process.stdout.write(
|
|
35
|
+
(json ? JSON.stringify(line, null, 2) : `${line.status}${line.path ? " " + line.path : ""}`) + "\n",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default {
|
|
41
|
+
name: "mcp",
|
|
42
|
+
summary: "wire Oracle into AI clients",
|
|
43
|
+
group: "read",
|
|
44
|
+
usage: "oracle mcp install <target> | oracle mcp print [--target <t>]",
|
|
45
|
+
async run(ctx) {
|
|
46
|
+
const verb = ctx.argv[0];
|
|
47
|
+
if (!verb || verb === "--help" || verb === "-h") {
|
|
48
|
+
usage();
|
|
49
|
+
return verb ? 0 : 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let values, positionals;
|
|
53
|
+
try {
|
|
54
|
+
({ values, positionals } = parseArgs({
|
|
55
|
+
args: ctx.argv.slice(1),
|
|
56
|
+
options: {
|
|
57
|
+
"with-control": { type: "boolean", default: false },
|
|
58
|
+
project: { type: "boolean", default: false },
|
|
59
|
+
print: { type: "boolean", default: false },
|
|
60
|
+
json: { type: "boolean", default: false },
|
|
61
|
+
target: { type: "string" },
|
|
62
|
+
},
|
|
63
|
+
allowPositionals: true,
|
|
64
|
+
strict: false,
|
|
65
|
+
}));
|
|
66
|
+
} catch (err) {
|
|
67
|
+
process.stderr.write(`oracle mcp: ${err.message}\n`);
|
|
68
|
+
usage();
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const withControl = !!values["with-control"];
|
|
73
|
+
const project = !!values.project;
|
|
74
|
+
const json = !!values.json;
|
|
75
|
+
const operator = ctx.resolveOperator ? ctx.resolveOperator() : { ok: false };
|
|
76
|
+
|
|
77
|
+
if (withControl && (!operator || !operator.ok)) {
|
|
78
|
+
printOperatorNotInstalled(process.stderr);
|
|
79
|
+
return 3;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const opts = { oracleRoot: ctx.root, operator, withControl, project, cwd: process.cwd() };
|
|
83
|
+
|
|
84
|
+
if (verb === "print") {
|
|
85
|
+
const target = values.target || positionals[0] || "claude-code";
|
|
86
|
+
if (target === "generic" || target === "claude-code") {
|
|
87
|
+
const built = buildServerSpecs(opts);
|
|
88
|
+
if (!built.ok) { printOperatorNotInstalled(process.stderr); return 3; }
|
|
89
|
+
process.stdout.write(JSON.stringify({ mcpServers: built.specs }, null, 2) + "\n");
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
if (target === "chatgpt") {
|
|
93
|
+
const r = await installChatgpt({ printOnly: true });
|
|
94
|
+
emitResult(r, { json });
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
if (target === "codex") {
|
|
98
|
+
emitResult(await installCodex({ ...opts, printOnly: true }), { json });
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
if (target === "claude-desktop") {
|
|
102
|
+
emitResult(await installClaudeDesktop({ ...opts, printOnly: true }), { json });
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
process.stderr.write(`oracle mcp: unknown target '${target}'\n`);
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (verb === "install") {
|
|
110
|
+
const target = positionals[0] || values.target;
|
|
111
|
+
if (!target) { usage(); return 1; }
|
|
112
|
+
const printOnly = !!values.print;
|
|
113
|
+
let result;
|
|
114
|
+
if (target === "claude-code") result = await installClaudeCode({ ...opts, printOnly });
|
|
115
|
+
else if (target === "claude-desktop") result = await installClaudeDesktop({ ...opts, printOnly });
|
|
116
|
+
else if (target === "codex") result = await installCodex({ ...opts, printOnly });
|
|
117
|
+
else if (target === "chatgpt") result = await installChatgpt({ printOnly });
|
|
118
|
+
else if (target === "generic") {
|
|
119
|
+
const built = buildServerSpecs(opts);
|
|
120
|
+
if (!built.ok) { printOperatorNotInstalled(process.stderr); return 3; }
|
|
121
|
+
process.stdout.write(JSON.stringify({ mcpServers: built.specs }, null, 2) + "\n");
|
|
122
|
+
return 0;
|
|
123
|
+
} else {
|
|
124
|
+
process.stderr.write(`oracle mcp: unknown target '${target}'\n`);
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
if (!result.ok) {
|
|
128
|
+
if (result.code === 3) printOperatorNotInstalled(process.stderr);
|
|
129
|
+
else process.stderr.write(`oracle mcp: ${result.reason || "failed"}\n`);
|
|
130
|
+
return result.code || 1;
|
|
131
|
+
}
|
|
132
|
+
emitResult(result, { json });
|
|
133
|
+
if (result.status && String(result.status).startsWith("printed") && result.print) {
|
|
134
|
+
process.stdout.write(JSON.stringify(result.print, null, 2) + "\n");
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
usage();
|
|
140
|
+
return 1;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export default {
|
|
4
|
+
name: "prepare", summary: "build an unsigned swap (alias of route prepare)", group: "read",
|
|
5
|
+
usage: "oracle prepare <chain> <tokenIn> <tokenOut> <taker> [amt]",
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
const bin = path.join(ctx.root, "bin", "oracle-route.mjs");
|
|
8
|
+
const r = spawnSync(process.execPath, [bin, "prepare", ...ctx.argv], { stdio: "inherit" });
|
|
9
|
+
if (r.error) { process.stderr.write(`oracle: failed to spawn oracle-route: ${r.error.message}\n`); return 1; }
|
|
10
|
+
return typeof r.status === "number" ? r.status : 1;
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { spawnChild } from "../spawn-child.mjs";
|
|
4
|
+
export default {
|
|
5
|
+
name: "public", summary: "secret-free public HTTP surface", group: "read",
|
|
6
|
+
usage: "oracle public serve [--port N]",
|
|
7
|
+
async run(ctx) {
|
|
8
|
+
const verb = ctx.argv[0] || "serve";
|
|
9
|
+
if (verb !== "serve") { process.stderr.write("usage: oracle public serve [--port N]\n"); return 1; }
|
|
10
|
+
let port = null;
|
|
11
|
+
try {
|
|
12
|
+
const { values } = parseArgs({ args: ctx.argv.slice(1), options: { port: { type: "string" } }, allowPositionals: true, strict: false });
|
|
13
|
+
port = values.port || null;
|
|
14
|
+
} catch {}
|
|
15
|
+
const env = { ...process.env };
|
|
16
|
+
if (port) env.ORACLE_PUBLIC_PORT = String(port);
|
|
17
|
+
const bin = path.join(ctx.root, "bin", "oracle-public-server.mjs");
|
|
18
|
+
return spawnChild(process.execPath, [bin], { stdio: "inherit", env }, "oracle-public-server");
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export default {
|
|
4
|
+
name: "route", summary: "swap/bridge prepare helpers", group: "read",
|
|
5
|
+
usage: "oracle route <swap|bridge|prepare|prepare-bridge> ...",
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
const bin = path.join(ctx.root, "bin", "oracle-route.mjs");
|
|
8
|
+
const r = spawnSync(process.execPath, [bin, ...ctx.argv], { stdio: "inherit" });
|
|
9
|
+
if (r.error) { process.stderr.write(`oracle: failed to spawn oracle-route: ${r.error.message}\n`); return 1; }
|
|
10
|
+
return typeof r.status === "number" ? r.status : 1;
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export default {
|
|
4
|
+
name: "scan", summary: "chain scanner", group: "read",
|
|
5
|
+
usage: "oracle scan <chains|head|token|pools|risk|quote|sell> ...",
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
const bin = path.join(ctx.root, "bin", "oracle-scan.mjs");
|
|
8
|
+
const r = spawnSync(process.execPath, [bin, ...ctx.argv], { stdio: "inherit" });
|
|
9
|
+
if (r.error) { process.stderr.write(`oracle: failed to spawn oracle-scan: ${r.error.message}\n`); return 1; }
|
|
10
|
+
return typeof r.status === "number" ? r.status : 1;
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
name: "sign",
|
|
3
|
+
summary: "provision / check the local signer (opt-in)",
|
|
4
|
+
group: "sign",
|
|
5
|
+
usage: "oracle sign <init|doctor> ...",
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
const verb = ctx.argv[0];
|
|
8
|
+
if (verb === "init") {
|
|
9
|
+
return ctx.dispatchOperator("oracle-agentic-init", ctx.argv.slice(1), { appendHintOnError: true });
|
|
10
|
+
}
|
|
11
|
+
if (verb === "doctor") {
|
|
12
|
+
return ctx.dispatchOperator("oracle-agentic-doctor", ctx.argv.slice(1), { appendHintOnError: true });
|
|
13
|
+
}
|
|
14
|
+
process.stderr.write(
|
|
15
|
+
"usage: oracle sign <init|doctor> ...\n" +
|
|
16
|
+
" oracle sign init provision keys + policy (interactive)\n" +
|
|
17
|
+
" oracle sign doctor check local signer readiness\n",
|
|
18
|
+
);
|
|
19
|
+
return 1;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export default {
|
|
4
|
+
name: "upgrade", summary: "upgrade installed Hermes lanes", group: "read",
|
|
5
|
+
usage: "oracle upgrade [--apply ...]",
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
const bin = path.join(ctx.root, "bin", "oracle-upgrade.mjs");
|
|
8
|
+
const r = spawnSync(process.execPath, [bin, ...ctx.argv], { stdio: "inherit" });
|
|
9
|
+
if (r.error) { process.stderr.write(`oracle: failed to spawn oracle-upgrade: ${r.error.message}\n`); return 1; }
|
|
10
|
+
return typeof r.status === "number" ? r.status : 1;
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
name: "vault",
|
|
3
|
+
summary: "encrypt local key files at rest",
|
|
4
|
+
group: "sign",
|
|
5
|
+
usage: "oracle vault <encrypt|rekey|inspect|decrypt> ...",
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
return ctx.dispatchOperator("oracle-vault", ctx.argv, { appendHintOnError: true });
|
|
8
|
+
},
|
|
9
|
+
};
|