@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,143 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import {
|
|
4
|
+
findWorkingChain,
|
|
5
|
+
listWorkingChains,
|
|
6
|
+
renderChainList,
|
|
7
|
+
} from "../chain-catalog.mjs";
|
|
8
|
+
import {
|
|
9
|
+
clearActiveChain,
|
|
10
|
+
readActiveChain,
|
|
11
|
+
writeActiveChain,
|
|
12
|
+
} from "../chain-state.mjs";
|
|
13
|
+
|
|
14
|
+
function usage() {
|
|
15
|
+
return [
|
|
16
|
+
"oracle chain — pick the build/trade surface",
|
|
17
|
+
"",
|
|
18
|
+
" oracle chain list working chains",
|
|
19
|
+
" oracle chain list same",
|
|
20
|
+
" oracle chain show show active chain",
|
|
21
|
+
" oracle chain use <name> select (hyperliquid, base, solana, ...)",
|
|
22
|
+
" oracle chain clear unset",
|
|
23
|
+
" oracle chain --json machine output",
|
|
24
|
+
"",
|
|
25
|
+
"chat:",
|
|
26
|
+
" /chain",
|
|
27
|
+
" /chain hyperliquid",
|
|
28
|
+
" /chain show",
|
|
29
|
+
].join("\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function printList(json) {
|
|
33
|
+
const active = readActiveChain();
|
|
34
|
+
if (json) {
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
JSON.stringify(
|
|
37
|
+
{
|
|
38
|
+
active,
|
|
39
|
+
chains: listWorkingChains(),
|
|
40
|
+
},
|
|
41
|
+
null,
|
|
42
|
+
2,
|
|
43
|
+
) + "\n",
|
|
44
|
+
);
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
process.stdout.write(
|
|
48
|
+
renderChainList({ selectedKey: active?.key || null }),
|
|
49
|
+
);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function printShow(json) {
|
|
54
|
+
const active = readActiveChain();
|
|
55
|
+
if (json) {
|
|
56
|
+
process.stdout.write(JSON.stringify({ active }, null, 2) + "\n");
|
|
57
|
+
return active ? 0 : 1;
|
|
58
|
+
}
|
|
59
|
+
if (!active) {
|
|
60
|
+
process.stdout.write("active: none\nrun: oracle chain use hyperliquid\n");
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
process.stdout.write(
|
|
64
|
+
`active: ${active.key} id=${active.chainId} agent=${active.agent}\n`,
|
|
65
|
+
);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function useChain(query, json) {
|
|
70
|
+
try {
|
|
71
|
+
const active = writeActiveChain(query);
|
|
72
|
+
if (json) {
|
|
73
|
+
process.stdout.write(JSON.stringify({ ok: true, active }, null, 2) + "\n");
|
|
74
|
+
} else {
|
|
75
|
+
process.stdout.write(
|
|
76
|
+
`selected ${active.key} (${active.chainId}) · agent ${active.agent}\n` +
|
|
77
|
+
`build/trade context pinned for this machine\n`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (err?.code === "UNKNOWN_CHAIN") {
|
|
83
|
+
process.stderr.write(`oracle chain: unknown '${query}'\n`);
|
|
84
|
+
process.stderr.write("try: oracle chain list\n");
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default {
|
|
92
|
+
name: "chain",
|
|
93
|
+
summary: "list/select working chains (hyperliquid, base, solana, ...)",
|
|
94
|
+
group: "read",
|
|
95
|
+
usage: usage(),
|
|
96
|
+
async run(ctx) {
|
|
97
|
+
const args = [...ctx.argv];
|
|
98
|
+
const json = args.includes("--json");
|
|
99
|
+
const clean = args.filter((a) => a !== "--json");
|
|
100
|
+
const verb = (clean[0] || "list").toLowerCase();
|
|
101
|
+
const rest = clean.slice(1);
|
|
102
|
+
|
|
103
|
+
if (["-h", "--help", "help"].includes(verb)) {
|
|
104
|
+
process.stdout.write(usage() + "\n");
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
if (["list", "ls"].includes(verb)) return printList(json);
|
|
108
|
+
if (["show", "status", "current"].includes(verb)) return printShow(json);
|
|
109
|
+
if (["clear", "unset", "none"].includes(verb)) {
|
|
110
|
+
clearActiveChain();
|
|
111
|
+
process.stdout.write(json ? "{\"ok\":true,\"active\":null}\n" : "active chain cleared\n");
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
if (["use", "set", "select", "pick"].includes(verb)) {
|
|
115
|
+
const q = rest.join(" ").trim();
|
|
116
|
+
if (!q) {
|
|
117
|
+
process.stderr.write("usage: oracle chain use <name>\n");
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
return useChain(q, json);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// bare token: oracle chain hyperliquid
|
|
124
|
+
if (findWorkingChain(verb)) return useChain(verb, json);
|
|
125
|
+
|
|
126
|
+
// interactive pick when tty and no args beyond list default already handled
|
|
127
|
+
if (!clean.length && process.stdin.isTTY) {
|
|
128
|
+
const chains = listWorkingChains();
|
|
129
|
+
process.stdout.write(renderChainList({ selectedKey: readActiveChain()?.key }));
|
|
130
|
+
const rl = readline.createInterface({ input, output });
|
|
131
|
+
try {
|
|
132
|
+
const answer = (await rl.question("chain> ")).trim();
|
|
133
|
+
if (!answer) return 0;
|
|
134
|
+
return useChain(answer, false);
|
|
135
|
+
} finally {
|
|
136
|
+
rl.close();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
process.stderr.write(`oracle chain: unknown verb '${verb}'\n${usage()}\n`);
|
|
141
|
+
return 1;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { PACKAGE_ROOT, hermesRoot, ensureDir } from "../paths.mjs";
|
|
5
|
+
import { activeChainEnv } from "../chain-state.mjs";
|
|
6
|
+
import { spawnChild } from "../spawn-child.mjs";
|
|
7
|
+
|
|
8
|
+
const PROFILE = "oracle";
|
|
9
|
+
const SKIN_NAME = "oracle";
|
|
10
|
+
|
|
11
|
+
function which(bin) {
|
|
12
|
+
if (bin === "hermes" && process.env.ORACLE_HERMES_BIN) {
|
|
13
|
+
return process.env.ORACLE_HERMES_BIN;
|
|
14
|
+
}
|
|
15
|
+
for (const dir of (process.env.PATH || "").split(path.delimiter)) {
|
|
16
|
+
const c = path.join(dir, bin);
|
|
17
|
+
try {
|
|
18
|
+
if (fs.existsSync(c)) return c;
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function pythonShebang(file) {
|
|
25
|
+
try {
|
|
26
|
+
const first = fs.readFileSync(file, "utf8").split(/\r?\n/, 1)[0];
|
|
27
|
+
if (!first.startsWith("#!")) return null;
|
|
28
|
+
const parts = first.slice(2).trim().split(/\s+/);
|
|
29
|
+
if (parts[0]?.endsWith("/env") && parts[1]?.startsWith("python")) {
|
|
30
|
+
return { command: parts[0], prefix: [parts[1]] };
|
|
31
|
+
}
|
|
32
|
+
if (path.basename(parts[0] || "").startsWith("python")) {
|
|
33
|
+
return { command: parts[0], prefix: parts.slice(1) };
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveHermesPython(hermes) {
|
|
40
|
+
const direct = pythonShebang(hermes);
|
|
41
|
+
if (direct) return direct;
|
|
42
|
+
try {
|
|
43
|
+
const body = fs.readFileSync(hermes, "utf8");
|
|
44
|
+
const match = body.match(/["']([^"'\r\n]+\/bin\/hermes)["']/);
|
|
45
|
+
if (match) return pythonShebang(match[1]);
|
|
46
|
+
} catch {}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function harnessSourcePath() {
|
|
51
|
+
return path.join(PACKAGE_ROOT, "src", "cli", "oracle-harness.py");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function skinSourcePath() {
|
|
55
|
+
return path.join(PACKAGE_ROOT, "skins", "oracle.yaml");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ensureOracleSkin(profile = PROFILE) {
|
|
59
|
+
const src = skinSourcePath();
|
|
60
|
+
if (!fs.existsSync(src)) return { ok: false, reason: "skin missing from package" };
|
|
61
|
+
const rootSkins = path.join(hermesRoot(), "skins");
|
|
62
|
+
const profileSkins = path.join(hermesRoot(), "profiles", profile, "skins");
|
|
63
|
+
ensureDir(rootSkins);
|
|
64
|
+
ensureDir(profileSkins);
|
|
65
|
+
const body = fs.readFileSync(src, "utf8");
|
|
66
|
+
fs.writeFileSync(path.join(rootSkins, `${SKIN_NAME}.yaml`), body);
|
|
67
|
+
fs.writeFileSync(path.join(profileSkins, `${SKIN_NAME}.yaml`), body);
|
|
68
|
+
return { ok: true, skin: SKIN_NAME };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ensureQuickCommands(profile = PROFILE) {
|
|
72
|
+
// Install /chain and /setup as hermes quick commands that call oracle.
|
|
73
|
+
const cfgPath = path.join(hermesRoot(), "profiles", profile, "config.yaml");
|
|
74
|
+
if (!fs.existsSync(cfgPath)) return { ok: false, reason: "profile config missing" };
|
|
75
|
+
|
|
76
|
+
// Prefer a tiny sidecar json the chat skill can also read; hermes quick_commands
|
|
77
|
+
// live in config.yaml which we must not corrupt with naive YAML mutation.
|
|
78
|
+
// We install a hermes-facing skill command instead (see skills/oracle-chat).
|
|
79
|
+
const oracleBin = which("oracle") || path.join(PACKAGE_ROOT, "bin", "oracle.mjs");
|
|
80
|
+
const markerDir = path.join(hermesRoot(), "profiles", profile, "oracle");
|
|
81
|
+
ensureDir(markerDir);
|
|
82
|
+
const marker = {
|
|
83
|
+
profile,
|
|
84
|
+
skin: SKIN_NAME,
|
|
85
|
+
oracleBin,
|
|
86
|
+
commands: {
|
|
87
|
+
chain: [oracleBin, "chain"],
|
|
88
|
+
setup: [oracleBin, "setup"],
|
|
89
|
+
},
|
|
90
|
+
updatedAt: new Date().toISOString(),
|
|
91
|
+
};
|
|
92
|
+
fs.writeFileSync(
|
|
93
|
+
path.join(markerDir, "chat-surface.json"),
|
|
94
|
+
JSON.stringify(marker, null, 2) + "\n",
|
|
95
|
+
{ mode: 0o600 },
|
|
96
|
+
);
|
|
97
|
+
return { ok: true, marker };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function ensureSkillCommands(profile = PROFILE) {
|
|
101
|
+
const srcDir = path.join(PACKAGE_ROOT, "skills", "oracle-chat");
|
|
102
|
+
if (!fs.existsSync(srcDir)) return { ok: false, reason: "oracle-chat skill missing" };
|
|
103
|
+
const destRoot = path.join(hermesRoot(), "profiles", profile, "skills", "oracle-chat");
|
|
104
|
+
ensureDir(destRoot);
|
|
105
|
+
for (const name of fs.readdirSync(srcDir)) {
|
|
106
|
+
const from = path.join(srcDir, name);
|
|
107
|
+
const to = path.join(destRoot, name);
|
|
108
|
+
if (fs.statSync(from).isFile()) {
|
|
109
|
+
fs.copyFileSync(from, to);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Also install under profile skills root aliases for /chain and /setup.
|
|
113
|
+
for (const slug of ["chain", "setup"]) {
|
|
114
|
+
const aliasDir = path.join(hermesRoot(), "profiles", profile, "skills", slug);
|
|
115
|
+
ensureDir(aliasDir);
|
|
116
|
+
const skillMd = path.join(srcDir, `${slug}.SKILL.md`);
|
|
117
|
+
const fallback = path.join(srcDir, "SKILL.md");
|
|
118
|
+
const body = fs.existsSync(skillMd)
|
|
119
|
+
? fs.readFileSync(skillMd, "utf8")
|
|
120
|
+
: fs.readFileSync(fallback, "utf8");
|
|
121
|
+
fs.writeFileSync(path.join(aliasDir, "SKILL.md"), body);
|
|
122
|
+
}
|
|
123
|
+
return { ok: true };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function setDisplaySkin(profile = PROFILE) {
|
|
127
|
+
const hermes = which("hermes");
|
|
128
|
+
if (!hermes) return { ok: false, reason: "hermes missing" };
|
|
129
|
+
// hermes config set is safe; avoids hand-editing yaml.
|
|
130
|
+
const r = spawnSync(
|
|
131
|
+
hermes,
|
|
132
|
+
["-p", profile, "config", "set", "display.skin", SKIN_NAME],
|
|
133
|
+
{ encoding: "utf8", timeout: 30_000 },
|
|
134
|
+
);
|
|
135
|
+
if (r.status !== 0) {
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
reason: (r.stderr || r.stdout || "config set failed").trim(),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return { ok: true };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function usage() {
|
|
145
|
+
return [
|
|
146
|
+
"oracle chat — one lowercase oracle surface",
|
|
147
|
+
"",
|
|
148
|
+
" oracle chat open interactive oracle chat",
|
|
149
|
+
" oracle chat -q \"...\" one-shot query",
|
|
150
|
+
" oracle chat --model <m> pass model to hermes for this session",
|
|
151
|
+
" oracle chat --print-banner show plain ascii banner only",
|
|
152
|
+
"",
|
|
153
|
+
"inside chat:",
|
|
154
|
+
" /chain list/select chains",
|
|
155
|
+
" /setup messaging setup menu",
|
|
156
|
+
" /model switch models (still one oracle persona)",
|
|
157
|
+
"",
|
|
158
|
+
"brand: plain lowercase ascii oracle",
|
|
159
|
+
].join("\n");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function printBanner() {
|
|
163
|
+
const lines = [
|
|
164
|
+
"",
|
|
165
|
+
" _",
|
|
166
|
+
" ___ _ _ __ _ __| |___",
|
|
167
|
+
"/ _ \\ '_/ _` / _| / -_)",
|
|
168
|
+
"\\___/_| \\__,_\\__|_\\___|",
|
|
169
|
+
"",
|
|
170
|
+
];
|
|
171
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export default {
|
|
175
|
+
name: "chat",
|
|
176
|
+
summary: "open the one oracle chat surface (telegram-like, multi-model)",
|
|
177
|
+
group: "read",
|
|
178
|
+
usage: usage(),
|
|
179
|
+
async run(ctx) {
|
|
180
|
+
const args = [...ctx.argv];
|
|
181
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
182
|
+
process.stdout.write(usage() + "\n");
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
if (args.includes("--print-banner")) {
|
|
186
|
+
printBanner();
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const hermes = which("hermes");
|
|
191
|
+
if (!hermes) {
|
|
192
|
+
process.stderr.write(
|
|
193
|
+
"oracle chat needs hermes on PATH\n" +
|
|
194
|
+
"install: https://hermes-agent.nousresearch.com/docs\n",
|
|
195
|
+
);
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
ensureOracleSkin(PROFILE);
|
|
200
|
+
ensureQuickCommands(PROFILE);
|
|
201
|
+
ensureSkillCommands(PROFILE);
|
|
202
|
+
const skinSet = setDisplaySkin(PROFILE);
|
|
203
|
+
if (!skinSet.ok && process.env.ORACLE_CLI_DEBUG) {
|
|
204
|
+
process.stderr.write(`oracle chat: skin note: ${skinSet.reason}\n`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Parse a few passthrough flags.
|
|
208
|
+
const hermesArgs = ["-p", PROFILE, "chat"];
|
|
209
|
+
const pass = [];
|
|
210
|
+
for (let i = 0; i < args.length; i++) {
|
|
211
|
+
const a = args[i];
|
|
212
|
+
if (a === "--model" || a === "-m") {
|
|
213
|
+
if (args[i + 1]) {
|
|
214
|
+
pass.push("--model", args[++i]);
|
|
215
|
+
}
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (a === "--provider") {
|
|
219
|
+
if (args[i + 1]) {
|
|
220
|
+
pass.push("--provider", args[++i]);
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (a === "-q" || a === "--query") {
|
|
225
|
+
if (args[i + 1]) {
|
|
226
|
+
pass.push("-q", args[++i]);
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (a === "--quiet" || a === "-Q") {
|
|
231
|
+
pass.push("--quiet");
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (a === "--continue" || a === "-c") {
|
|
235
|
+
pass.push("--continue");
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
// unknown flags go through
|
|
239
|
+
pass.push(a);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// hermes -p scopes the same profile used by Telegram. Do not replace
|
|
243
|
+
// HERMES_HOME with the profile dir; nested profile resolution would break.
|
|
244
|
+
const cleanEnv = activeChainEnv({
|
|
245
|
+
ORACLE_CHAT_SURFACE: "1",
|
|
246
|
+
ORACLE_PROFILE: PROFILE,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const harness = harnessSourcePath();
|
|
250
|
+
const runtime = process.env.ORACLE_PLAIN_HARNESS === "0"
|
|
251
|
+
? null
|
|
252
|
+
: resolveHermesPython(hermes);
|
|
253
|
+
const command = runtime && fs.existsSync(harness) ? runtime.command : hermes;
|
|
254
|
+
const launchArgs = runtime && fs.existsSync(harness)
|
|
255
|
+
? [...runtime.prefix, harness, ...hermesArgs, ...pass]
|
|
256
|
+
: [...hermesArgs, ...pass];
|
|
257
|
+
|
|
258
|
+
return spawnChild(
|
|
259
|
+
command,
|
|
260
|
+
launchArgs,
|
|
261
|
+
{
|
|
262
|
+
stdio: "inherit",
|
|
263
|
+
env: cleanEnv,
|
|
264
|
+
},
|
|
265
|
+
"oracle-chat",
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
};
|
|
@@ -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
|
+
};
|