@oracle-agent/oracle 0.4.1 → 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 +74 -20
- package/SETUP.md +15 -2
- 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/docs/oracle-pack-standard.md +29 -0
- package/docs/profiles.md +1 -1
- package/examples/oracle-pack-template.mjs +38 -0
- package/package.json +13 -4
- package/profiles/oracle/SOUL.md +3 -1
- package/profiles/robinhood-agent/SOUL.md +5 -3
- package/public/agent-connect/app.js +84 -0
- package/public/agent-connect/index.html +83 -0
- package/public/agent-connect/styles.css +70 -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/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/adversarial-bench.mjs +114 -0
- package/scripts/check-doc-drift.mjs +111 -0
- package/skills/oracle-action-semantics/SKILL.md +1 -1
- package/src/action-receipts.mjs +168 -0
- package/src/address-book.mjs +53 -5
- 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/exec-policy.mjs +6 -0
- package/src/index.mjs +24 -0
- package/src/onboarding/harness-configs.mjs +102 -0
- package/src/oracle-env.mjs +11 -2
- package/src/portfolio-risk.mjs +170 -0
- package/src/public-api/connect-agent.mjs +29 -10
- package/src/signals/engine.mjs +146 -0
- package/src/signals/index.mjs +1 -0
- package/src/watch-preferences.mjs +85 -0
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveOperator } from "../operator-dispatch.mjs";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: "version",
|
|
7
|
+
summary: "print oracle (and operator, if present) version",
|
|
8
|
+
group: "meta",
|
|
9
|
+
usage: "oracle version",
|
|
10
|
+
async run(ctx) {
|
|
11
|
+
let version = "0.0.0";
|
|
12
|
+
try {
|
|
13
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(ctx.root, "package.json"), "utf8"));
|
|
14
|
+
version = pkg.version || version;
|
|
15
|
+
} catch {}
|
|
16
|
+
let line = `oracle ${version}`;
|
|
17
|
+
const op = (ctx.resolveOperator || resolveOperator)();
|
|
18
|
+
if (op.ok) line += ` operator ${op.version}`;
|
|
19
|
+
process.stdout.write(line + "\n");
|
|
20
|
+
return 0;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const NEXT_STEPS_AFTER_INIT = [
|
|
2
|
+
"oracle data serve # loopback read/prepare plane on 127.0.0.1:8787",
|
|
3
|
+
"oracle doctor # verify",
|
|
4
|
+
"oracle mcp install claude-code",
|
|
5
|
+
"# optional, explicit second step — signing stays a separate local package:",
|
|
6
|
+
"npm i -g @oracle-agent/operator",
|
|
7
|
+
"oracle sign init",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export function renderNextSteps() {
|
|
11
|
+
return [
|
|
12
|
+
"",
|
|
13
|
+
"Next:",
|
|
14
|
+
...NEXT_STEPS_AFTER_INIT.map((l) => " " + l),
|
|
15
|
+
"",
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DOCTOR_SIGNING_OPTIONAL =
|
|
20
|
+
"signing: not installed (optional) — npm i -g @oracle-agent/operator";
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { PACKAGE_ROOT, paths } from "./paths.mjs";
|
|
5
|
+
import {
|
|
6
|
+
dispatchOperator,
|
|
7
|
+
resolveOperator,
|
|
8
|
+
printOperatorNotInstalled,
|
|
9
|
+
SIGN_NOUNS,
|
|
10
|
+
} from "./operator-dispatch.mjs";
|
|
11
|
+
|
|
12
|
+
const HELP_HEADER = `oracle — prepare-only multichain agent control plane`;
|
|
13
|
+
|
|
14
|
+
const STATIC_HELP = `READ / RESEARCH (no keys, this package)
|
|
15
|
+
oracle init install agent lanes + read plane (dry-run by default)
|
|
16
|
+
oracle doctor check read plane; checks signer too when installed
|
|
17
|
+
oracle data serve|call|catalog|health
|
|
18
|
+
oracle data-mcp start the read-only MCP server
|
|
19
|
+
oracle scan chains|head|token|pools|risk|quote|sell
|
|
20
|
+
oracle route swap|bridge|prepare|prepare-bridge
|
|
21
|
+
oracle prepare build an unsigned swap (alias of route prepare)
|
|
22
|
+
oracle public serve secret-free public HTTP surface
|
|
23
|
+
oracle mcp install <t> wire Oracle into claude-code|claude-desktop|codex|chatgpt
|
|
24
|
+
oracle upgrade upgrade installed Hermes lanes
|
|
25
|
+
|
|
26
|
+
SIGNING (dispatches to @oracle-agent/operator on THIS machine; never in this package)
|
|
27
|
+
oracle sign init|doctor provision / check the local signer (opt-in)
|
|
28
|
+
oracle vault ... encrypt local key files at rest
|
|
29
|
+
oracle signer / runner loopback signer daemon / action runner
|
|
30
|
+
oracle credential ... OS credential store glue
|
|
31
|
+
|
|
32
|
+
oracle <noun> --help for details. Keys never leave your machine; this package
|
|
33
|
+
cannot sign — signing commands run the locally installed operator package.
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
function readPackageVersion() {
|
|
37
|
+
try {
|
|
38
|
+
const pkg = JSON.parse(
|
|
39
|
+
fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8"),
|
|
40
|
+
);
|
|
41
|
+
return pkg.version || "0.0.0";
|
|
42
|
+
} catch {
|
|
43
|
+
return "0.0.0";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function commandsDir() {
|
|
48
|
+
return path.join(PACKAGE_ROOT, "src", "cli", "commands");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function discoverCommands() {
|
|
52
|
+
const dir = commandsDir();
|
|
53
|
+
const out = new Map();
|
|
54
|
+
if (!fs.existsSync(dir)) return out;
|
|
55
|
+
const entries = fs
|
|
56
|
+
.readdirSync(dir)
|
|
57
|
+
.filter((f) => f.endsWith(".mjs"))
|
|
58
|
+
.sort();
|
|
59
|
+
for (const file of entries) {
|
|
60
|
+
const full = path.join(dir, file);
|
|
61
|
+
try {
|
|
62
|
+
const mod = await import(pathToFileURL(full).href + `?t=${Date.now()}`);
|
|
63
|
+
const cmd = mod.default;
|
|
64
|
+
if (!cmd || typeof cmd !== "object" || typeof cmd.run !== "function") {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const name = cmd.name || file.replace(/\.mjs$/, "");
|
|
68
|
+
out.set(name, { ...cmd, name, __file: full });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (process.env.ORACLE_CLI_DEBUG) {
|
|
71
|
+
console.error(`oracle: failed loading command ${file}: ${err.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function renderHelp(version, commands) {
|
|
79
|
+
const lines = [];
|
|
80
|
+
lines.push(`${HELP_HEADER} (v${version})`);
|
|
81
|
+
lines.push("");
|
|
82
|
+
lines.push(STATIC_HELP.trimEnd());
|
|
83
|
+
|
|
84
|
+
const extras = [];
|
|
85
|
+
for (const cmd of commands.values()) {
|
|
86
|
+
if (["help", "version"].includes(cmd.name)) continue;
|
|
87
|
+
const known = new Set([
|
|
88
|
+
"init","doctor","data","data-mcp","scan","route","prepare","public","mcp","upgrade",
|
|
89
|
+
"sign","vault","signer","runner","credential",
|
|
90
|
+
]);
|
|
91
|
+
if (!known.has(cmd.name) && cmd.summary) {
|
|
92
|
+
extras.push(` oracle ${cmd.name.padEnd(20)} ${cmd.summary}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (extras.length) {
|
|
96
|
+
lines.push("");
|
|
97
|
+
lines.push("ADDITIONAL");
|
|
98
|
+
lines.push(...extras);
|
|
99
|
+
}
|
|
100
|
+
return lines.join("\n") + "\n";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function unknownNounMessage(noun, commands) {
|
|
104
|
+
const known = [...commands.keys()].sort();
|
|
105
|
+
const list = known.length ? known.join(", ") : "(none loaded yet)";
|
|
106
|
+
return `oracle: unknown command '${noun}'. Known: ${list}\nTry: oracle --help\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildCtx(argv) {
|
|
110
|
+
return {
|
|
111
|
+
argv,
|
|
112
|
+
root: PACKAGE_ROOT,
|
|
113
|
+
paths,
|
|
114
|
+
dispatchOperator,
|
|
115
|
+
resolveOperator,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function run(argv = []) {
|
|
120
|
+
const version = readPackageVersion();
|
|
121
|
+
|
|
122
|
+
if (argv.includes("--version") || argv.includes("-V")) {
|
|
123
|
+
return runVersion();
|
|
124
|
+
}
|
|
125
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
126
|
+
const commands = await discoverCommands();
|
|
127
|
+
process.stdout.write(renderHelp(version, commands));
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const noun = argv[0];
|
|
132
|
+
const rest = argv.slice(1);
|
|
133
|
+
|
|
134
|
+
if (noun === "version") {
|
|
135
|
+
return runVersion();
|
|
136
|
+
}
|
|
137
|
+
if (noun === "help") {
|
|
138
|
+
const commands = await discoverCommands();
|
|
139
|
+
if (rest[0]) {
|
|
140
|
+
const cmd = commands.get(rest[0]);
|
|
141
|
+
if (cmd) {
|
|
142
|
+
process.stdout.write((cmd.usage || `oracle ${cmd.name}`) + "\n");
|
|
143
|
+
if (cmd.summary) process.stdout.write(cmd.summary + "\n");
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
process.stderr.write(unknownNounMessage(rest[0], commands));
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
process.stdout.write(renderHelp(version, commands));
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const commands = await discoverCommands();
|
|
154
|
+
const cmd = commands.get(noun);
|
|
155
|
+
|
|
156
|
+
if (cmd) {
|
|
157
|
+
if (rest[0] === "--help" || rest[0] === "-h") {
|
|
158
|
+
process.stdout.write((cmd.usage || `oracle ${cmd.name}`) + "\n");
|
|
159
|
+
if (cmd.summary) process.stdout.write(cmd.summary + "\n");
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const code = await cmd.run(buildCtx(rest));
|
|
164
|
+
return typeof code === "number" ? code : 0;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
process.stderr.write(`oracle ${noun}: ${err?.message || err}\n`);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (SIGN_NOUNS.includes(noun)) {
|
|
172
|
+
const resolved = resolveOperator();
|
|
173
|
+
if (!resolved.ok) {
|
|
174
|
+
printOperatorNotInstalled(process.stderr);
|
|
175
|
+
return 3;
|
|
176
|
+
}
|
|
177
|
+
process.stderr.write(
|
|
178
|
+
`oracle: sign-plane command '${noun}' is not available in this build (operator is installed).\n`,
|
|
179
|
+
);
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
process.stderr.write(unknownNounMessage(noun, commands));
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function runVersion() {
|
|
188
|
+
const version = readPackageVersion();
|
|
189
|
+
let line = `oracle ${version}`;
|
|
190
|
+
const op = resolveOperator();
|
|
191
|
+
if (op.ok) {
|
|
192
|
+
line += ` operator ${op.version}`;
|
|
193
|
+
}
|
|
194
|
+
process.stdout.write(line + "\n");
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export default { run, discoverCommands, renderHelp };
|