@oracle-agent/oracle 0.9.8 → 0.11.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 +15 -10
- package/bin/oracle-data-mcp.mjs +9 -0
- package/docs/architecture.md +3 -1
- package/docs/profiles.md +3 -6
- package/docs/public-surface.md +133 -0
- package/package.json +3 -2
- package/public/oracle-splash/assets/cli-chain-hyperliquid.jpg +0 -0
- package/public/oracle-splash/brand/aave.webp +0 -0
- package/public/oracle-splash/brand/aavev2.webp +0 -0
- package/public/oracle-splash/brand/aborean.webp +0 -0
- package/public/oracle-splash/brand/across.webp +0 -0
- package/public/oracle-splash/brand/aerodrome.webp +0 -0
- package/public/oracle-splash/brand/afi.webp +0 -0
- package/public/oracle-splash/brand/arcus.webp +0 -0
- package/public/oracle-splash/brand/aster.webp +0 -0
- package/public/oracle-splash/brand/avant.webp +0 -0
- package/public/oracle-splash/brand/balancer.webp +0 -0
- package/public/oracle-splash/brand/beefy.webp +0 -0
- package/public/oracle-splash/brand/benqi.webp +0 -0
- package/public/oracle-splash/brand/bestinslot.ico +0 -0
- package/public/oracle-splash/brand/bitfi.webp +0 -0
- package/public/oracle-splash/brand/bonkswap.webp +0 -0
- package/public/oracle-splash/brand/bouncebit.webp +0 -0
- package/public/oracle-splash/brand/camelot.webp +0 -0
- package/public/oracle-splash/brand/compound.webp +0 -0
- package/public/oracle-splash/brand/concrete.webp +0 -0
- package/public/oracle-splash/brand/cow.webp +0 -0
- package/public/oracle-splash/brand/curve.webp +0 -0
- package/public/oracle-splash/brand/derive.webp +0 -0
- package/public/oracle-splash/brand/dolomite.webp +0 -0
- package/public/oracle-splash/brand/eigenlayer.webp +0 -0
- package/public/oracle-splash/brand/ekubo.webp +0 -0
- package/public/oracle-splash/brand/ethena.webp +0 -0
- package/public/oracle-splash/brand/etherfi.webp +0 -0
- package/public/oracle-splash/brand/etherfiborrow.webp +0 -0
- package/public/oracle-splash/brand/felix.webp +0 -0
- package/public/oracle-splash/brand/fluid.webp +0 -0
- package/public/oracle-splash/brand/fluxbeam.webp +0 -0
- package/public/oracle-splash/brand/folks.webp +0 -0
- package/public/oracle-splash/brand/fortyacres.webp +0 -0
- package/public/oracle-splash/brand/gainsnetwork.webp +0 -0
- package/public/oracle-splash/brand/gassss.webp +0 -0
- package/public/oracle-splash/brand/gmx.webp +0 -0
- package/public/oracle-splash/brand/harmonix.webp +0 -0
- package/public/oracle-splash/brand/hlorderbook.webp +0 -0
- package/public/oracle-splash/brand/hlp.webp +0 -0
- package/public/oracle-splash/brand/hop.webp +0 -0
- package/public/oracle-splash/brand/hyperbeat.webp +0 -0
- package/public/oracle-splash/brand/hyperlend.webp +0 -0
- package/public/oracle-splash/brand/hyperliquid.webp +0 -0
- package/public/oracle-splash/brand/hyperliquidhpl.webp +0 -0
- package/public/oracle-splash/brand/hyperliquidspot.webp +0 -0
- package/public/oracle-splash/brand/hyperswap.webp +0 -0
- package/public/oracle-splash/brand/hypha.webp +0 -0
- package/public/oracle-splash/brand/joedex.webp +0 -0
- package/public/oracle-splash/brand/jupiter.webp +0 -0
- package/public/oracle-splash/brand/kelp.webp +0 -0
- package/public/oracle-splash/brand/kinetiq.webp +0 -0
- package/public/oracle-splash/brand/kmhype.webp +0 -0
- package/public/oracle-splash/brand/kona.webp +0 -0
- package/public/oracle-splash/brand/konalend.webp +0 -0
- package/public/oracle-splash/brand/lfj.webp +0 -0
- package/public/oracle-splash/brand/lido.webp +0 -0
- package/public/oracle-splash/brand/lifi.webp +0 -0
- package/public/oracle-splash/brand/lighter.webp +0 -0
- package/public/oracle-splash/brand/lista.webp +0 -0
- package/public/oracle-splash/brand/loopedhype.webp +0 -0
- package/public/oracle-splash/brand/lorenzo.webp +0 -0
- package/public/oracle-splash/brand/magic-eden.webp +0 -0
- package/public/oracle-splash/brand/magiceden.webp +0 -0
- package/public/oracle-splash/brand/mempool.png +0 -0
- package/public/oracle-splash/brand/meteora.webp +0 -0
- package/public/oracle-splash/brand/morpho.webp +0 -0
- package/public/oracle-splash/brand/myriad.webp +0 -0
- package/public/oracle-splash/brand/noxa.webp +0 -0
- package/public/oracle-splash/brand/odos.webp +0 -0
- package/public/oracle-splash/brand/oneinch.webp +0 -0
- package/public/oracle-splash/brand/openbook.webp +0 -0
- package/public/oracle-splash/brand/opensea.webp +0 -0
- package/public/oracle-splash/brand/orca.webp +0 -0
- package/public/oracle-splash/brand/ordinals.png +0 -0
- package/public/oracle-splash/brand/ostium.webp +0 -0
- package/public/oracle-splash/brand/pancakeswap.webp +0 -0
- package/public/oracle-splash/brand/pendle.webp +0 -0
- package/public/oracle-splash/brand/perena.webp +0 -0
- package/public/oracle-splash/brand/pharaoh.webp +0 -0
- package/public/oracle-splash/brand/phoenix.webp +0 -0
- package/public/oracle-splash/brand/polymarket.webp +0 -0
- package/public/oracle-splash/brand/polynomial.webp +0 -0
- package/public/oracle-splash/brand/projectx.webp +0 -0
- package/public/oracle-splash/brand/pumpfun.webp +0 -0
- package/public/oracle-splash/brand/quickswap.webp +0 -0
- package/public/oracle-splash/brand/rain.webp +0 -0
- package/public/oracle-splash/brand/raydium.webp +0 -0
- package/public/oracle-splash/brand/relay.webp +0 -0
- package/public/oracle-splash/brand/rocketpool.webp +0 -0
- package/public/oracle-splash/brand/saber.webp +0 -0
- package/public/oracle-splash/brand/sakuraswap.webp +0 -0
- package/public/oracle-splash/brand/sanctum.webp +0 -0
- package/public/oracle-splash/brand/satflow.png +0 -0
- package/public/oracle-splash/brand/sierra.webp +0 -0
- package/public/oracle-splash/brand/sky.webp +0 -0
- package/public/oracle-splash/brand/solv.webp +0 -0
- package/public/oracle-splash/brand/spark.webp +0 -0
- package/public/oracle-splash/brand/sparksavings.webp +0 -0
- package/public/oracle-splash/brand/sphere.webp +0 -0
- package/public/oracle-splash/brand/ssv.webp +0 -0
- package/public/oracle-splash/brand/stabble.webp +0 -0
- package/public/oracle-splash/brand/stableswap.webp +0 -0
- package/public/oracle-splash/brand/stakewise.webp +0 -0
- package/public/oracle-splash/brand/stargate.webp +0 -0
- package/public/oracle-splash/brand/sthype.webp +0 -0
- package/public/oracle-splash/brand/sushiswap.webp +0 -0
- package/public/oracle-splash/brand/unisat.png +0 -0
- package/public/oracle-splash/brand/uniswap.webp +0 -0
- package/public/oracle-splash/brand/uniswapv2.webp +0 -0
- package/public/oracle-splash/brand/uniswapv4.webp +0 -0
- package/public/oracle-splash/brand/unit.webp +0 -0
- package/public/oracle-splash/brand/velodrome.webp +0 -0
- package/public/oracle-splash/brand/velora.webp +0 -0
- package/public/oracle-splash/brand/venus.webp +0 -0
- package/public/oracle-splash/brand/wbeth.webp +0 -0
- package/public/oracle-splash/brand/xsy.webp +0 -0
- package/public/oracle-splash/brand/yieldyak.webp +0 -0
- package/public/oracle-splash/brand/zerobase.webp +0 -0
- package/public/oracle-splash/brand/zerox.webp +0 -0
- package/public/oracle-splash/index.html +337 -197
- package/scripts/check-doc-drift.mjs +11 -0
- package/scripts/e2e-solana-bitcoin.mjs +1 -1
- package/scripts/secret-scan.mjs +1 -1
- package/src/action-receipts.mjs +108 -11
- package/src/auth/oauth.mjs +672 -0
- package/src/cli/commands/auth.mjs +193 -0
- package/src/cli/commands/bootstrap.mjs +2 -2
- package/src/cli/commands/chat.mjs +97 -75
- package/src/cli/commands/data.mjs +3 -1
- package/src/cli/commands/doctor.mjs +11 -1
- package/src/cli/commands/model.mjs +79 -23
- package/src/cli/kernel.mjs +4 -3
- package/src/cli/model-config.mjs +70 -0
- package/src/data/catalog.mjs +38 -7
- package/src/data/desk-data.mjs +12 -0
- package/src/data/providers/evm-rpc.mjs +19 -16
- package/src/data/providers/jupiter-venues.mjs +119 -0
- package/src/data/providers/jupiter.mjs +132 -0
- package/src/data/providers/magiceden-sol.mjs +13 -0
- package/src/data/providers/morpho.mjs +6 -2
- package/src/data/providers/pendle.mjs +3 -2
- package/src/data/providers/satflow.mjs +4 -1
- package/src/data/providers/signed-material-guard.mjs +108 -0
- package/src/data/providers/uniswap-v3.mjs +34 -5
- package/src/data/providers/uniswap-v4.mjs +414 -0
- package/src/prepare-envelope.mjs +22 -4
- package/src/router/prepare-route.mjs +17 -0
- package/src/router/route-sources.mjs +35 -0
- package/src/tui/app.mjs +18 -7
- package/src/tui/backend.mjs +145 -0
- package/src/tui/standalone-client.mjs +666 -0
- package/src/cards.mjs +0 -369
- package/src/cli/__pycache__/oracle-harness.cpython-311.pyc +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { writeModelConfig } from "../model-config.mjs";
|
|
2
|
+
import {
|
|
3
|
+
getOAuthStore,
|
|
4
|
+
loginOAuth,
|
|
5
|
+
oauthProviderConfig,
|
|
6
|
+
oauthProviderId,
|
|
7
|
+
} from "../../auth/oauth.mjs";
|
|
8
|
+
|
|
9
|
+
const API_PROVIDERS = Object.freeze({
|
|
10
|
+
openrouter: { model: "openrouter/auto" },
|
|
11
|
+
openai: { model: "gpt-4.1-mini" },
|
|
12
|
+
xai: { model: "grok-4.5" },
|
|
13
|
+
deepseek: { model: "deepseek-chat" },
|
|
14
|
+
gemini: { model: "gemini-2.5-flash" },
|
|
15
|
+
custom: { model: "" },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function option(args, name) {
|
|
19
|
+
const index = args.indexOf(name);
|
|
20
|
+
return index >= 0 && args[index + 1] ? args[index + 1] : "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function readAllStdin() {
|
|
24
|
+
let value = "";
|
|
25
|
+
for await (const chunk of process.stdin) value += chunk;
|
|
26
|
+
return value.trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readHidden(prompt = "API key: ") {
|
|
30
|
+
if (!process.stdin.isTTY || !process.stdin.setRawMode) {
|
|
31
|
+
throw new Error("non-interactive API-key input requires --stdin");
|
|
32
|
+
}
|
|
33
|
+
process.stdout.write(prompt);
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
let value = "";
|
|
36
|
+
const cleanup = () => {
|
|
37
|
+
process.stdin.off("data", onData);
|
|
38
|
+
process.stdin.setRawMode(false);
|
|
39
|
+
process.stdin.pause();
|
|
40
|
+
process.stdout.write("\n");
|
|
41
|
+
};
|
|
42
|
+
const onData = (chunk) => {
|
|
43
|
+
const text = String(chunk);
|
|
44
|
+
for (const character of text) {
|
|
45
|
+
if (character === "\r" || character === "\n") {
|
|
46
|
+
cleanup();
|
|
47
|
+
resolve(value.trim());
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (character === "\u0003") {
|
|
51
|
+
cleanup();
|
|
52
|
+
reject(new Error("cancelled"));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (character === "\u007f" || character === "\b") value = value.slice(0, -1);
|
|
56
|
+
else value += character;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
process.stdin.setRawMode(true);
|
|
60
|
+
process.stdin.resume();
|
|
61
|
+
process.stdin.on("data", onData);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function selection(provider, model, { baseUrl = "" } = {}) {
|
|
66
|
+
if (!model) throw new Error(`${provider} provider requires --model`);
|
|
67
|
+
const config = { backend: "standalone", provider, model };
|
|
68
|
+
if (provider === "custom") {
|
|
69
|
+
if (!baseUrl) throw new Error("custom provider requires --base-url");
|
|
70
|
+
config.baseUrl = baseUrl;
|
|
71
|
+
}
|
|
72
|
+
return config;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function saveSelection(provider, model, options = {}) {
|
|
76
|
+
return writeModelConfig(selection(provider, model, options));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function printStatus(status) {
|
|
80
|
+
process.stdout.write(`storage: ${status.storage}\n`);
|
|
81
|
+
const oauth = Object.entries(status.providers || {});
|
|
82
|
+
const apiKeys = Object.entries(status.apiKeys || {});
|
|
83
|
+
if (!oauth.length && !apiKeys.length) {
|
|
84
|
+
process.stdout.write("no credentials configured\n");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const [provider, value] of oauth) {
|
|
88
|
+
const expiry = value.expiresAt ? new Date(value.expiresAt).toISOString() : "unknown";
|
|
89
|
+
process.stdout.write(`${provider}: ${value.loggedIn ? "logged in" : "missing"}, expires ${expiry}\n`);
|
|
90
|
+
}
|
|
91
|
+
for (const [provider, value] of apiKeys) {
|
|
92
|
+
process.stdout.write(`${provider}: API key ${value.configured ? "configured" : "missing"}\n`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function usage() {
|
|
97
|
+
return [
|
|
98
|
+
"oracle auth, configure OAuth or API-key authentication",
|
|
99
|
+
"",
|
|
100
|
+
" oracle auth login claude|codex|grok [--model MODEL] [--no-browser]",
|
|
101
|
+
" oracle auth api-key PROVIDER [--model MODEL] [--base-url URL]",
|
|
102
|
+
" printf '%s\\n' \"$KEY\" | oracle auth api-key PROVIDER --stdin",
|
|
103
|
+
" oracle auth status [--json]",
|
|
104
|
+
" oracle auth logout PROVIDER",
|
|
105
|
+
"",
|
|
106
|
+
"OAuth and API keys are additive. Select any compatible model with oracle model --provider PROVIDER --model MODEL.",
|
|
107
|
+
].join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default {
|
|
111
|
+
name: "auth",
|
|
112
|
+
summary: "login with Claude, Codex, or Grok OAuth; store provider API keys",
|
|
113
|
+
group: "read",
|
|
114
|
+
usage: usage(),
|
|
115
|
+
async run(ctx) {
|
|
116
|
+
const args = [...ctx.argv];
|
|
117
|
+
const action = args.shift() || "status";
|
|
118
|
+
const store = getOAuthStore();
|
|
119
|
+
try {
|
|
120
|
+
if (action === "status") {
|
|
121
|
+
const status = store.status();
|
|
122
|
+
if (args.includes("--json")) process.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
123
|
+
else printStatus(status);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (action === "login") {
|
|
128
|
+
const requested = args.find((value) => !value.startsWith("-"));
|
|
129
|
+
if (!requested) throw new Error("usage: oracle auth login claude|codex|grok");
|
|
130
|
+
const config = oauthProviderConfig(requested);
|
|
131
|
+
const model = option(args, "--model") || config.model;
|
|
132
|
+
selection(config.id, model);
|
|
133
|
+
const credential = await loginOAuth(config.id, {
|
|
134
|
+
openFn: args.includes("--no-browser") ? () => false : undefined,
|
|
135
|
+
});
|
|
136
|
+
await store.withLock(async () => {
|
|
137
|
+
const previous = store.get(config.id);
|
|
138
|
+
store.set(config.id, credential);
|
|
139
|
+
try {
|
|
140
|
+
saveSelection(config.id, model);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (previous) store.set(config.id, previous);
|
|
143
|
+
else store.remove(config.id);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
process.stdout.write(`${config.id}: logged in, stored in ${store.storage}\n`);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (action === "api-key") {
|
|
152
|
+
const provider = String(args.find((value) => !value.startsWith("-")) || "").trim().toLowerCase();
|
|
153
|
+
if (!provider) throw new Error("usage: oracle auth api-key PROVIDER [--stdin]");
|
|
154
|
+
if (!Object.hasOwn(API_PROVIDERS, provider)) throw new Error(`unsupported API-key provider: ${provider}`);
|
|
155
|
+
const model = option(args, "--model") || API_PROVIDERS[provider].model;
|
|
156
|
+
const baseUrl = option(args, "--base-url");
|
|
157
|
+
selection(provider, model, { baseUrl });
|
|
158
|
+
const apiKey = args.includes("--stdin") ? await readAllStdin() : await readHidden();
|
|
159
|
+
await store.withLock(async () => {
|
|
160
|
+
const previous = store.getApiKey(provider);
|
|
161
|
+
store.setApiKey(provider, apiKey);
|
|
162
|
+
try {
|
|
163
|
+
saveSelection(provider, model, { baseUrl });
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (previous) store.setApiKey(provider, previous);
|
|
166
|
+
else store.removeApiKey(provider);
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
process.stdout.write(`${provider}: API key configured in ${store.storage}\n`);
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (action === "logout") {
|
|
175
|
+
const requested = String(args.find((value) => !value.startsWith("-")) || "").trim().toLowerCase();
|
|
176
|
+
if (!requested) throw new Error("usage: oracle auth logout PROVIDER");
|
|
177
|
+
let removedOAuth = false;
|
|
178
|
+
try {
|
|
179
|
+
removedOAuth = store.remove(oauthProviderId(requested));
|
|
180
|
+
} catch {}
|
|
181
|
+
const removedApiKey = store.removeApiKey(requested);
|
|
182
|
+
if (!removedOAuth && !removedApiKey) throw new Error(`${requested}: no local credential found`);
|
|
183
|
+
process.stdout.write(`${requested}: local credential removed\n`);
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw new Error(`unknown auth action: ${action}`);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
process.stderr.write(`oracle auth: ${error.message}\n`);
|
|
190
|
+
return 1;
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
};
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
function usage() {
|
|
9
9
|
return [
|
|
10
|
-
"oracle bootstrap - install
|
|
10
|
+
"oracle bootstrap - optionally install an isolated Hermes compatibility runtime",
|
|
11
11
|
"",
|
|
12
12
|
" oracle bootstrap install into ~/.config/oracle/runtime/venv",
|
|
13
13
|
" oracle bootstrap --upgrade upgrade the managed runtime",
|
|
@@ -22,7 +22,7 @@ function usage() {
|
|
|
22
22
|
|
|
23
23
|
export default {
|
|
24
24
|
name: "bootstrap",
|
|
25
|
-
summary: "install
|
|
25
|
+
summary: "optionally install a Hermes compatibility runtime",
|
|
26
26
|
group: "read",
|
|
27
27
|
usage: usage(),
|
|
28
28
|
async run(ctx) {
|
|
@@ -3,9 +3,13 @@ import path from "node:path";
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { PACKAGE_ROOT, hermesRoot, ensureDir } from "../paths.mjs";
|
|
5
5
|
import { activeChainEnv } from "../chain-state.mjs";
|
|
6
|
+
import { readModelConfig } from "../model-config.mjs";
|
|
6
7
|
import { spawnChild } from "../spawn-child.mjs";
|
|
7
|
-
import {
|
|
8
|
+
import { resolveHermes } from "../runtime.mjs";
|
|
8
9
|
import { runOracleTui } from "../../tui/app.mjs";
|
|
10
|
+
import { resolveChatBackend } from "../../tui/backend.mjs";
|
|
11
|
+
import { createStandaloneClient, runStandaloneQuery } from "../../tui/standalone-client.mjs";
|
|
12
|
+
import { getOAuthStore } from "../../auth/oauth.mjs";
|
|
9
13
|
|
|
10
14
|
const PROFILE = "oracle";
|
|
11
15
|
const SKIN_NAME = "oracle";
|
|
@@ -24,30 +28,6 @@ function which(bin) {
|
|
|
24
28
|
return null;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
/**
|
|
28
|
-
* First-run bootstrap. Only fires for a real interactive TTY so scripts,
|
|
29
|
-
* pipes, and CI never trigger a multi-minute install.
|
|
30
|
-
*/
|
|
31
|
-
function ensureRuntime() {
|
|
32
|
-
const found = resolveHermes();
|
|
33
|
-
if (found.ok) return found.bin;
|
|
34
|
-
if (process.env.ORACLE_NO_BOOTSTRAP === "1") return null;
|
|
35
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
36
|
-
|
|
37
|
-
process.stdout.write(
|
|
38
|
-
"oracle needs a local agent runtime for chat (one-time setup).\n" +
|
|
39
|
-
"installing into ~/.config/oracle/runtime/venv, a few minutes.\n\n",
|
|
40
|
-
);
|
|
41
|
-
const r = installManagedHermes();
|
|
42
|
-
if (!r.ok) {
|
|
43
|
-
process.stderr.write(`oracle: runtime setup failed: ${r.reason}\n`);
|
|
44
|
-
if (r.stderr) process.stderr.write(`${r.stderr}\n`);
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
process.stdout.write("\nruntime ready.\n\n");
|
|
48
|
-
return r.bin;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
31
|
function pythonShebang(file) {
|
|
52
32
|
try {
|
|
53
33
|
const first = fs.readFileSync(file, "utf8").split(/\r?\n/, 1)[0];
|
|
@@ -291,6 +271,38 @@ function printBanner() {
|
|
|
291
271
|
process.stdout.write(centerBlock(lines, terminalColumns()).join("\n") + "\n");
|
|
292
272
|
}
|
|
293
273
|
|
|
274
|
+
function parseLaunchArgs(args) {
|
|
275
|
+
const parsed = { pass: [], query: null, model: null, provider: null };
|
|
276
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
277
|
+
const arg = args[i];
|
|
278
|
+
if ((arg === "--model" || arg === "-m") && args[i + 1]) {
|
|
279
|
+
parsed.model = args[++i];
|
|
280
|
+
parsed.pass.push("--model", parsed.model);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (arg === "--provider" && args[i + 1]) {
|
|
284
|
+
parsed.provider = args[++i];
|
|
285
|
+
parsed.pass.push("--provider", parsed.provider);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if ((arg === "-q" || arg === "--query") && args[i + 1]) {
|
|
289
|
+
parsed.query = args[++i];
|
|
290
|
+
parsed.pass.push("-q", parsed.query);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (arg === "--quiet" || arg === "-Q") {
|
|
294
|
+
parsed.pass.push("--quiet");
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (arg === "--continue" || arg === "-c") {
|
|
298
|
+
parsed.pass.push("--continue");
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
parsed.pass.push(arg);
|
|
302
|
+
}
|
|
303
|
+
return parsed;
|
|
304
|
+
}
|
|
305
|
+
|
|
294
306
|
export default {
|
|
295
307
|
name: "chat",
|
|
296
308
|
summary: "open the one oracle chat surface (telegram-like, multi-model)",
|
|
@@ -307,15 +319,69 @@ export default {
|
|
|
307
319
|
return 0;
|
|
308
320
|
}
|
|
309
321
|
|
|
310
|
-
const
|
|
311
|
-
|
|
322
|
+
const parsed = parseLaunchArgs(args);
|
|
323
|
+
const selectionEnv = {
|
|
324
|
+
...process.env,
|
|
325
|
+
...(parsed.model ? { ORACLE_MODEL: parsed.model } : {}),
|
|
326
|
+
...(parsed.provider ? { ORACLE_PROVIDER: parsed.provider } : {}),
|
|
327
|
+
};
|
|
328
|
+
const authStore = getOAuthStore();
|
|
329
|
+
const backend = resolveChatBackend({
|
|
330
|
+
env: selectionEnv,
|
|
331
|
+
hermes: resolveHermes(),
|
|
332
|
+
storedConfig: readModelConfig(),
|
|
333
|
+
oauthAvailable: (provider) => Boolean(authStore.get(provider)),
|
|
334
|
+
apiKeyResolver: (provider) => authStore.getApiKey(provider),
|
|
335
|
+
});
|
|
336
|
+
if (backend.kind === "unconfigured") {
|
|
337
|
+
process.stderr.write(`${backend.reason}\n`);
|
|
338
|
+
return 1;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const cleanEnv = activeChainEnv({
|
|
342
|
+
ORACLE_CHAT_SURFACE: "1",
|
|
343
|
+
ORACLE_PROFILE: PROFILE,
|
|
344
|
+
ORACLE_NODE_BIN: process.execPath,
|
|
345
|
+
ORACLE_CLI_ENTRY: path.join(PACKAGE_ROOT, "bin", "oracle.mjs"),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (backend.kind === "standalone") {
|
|
349
|
+
const client = createStandaloneClient({
|
|
350
|
+
config: {
|
|
351
|
+
...backend.config,
|
|
352
|
+
...(parsed.model ? { model: parsed.model } : {}),
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
if (parsed.query) {
|
|
356
|
+
try {
|
|
357
|
+
return await runStandaloneQuery({
|
|
358
|
+
client,
|
|
359
|
+
text: parsed.query,
|
|
360
|
+
stdout: process.stdout,
|
|
361
|
+
stderr: process.stderr,
|
|
362
|
+
});
|
|
363
|
+
} catch {
|
|
364
|
+
return 1;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const native = await runOracleTui({
|
|
368
|
+
client,
|
|
369
|
+
args,
|
|
370
|
+
env: cleanEnv,
|
|
371
|
+
cwd: process.cwd(),
|
|
372
|
+
stdin: process.stdin,
|
|
373
|
+
stdout: process.stdout,
|
|
374
|
+
});
|
|
375
|
+
if (native.native) return native.code;
|
|
312
376
|
process.stderr.write(
|
|
313
|
-
|
|
314
|
-
"
|
|
377
|
+
process.env.ORACLE_NATIVE_TUI === "0"
|
|
378
|
+
? "oracle standalone chat requires the native TUI; remove ORACLE_NATIVE_TUI=0\n"
|
|
379
|
+
: "oracle standalone chat requires an interactive TTY or -q <query>\n",
|
|
315
380
|
);
|
|
316
381
|
return 1;
|
|
317
382
|
}
|
|
318
383
|
|
|
384
|
+
const hermes = backend.bin;
|
|
319
385
|
ensureOracleProfile(PROFILE);
|
|
320
386
|
ensureOracleSkin(PROFILE);
|
|
321
387
|
ensureSkillCommands(PROFILE);
|
|
@@ -325,7 +391,7 @@ export default {
|
|
|
325
391
|
process.stderr.write(`oracle chat: skin note: ${skinSet.reason}\n`);
|
|
326
392
|
}
|
|
327
393
|
|
|
328
|
-
const explicitModel =
|
|
394
|
+
const explicitModel = Boolean(parsed.model || parsed.provider);
|
|
329
395
|
if (!modelReady(PROFILE) && !explicitModel) {
|
|
330
396
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
331
397
|
process.stderr.write("oracle chat needs a configured model\nrun: oracle model\n");
|
|
@@ -342,50 +408,7 @@ export default {
|
|
|
342
408
|
if (selected !== 0 || !modelReady(PROFILE)) return selected || 1;
|
|
343
409
|
}
|
|
344
410
|
|
|
345
|
-
// Parse a few passthrough flags.
|
|
346
411
|
const hermesArgs = ["-p", PROFILE, "chat"];
|
|
347
|
-
const pass = [];
|
|
348
|
-
for (let i = 0; i < args.length; i++) {
|
|
349
|
-
const a = args[i];
|
|
350
|
-
if (a === "--model" || a === "-m") {
|
|
351
|
-
if (args[i + 1]) {
|
|
352
|
-
pass.push("--model", args[++i]);
|
|
353
|
-
}
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
if (a === "--provider") {
|
|
357
|
-
if (args[i + 1]) {
|
|
358
|
-
pass.push("--provider", args[++i]);
|
|
359
|
-
}
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
if (a === "-q" || a === "--query") {
|
|
363
|
-
if (args[i + 1]) {
|
|
364
|
-
pass.push("-q", args[++i]);
|
|
365
|
-
}
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
if (a === "--quiet" || a === "-Q") {
|
|
369
|
-
pass.push("--quiet");
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
if (a === "--continue" || a === "-c") {
|
|
373
|
-
pass.push("--continue");
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
// unknown flags go through
|
|
377
|
-
pass.push(a);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// hermes -p scopes the same profile used by Telegram. Do not replace
|
|
381
|
-
// HERMES_HOME with the profile dir; nested profile resolution would break.
|
|
382
|
-
const cleanEnv = activeChainEnv({
|
|
383
|
-
ORACLE_CHAT_SURFACE: "1",
|
|
384
|
-
ORACLE_PROFILE: PROFILE,
|
|
385
|
-
ORACLE_NODE_BIN: process.execPath,
|
|
386
|
-
ORACLE_CLI_ENTRY: path.join(PACKAGE_ROOT, "bin", "oracle.mjs"),
|
|
387
|
-
});
|
|
388
|
-
|
|
389
412
|
const hermesPython = resolveHermesPython(hermes);
|
|
390
413
|
const native = await runOracleTui({
|
|
391
414
|
hermesPython,
|
|
@@ -397,8 +420,7 @@ export default {
|
|
|
397
420
|
});
|
|
398
421
|
if (native.native) return native.code;
|
|
399
422
|
|
|
400
|
-
const launch = oracleHarnessInvocation(hermes, [...hermesArgs, ...pass]);
|
|
401
|
-
|
|
423
|
+
const launch = oracleHarnessInvocation(hermes, [...hermesArgs, ...parsed.pass]);
|
|
402
424
|
return spawnChild(
|
|
403
425
|
launch.command,
|
|
404
426
|
launch.args,
|
|
@@ -53,7 +53,9 @@ export default {
|
|
|
53
53
|
const bin = path.join(ctx.root, "bin", "desk-server.mjs");
|
|
54
54
|
return spawnChild(process.execPath, [bin], { stdio: "inherit", env }, "desk-server");
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
// The server serves /health; / intentionally 404s with a route list. This
|
|
57
|
+
// probed / and so reported the package's own running server as down.
|
|
58
|
+
if (verb === "health") return dataFetch("/health");
|
|
57
59
|
if (verb === "catalog") return dataFetch("/data/catalog");
|
|
58
60
|
if (verb === "call") {
|
|
59
61
|
const provider = ctx.argv[1];
|
|
@@ -21,7 +21,9 @@ function loadExecEnv(p) {
|
|
|
21
21
|
|
|
22
22
|
async function probeDataServer(url) {
|
|
23
23
|
try {
|
|
24
|
-
|
|
24
|
+
// The server serves /health; / intentionally 404s with a route list, so
|
|
25
|
+
// probing / reported a healthy server as down.
|
|
26
|
+
const res = await fetch(url.replace(/\/$/, "") + "/health");
|
|
25
27
|
return res.ok;
|
|
26
28
|
} catch {
|
|
27
29
|
return false;
|
|
@@ -47,9 +49,17 @@ export default {
|
|
|
47
49
|
|
|
48
50
|
const execEnvPath = ctx.paths.oracleExecEnvPath();
|
|
49
51
|
const envFile = loadExecEnv(execEnvPath);
|
|
52
|
+
// `oracle init --apply` writes ORACLE_ROUTE_ATTESTATION_SECRET (see
|
|
53
|
+
// bin/oracle-init.mjs), but doctor checked only ORACLE_ATTESTATION_SECRET
|
|
54
|
+
// and ORACLE_STAMP_HMAC_SECRET — so it told the user to rerun an init step
|
|
55
|
+
// that could never satisfy the check.
|
|
50
56
|
const att =
|
|
57
|
+
process.env.ORACLE_ROUTE_ATTESTATION_SECRET ||
|
|
58
|
+
process.env.MAD_ROUTE_ATTESTATION_SECRET ||
|
|
51
59
|
process.env.ORACLE_ATTESTATION_SECRET ||
|
|
52
60
|
process.env.ORACLE_STAMP_HMAC_SECRET ||
|
|
61
|
+
envFile.ORACLE_ROUTE_ATTESTATION_SECRET ||
|
|
62
|
+
envFile.MAD_ROUTE_ATTESTATION_SECRET ||
|
|
53
63
|
envFile.ORACLE_ATTESTATION_SECRET ||
|
|
54
64
|
envFile.ORACLE_STAMP_HMAC_SECRET;
|
|
55
65
|
push("attestation_secret", att ? "ok" : "warn", att ? "present" : "missing",
|
|
@@ -1,38 +1,94 @@
|
|
|
1
1
|
import { spawnChild } from "../spawn-child.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveHermes } from "../runtime.mjs";
|
|
3
|
+
import { readModelConfig, writeModelConfig } from "../model-config.mjs";
|
|
3
4
|
import { oracleHarnessInvocation } from "./chat.mjs";
|
|
4
5
|
|
|
5
|
-
function
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
function option(args, name) {
|
|
7
|
+
const index = args.indexOf(name);
|
|
8
|
+
return index >= 0 && args[index + 1] ? args[index + 1] : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function standalonePatch(args) {
|
|
12
|
+
const backend = option(args, "--backend");
|
|
13
|
+
const provider = option(args, "--provider");
|
|
14
|
+
const model = option(args, "--model") || option(args, "-m");
|
|
15
|
+
const baseUrl = option(args, "--base-url");
|
|
16
|
+
const apiKeyEnv = option(args, "--api-key-env");
|
|
17
|
+
const contextLength = option(args, "--context-length");
|
|
18
|
+
const reasoningEffort = option(args, "--reasoning-effort");
|
|
19
|
+
const hasStandaloneField = Boolean(provider || model || baseUrl || apiKeyEnv || contextLength || reasoningEffort);
|
|
20
|
+
if (!backend && !hasStandaloneField) return null;
|
|
21
|
+
const current = { ...(readModelConfig() || {}) };
|
|
22
|
+
if (provider) {
|
|
23
|
+
delete current.apiKeyEnv;
|
|
24
|
+
if (provider !== "custom" || provider !== current.provider) delete current.baseUrl;
|
|
15
25
|
}
|
|
16
|
-
return
|
|
26
|
+
return {
|
|
27
|
+
...current,
|
|
28
|
+
backend: backend || "standalone",
|
|
29
|
+
...(provider ? { provider } : {}),
|
|
30
|
+
...(model ? { model } : {}),
|
|
31
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
32
|
+
...(apiKeyEnv ? { apiKeyEnv } : {}),
|
|
33
|
+
...(contextLength ? { contextLength } : {}),
|
|
34
|
+
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function usage() {
|
|
39
|
+
return [
|
|
40
|
+
"oracle model, choose standalone model settings or open Hermes' picker",
|
|
41
|
+
"",
|
|
42
|
+
" oracle model",
|
|
43
|
+
" oracle model --show",
|
|
44
|
+
" oracle model --backend standalone --provider openrouter --model openrouter/auto",
|
|
45
|
+
" oracle model --backend auto",
|
|
46
|
+
"",
|
|
47
|
+
"standalone keys stay in environment variables, never model.json:",
|
|
48
|
+
" OPENROUTER_API_KEY, OPENAI_API_KEY, XAI_API_KEY, DEEPSEEK_API_KEY, GEMINI_API_KEY",
|
|
49
|
+
].join("\n");
|
|
17
50
|
}
|
|
18
51
|
|
|
19
52
|
export default {
|
|
20
53
|
name: "model",
|
|
21
|
-
summary: "choose
|
|
54
|
+
summary: "choose Oracle's standalone model or existing Hermes provider",
|
|
22
55
|
group: "read",
|
|
23
|
-
usage:
|
|
24
|
-
"oracle model - interactive provider/model picker for the oracle profile",
|
|
25
|
-
"",
|
|
26
|
-
"inside chat: /model",
|
|
27
|
-
"one oracle persona stays loaded when the model changes.",
|
|
28
|
-
].join("\n"),
|
|
56
|
+
usage: usage(),
|
|
29
57
|
async run(ctx) {
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
process.
|
|
58
|
+
const args = [...ctx.argv];
|
|
59
|
+
if (args.includes("--show")) {
|
|
60
|
+
process.stdout.write(`${JSON.stringify(readModelConfig() || { backend: "auto" }, null, 2)}\n`);
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const patch = standalonePatch(args);
|
|
65
|
+
if (patch) {
|
|
66
|
+
try {
|
|
67
|
+
const saved = writeModelConfig(patch);
|
|
68
|
+
process.stdout.write(`oracle model: ${saved.backend}`);
|
|
69
|
+
if (saved.provider) process.stdout.write(` / ${saved.provider}`);
|
|
70
|
+
if (saved.model) process.stdout.write(` / ${saved.model}`);
|
|
71
|
+
process.stdout.write("\n");
|
|
72
|
+
if (saved.backend === "standalone" && saved.apiKeyEnv) {
|
|
73
|
+
process.stdout.write(`key source: ${saved.apiKeyEnv}\n`);
|
|
74
|
+
}
|
|
75
|
+
return 0;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
process.stderr.write(`oracle model: ${error.message}\n`);
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const found = resolveHermes();
|
|
83
|
+
if (!found.ok) {
|
|
84
|
+
process.stderr.write(
|
|
85
|
+
"Oracle standalone chat does not require Hermes.\n" +
|
|
86
|
+
"Configure a provider with: oracle model --backend standalone --provider openrouter --model openrouter/auto\n" +
|
|
87
|
+
"Then set OPENROUTER_API_KEY.\n",
|
|
88
|
+
);
|
|
33
89
|
return 1;
|
|
34
90
|
}
|
|
35
|
-
const launch = oracleHarnessInvocation(
|
|
91
|
+
const launch = oracleHarnessInvocation(found.bin, ["-p", "oracle", "model", ...args]);
|
|
36
92
|
return spawnChild(
|
|
37
93
|
launch.command,
|
|
38
94
|
launch.args,
|
package/src/cli/kernel.mjs
CHANGED
|
@@ -12,12 +12,13 @@ import {
|
|
|
12
12
|
const HELP_HEADER = `oracle — self-custody multichain agent control plane`;
|
|
13
13
|
|
|
14
14
|
const STATIC_HELP = `READ / RESEARCH (no keys, this package)
|
|
15
|
-
oracle open the
|
|
16
|
-
oracle bootstrap install
|
|
15
|
+
oracle open the native Oracle chat (TTY)
|
|
16
|
+
oracle bootstrap optional: install an isolated Hermes compatibility runtime
|
|
17
17
|
oracle init install agent lanes + read plane (dry-run by default)
|
|
18
18
|
oracle doctor check read plane; checks signer too when installed
|
|
19
19
|
oracle chat premium boxed oracle chat (multi-model)
|
|
20
|
-
oracle model choose
|
|
20
|
+
oracle model choose standalone/Hermes provider and any compatible model
|
|
21
|
+
oracle auth Claude/Codex/Grok OAuth or securely stored provider API keys
|
|
21
22
|
oracle chain list/select working chains (hyperliquid, base, ...)
|
|
22
23
|
oracle setup telegram/discord/slack messaging menu
|
|
23
24
|
oracle data serve|call|catalog|health
|