@oracle-agent/oracle 0.8.0 → 0.9.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.
@@ -0,0 +1,87 @@
1
+ import {
2
+ HERMES_PYPI,
3
+ installManagedHermes,
4
+ runtimeStatus,
5
+ runtimeVenvDir,
6
+ } from "../runtime.mjs";
7
+
8
+ function usage() {
9
+ return [
10
+ "oracle bootstrap - install the local agent runtime oracle chat needs",
11
+ "",
12
+ " oracle bootstrap install into ~/.config/oracle/runtime/venv",
13
+ " oracle bootstrap --upgrade upgrade the managed runtime",
14
+ " oracle bootstrap --status show what runtime oracle would use",
15
+ " oracle bootstrap --json machine-readable status",
16
+ "",
17
+ "notes:",
18
+ " a hermes on PATH always wins; bootstrap never touches system python",
19
+ " read/prepare commands (chain, scan, route, data) never need this",
20
+ ].join("\n");
21
+ }
22
+
23
+ export default {
24
+ name: "bootstrap",
25
+ summary: "install the local agent runtime for oracle chat",
26
+ group: "read",
27
+ usage: usage(),
28
+ async run(ctx) {
29
+ const args = ctx.argv;
30
+ if (args.includes("-h") || args.includes("--help")) {
31
+ process.stdout.write(usage() + "\n");
32
+ return 0;
33
+ }
34
+
35
+ const json = args.includes("--json");
36
+ const status = runtimeStatus();
37
+
38
+ if (args.includes("--status") || json) {
39
+ if (json) {
40
+ process.stdout.write(JSON.stringify(status, null, 2) + "\n");
41
+ return 0;
42
+ }
43
+ process.stdout.write(
44
+ [
45
+ `hermes: ${status.hermes.ok ? `${status.hermes.bin} (${status.hermes.source})` : "not installed"}`,
46
+ `managed venv: ${status.managedVenv}`,
47
+ `managed built: ${status.managedInstalled ? "yes" : "no"}`,
48
+ `host python: ${status.hostPython ? `${status.hostPython.bin} (${status.hostPython.version})` : "none supported (need 3.11-3.13)"}`,
49
+ `uv: ${status.uv || "not installed (oracle will install it locally)"}`,
50
+ "",
51
+ ].join("\n"),
52
+ );
53
+ return 0;
54
+ }
55
+
56
+ const upgrade = args.includes("--upgrade");
57
+
58
+ if (status.hermes.ok && status.hermes.source === "path" && !upgrade) {
59
+ process.stdout.write(
60
+ `hermes already on PATH: ${status.hermes.bin}\nnothing to do. run: oracle\n`,
61
+ );
62
+ return 0;
63
+ }
64
+
65
+ process.stdout.write(
66
+ `installing ${HERMES_PYPI} into ${runtimeVenvDir()}\n` +
67
+ (status.hostPython
68
+ ? `using python ${status.hostPython.version} (${status.hostPython.bin})\n`
69
+ : "provisioning an isolated python 3.13 with uv\n") +
70
+ "this takes a few minutes on first run\n\n",
71
+ );
72
+
73
+ const r = installManagedHermes({ upgrade });
74
+ if (!r.ok) {
75
+ process.stderr.write(`oracle bootstrap failed: ${r.reason}\n`);
76
+ if (r.stderr) process.stderr.write(`${r.stderr}\n`);
77
+ return 1;
78
+ }
79
+
80
+ process.stdout.write(
81
+ r.reused
82
+ ? `runtime already present: ${r.bin}\n`
83
+ : `\nruntime ready: ${r.bin}\nrun: oracle\n`,
84
+ );
85
+ return 0;
86
+ },
87
+ };
@@ -4,13 +4,15 @@ 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
6
  import { spawnChild } from "../spawn-child.mjs";
7
+ import { installManagedHermes, resolveHermes } from "../runtime.mjs";
7
8
 
8
9
  const PROFILE = "oracle";
9
10
  const SKIN_NAME = "oracle";
10
11
 
11
12
  function which(bin) {
12
- if (bin === "hermes" && process.env.ORACLE_HERMES_BIN) {
13
- return process.env.ORACLE_HERMES_BIN;
13
+ if (bin === "hermes") {
14
+ const r = resolveHermes();
15
+ return r.ok ? r.bin : null;
14
16
  }
15
17
  for (const dir of (process.env.PATH || "").split(path.delimiter)) {
16
18
  const c = path.join(dir, bin);
@@ -21,6 +23,30 @@ function which(bin) {
21
23
  return null;
22
24
  }
23
25
 
26
+ /**
27
+ * First-run bootstrap. Only fires for a real interactive TTY so scripts,
28
+ * pipes, and CI never trigger a multi-minute install.
29
+ */
30
+ function ensureRuntime() {
31
+ const found = resolveHermes();
32
+ if (found.ok) return found.bin;
33
+ if (process.env.ORACLE_NO_BOOTSTRAP === "1") return null;
34
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
35
+
36
+ process.stdout.write(
37
+ "oracle needs a local agent runtime for chat (one-time setup).\n" +
38
+ "installing into ~/.config/oracle/runtime/venv, a few minutes.\n\n",
39
+ );
40
+ const r = installManagedHermes();
41
+ if (!r.ok) {
42
+ process.stderr.write(`oracle: runtime setup failed: ${r.reason}\n`);
43
+ if (r.stderr) process.stderr.write(`${r.stderr}\n`);
44
+ return null;
45
+ }
46
+ process.stdout.write("\nruntime ready.\n\n");
47
+ return r.bin;
48
+ }
49
+
24
50
  function pythonShebang(file) {
25
51
  try {
26
52
  const first = fs.readFileSync(file, "utf8").split(/\r?\n/, 1)[0];
@@ -47,14 +73,43 @@ export function resolveHermesPython(hermes) {
47
73
  return null;
48
74
  }
49
75
 
50
- function harnessSourcePath() {
76
+ export function harnessSourcePath() {
51
77
  return path.join(PACKAGE_ROOT, "src", "cli", "oracle-harness.py");
52
78
  }
53
79
 
80
+ export function oracleHarnessInvocation(hermes, args) {
81
+ const harness = harnessSourcePath();
82
+ const runtime = process.env.ORACLE_PLAIN_HARNESS === "0"
83
+ ? null
84
+ : resolveHermesPython(hermes);
85
+ if (runtime && fs.existsSync(harness)) {
86
+ return {
87
+ command: runtime.command,
88
+ args: [...runtime.prefix, harness, ...args],
89
+ };
90
+ }
91
+ return { command: hermes, args };
92
+ }
93
+
54
94
  function skinSourcePath() {
55
95
  return path.join(PACKAGE_ROOT, "skins", "oracle.yaml");
56
96
  }
57
97
 
98
+ function ensureOracleProfile(profile = PROFILE) {
99
+ const src = path.join(PACKAGE_ROOT, "profiles", "oracle", "SOUL.md");
100
+ if (!fs.existsSync(src)) return { ok: false, reason: "oracle persona missing from package" };
101
+ const dest = path.join(hermesRoot(), "profiles", profile, "SOUL.md");
102
+ if (fs.existsSync(dest)) {
103
+ const current = fs.readFileSync(dest, "utf8");
104
+ if (!current.startsWith("You are Hermes Agent, an intelligent AI assistant created by Nous Research.")) {
105
+ return { ok: true, reused: true };
106
+ }
107
+ }
108
+ ensureDir(path.dirname(dest));
109
+ fs.copyFileSync(src, dest);
110
+ return { ok: true, reused: false };
111
+ }
112
+
58
113
  function ensureOracleSkin(profile = PROFILE) {
59
114
  const src = skinSourcePath();
60
115
  if (!fs.existsSync(src)) return { ok: false, reason: "skin missing from package" };
@@ -141,31 +196,78 @@ function setDisplaySkin(profile = PROFILE) {
141
196
  return { ok: true };
142
197
  }
143
198
 
199
+ function hasModelConfig(file) {
200
+ if (!fs.existsSync(file)) return false;
201
+ const text = fs.readFileSync(file, "utf8");
202
+ if (/^model:\s*\S+/m.test(text)) return true;
203
+ const block = text.match(/^model:\s*\n((?:[ \t]+.*(?:\n|$))*)/m)?.[1] || "";
204
+ return /^\s+(?:default|provider):\s*\S+/m.test(block);
205
+ }
206
+
207
+ function hasProviderCredentials(profile = PROFILE) {
208
+ const root = hermesRoot();
209
+ if (fs.existsSync(path.join(root, "auth.json"))) return true;
210
+ if (fs.existsSync(path.join(root, "profiles", profile, "auth.json"))) return true;
211
+ const keys = [
212
+ "OPENROUTER_API_KEY",
213
+ "ANTHROPIC_API_KEY",
214
+ "OPENAI_API_KEY",
215
+ "XAI_API_KEY",
216
+ "GOOGLE_API_KEY",
217
+ "GEMINI_API_KEY",
218
+ "DEEPSEEK_API_KEY",
219
+ "HF_TOKEN",
220
+ "GLM_API_KEY",
221
+ "KIMI_API_KEY",
222
+ "MINIMAX_API_KEY",
223
+ ];
224
+ if (keys.some((key) => process.env[key])) return true;
225
+ for (const file of [path.join(root, ".env"), path.join(root, "profiles", profile, ".env")]) {
226
+ if (!fs.existsSync(file)) continue;
227
+ const text = fs.readFileSync(file, "utf8");
228
+ if (keys.some((key) => new RegExp(`^${key}=.+`, "m").test(text))) return true;
229
+ }
230
+ return false;
231
+ }
232
+
233
+ function modelReady(profile = PROFILE) {
234
+ const root = hermesRoot();
235
+ return (
236
+ hasModelConfig(path.join(root, "config.yaml")) ||
237
+ hasModelConfig(path.join(root, "profiles", profile, "config.yaml")) ||
238
+ hasProviderCredentials(profile)
239
+ );
240
+ }
241
+
144
242
  function usage() {
145
243
  return [
146
- "oracle chat — one lowercase oracle surface",
244
+ "oracle chat — filled lowercase oracle chat",
147
245
  "",
148
246
  " oracle chat open interactive oracle chat",
149
247
  " 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",
248
+ " oracle chat --model <m> use a model for this session",
249
+ " oracle chat --print-banner show filled oracle banner only",
152
250
  "",
153
251
  "inside chat:",
154
252
  " /chain list/select chains",
155
253
  " /setup messaging setup menu",
156
254
  " /model switch models (still one oracle persona)",
157
255
  "",
158
- "brand: plain lowercase ascii oracle",
256
+ "brand: filled lowercase oracle / classic boxed layout",
159
257
  ].join("\n");
160
258
  }
161
259
 
162
260
  function printBanner() {
163
261
  const lines = [
164
262
  "",
165
- " _",
166
- " ___ _ _ __ _ __| |___",
167
- "/ _ \\ '_/ _` / _| / -_)",
168
- "\\___/_| \\__,_\\__|_\\___|",
263
+ " ████",
264
+ " ░░███",
265
+ " ██████ ████████ ██████ ██████ ░███ ██████",
266
+ " ███░░███░░███░░███ ░░░░░███ ███░░███ ░███ ███░░███",
267
+ "░███ ░███ ░███ ░░░ ███████ ░███ ░░░ ░███ ░███████",
268
+ "░███ ░███ ░███ ███░░███ ░███ ███ ░███ ░███░░░",
269
+ "░░██████ █████ ░░████████░░██████ █████░░██████",
270
+ " ░░░░░░ ░░░░░ ░░░░░░░░ ░░░░░░ ░░░░░ ░░░░░░",
169
271
  "",
170
272
  ];
171
273
  process.stdout.write(lines.join("\n") + "\n");
@@ -187,23 +289,41 @@ export default {
187
289
  return 0;
188
290
  }
189
291
 
190
- const hermes = which("hermes");
292
+ const hermes = which("hermes") || ensureRuntime();
191
293
  if (!hermes) {
192
294
  process.stderr.write(
193
- "oracle chat needs hermes on PATH\n" +
194
- "install: https://hermes-agent.nousresearch.com/docs\n",
295
+ "oracle chat needs a local agent runtime\n" +
296
+ "run: oracle bootstrap\n",
195
297
  );
196
298
  return 1;
197
299
  }
198
300
 
301
+ ensureOracleProfile(PROFILE);
199
302
  ensureOracleSkin(PROFILE);
200
- ensureQuickCommands(PROFILE);
201
303
  ensureSkillCommands(PROFILE);
202
304
  const skinSet = setDisplaySkin(PROFILE);
305
+ ensureQuickCommands(PROFILE);
203
306
  if (!skinSet.ok && process.env.ORACLE_CLI_DEBUG) {
204
307
  process.stderr.write(`oracle chat: skin note: ${skinSet.reason}\n`);
205
308
  }
206
309
 
310
+ const explicitModel = args.includes("--model") || args.includes("-m") || args.includes("--provider");
311
+ if (!modelReady(PROFILE) && !explicitModel) {
312
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
313
+ process.stderr.write("oracle chat needs a configured model\nrun: oracle model\n");
314
+ return 1;
315
+ }
316
+ process.stdout.write("choose oracle's model before first chat\n\n");
317
+ const modelLaunch = oracleHarnessInvocation(hermes, ["-p", PROFILE, "model"]);
318
+ const selected = await spawnChild(
319
+ modelLaunch.command,
320
+ modelLaunch.args,
321
+ { stdio: "inherit", env: process.env },
322
+ "oracle-model",
323
+ );
324
+ if (selected !== 0 || !modelReady(PROFILE)) return selected || 1;
325
+ }
326
+
207
327
  // Parse a few passthrough flags.
208
328
  const hermesArgs = ["-p", PROFILE, "chat"];
209
329
  const pass = [];
@@ -244,20 +364,15 @@ export default {
244
364
  const cleanEnv = activeChainEnv({
245
365
  ORACLE_CHAT_SURFACE: "1",
246
366
  ORACLE_PROFILE: PROFILE,
367
+ ORACLE_NODE_BIN: process.execPath,
368
+ ORACLE_CLI_ENTRY: path.join(PACKAGE_ROOT, "bin", "oracle.mjs"),
247
369
  });
248
370
 
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];
371
+ const launch = oracleHarnessInvocation(hermes, [...hermesArgs, ...pass]);
257
372
 
258
373
  return spawnChild(
259
- command,
260
- launchArgs,
374
+ launch.command,
375
+ launch.args,
261
376
  {
262
377
  stdio: "inherit",
263
378
  env: cleanEnv,
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { DOCTOR_SIGNING_OPTIONAL } from "../first-run.mjs";
4
+ import { resolveHermes } from "../runtime.mjs";
4
5
 
5
6
  function nodeVersionOk() {
6
7
  const [maj, min] = process.versions.node.split(".").map(Number);
@@ -17,13 +18,6 @@ function loadExecEnv(p) {
17
18
  return out;
18
19
  }
19
20
 
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
21
 
28
22
  async function probeDataServer(url) {
29
23
  try {
@@ -66,9 +60,10 @@ export default {
66
60
  push("data_server", dataUp ? "ok" : "warn", dataUp ? `reachable ${dataUrl}` : `down ${dataUrl}`,
67
61
  dataUp ? null : "oracle data serve");
68
62
 
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");
63
+ const runtime = resolveHermes();
64
+ push("agent_runtime", runtime.ok ? "ok" : "warn",
65
+ runtime.ok ? `present ${runtime.bin} (${runtime.source})` : "not installed",
66
+ runtime.ok ? null : "oracle bootstrap");
72
67
 
73
68
  const profilesDir = path.join(ctx.paths.hermesRoot(), "profiles");
74
69
  let laneCount = 0;
@@ -1,14 +1,19 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import { spawnChild } from "../spawn-child.mjs";
2
+ import { installManagedHermes, resolveHermes } from "../runtime.mjs";
3
+ import { oracleHarnessInvocation } from "./chat.mjs";
4
4
 
5
- function hermesBin() {
6
- if (process.env.ORACLE_HERMES_BIN) return process.env.ORACLE_HERMES_BIN;
7
- for (const dir of (process.env.PATH || "").split(path.delimiter)) {
8
- const candidate = path.join(dir, "hermes");
9
- if (fs.existsSync(candidate)) return candidate;
5
+ function runtimeBin() {
6
+ const found = resolveHermes();
7
+ if (found.ok) return found.bin;
8
+ if (process.env.ORACLE_NO_BOOTSTRAP === "1") return null;
9
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
10
+ process.stdout.write("setting up oracle's local model runtime (one time)\n\n");
11
+ const installed = installManagedHermes();
12
+ if (!installed.ok) {
13
+ process.stderr.write(`oracle model: runtime setup failed: ${installed.reason}\n`);
14
+ return null;
10
15
  }
11
- return null;
16
+ return installed.bin;
12
17
  }
13
18
 
14
19
  export default {
@@ -16,20 +21,21 @@ export default {
16
21
  summary: "choose the oracle profile's model/provider",
17
22
  group: "read",
18
23
  usage: [
19
- "oracle model interactive provider/model picker for the oracle profile",
24
+ "oracle model - interactive provider/model picker for the oracle profile",
20
25
  "",
21
26
  "inside chat: /model",
22
27
  "one oracle persona stays loaded when the model changes.",
23
28
  ].join("\n"),
24
29
  async run(ctx) {
25
- const hermes = hermesBin();
30
+ const hermes = runtimeBin();
26
31
  if (!hermes) {
27
- process.stderr.write("oracle model needs hermes on PATH\n");
32
+ process.stderr.write("oracle model needs the local runtime\nrun: oracle bootstrap\n");
28
33
  return 1;
29
34
  }
35
+ const launch = oracleHarnessInvocation(hermes, ["-p", "oracle", "model", ...ctx.argv]);
30
36
  return spawnChild(
31
- hermes,
32
- ["-p", "oracle", "model", ...ctx.argv],
37
+ launch.command,
38
+ launch.args,
33
39
  { stdio: "inherit", env: process.env },
34
40
  "oracle-model",
35
41
  );
@@ -1,6 +1,4 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
2
  import readline from "node:readline/promises";
5
3
  import { stdin as input, stdout as output } from "node:process";
6
4
  import {
@@ -15,16 +13,7 @@ import {
15
13
  touchSetupState,
16
14
  } from "../setup-state.mjs";
17
15
  import { hermesRoot } from "../paths.mjs";
18
-
19
- function which(bin) {
20
- for (const dir of (process.env.PATH || "").split(path.delimiter)) {
21
- const c = path.join(dir, bin);
22
- try {
23
- if (fs.existsSync(c)) return c;
24
- } catch {}
25
- }
26
- return null;
27
- }
16
+ import { installManagedHermes, resolveHermes } from "../runtime.mjs";
28
17
 
29
18
  function usage() {
30
19
  return [
@@ -71,14 +60,17 @@ function stripProfileArgs(args) {
71
60
  }
72
61
 
73
62
  function runHermes(argv, { inherit = true, env = process.env } = {}) {
74
- const hermes = which("hermes");
75
- if (!hermes) {
76
- process.stderr.write(
77
- "oracle setup: hermes not on PATH\ninstall hermes, then re-run\n",
78
- );
63
+ let resolved = resolveHermes();
64
+ if (!resolved.ok && process.stdin.isTTY && process.stdout.isTTY && process.env.ORACLE_NO_BOOTSTRAP !== "1") {
65
+ process.stdout.write("setting up oracle's local messaging runtime (one time)\n\n");
66
+ const installed = installManagedHermes();
67
+ if (installed.ok) resolved = { ok: true, bin: installed.bin };
68
+ }
69
+ if (!resolved.ok) {
70
+ process.stderr.write("oracle setup needs the local runtime\nrun: oracle bootstrap\n");
79
71
  return 1;
80
72
  }
81
- const r = spawnSync(hermes, argv, {
73
+ const r = spawnSync(resolved.bin, argv, {
82
74
  stdio: inherit ? "inherit" : "pipe",
83
75
  encoding: "utf8",
84
76
  env,
@@ -214,7 +206,7 @@ async function configurePlatform(profile, platformKey, inlineArgs = []) {
214
206
 
215
207
  export default {
216
208
  name: "setup",
217
- summary: "configure telegram/discord/slack and other hermes messaging platforms",
209
+ summary: "configure telegram/discord/slack messaging platforms",
218
210
  group: "read",
219
211
  usage: usage(),
220
212
  async run(ctx) {
@@ -1,9 +1,9 @@
1
1
  export const NEXT_STEPS_AFTER_INIT = [
2
2
  "oracle data serve # loopback read/prepare plane on 127.0.0.1:8787",
3
3
  "oracle doctor # verify",
4
- "oracle chat # one lowercase oracle surface",
4
+ "oracle chat # premium oracle chat surface",
5
5
  "oracle chain list # pick hyperliquid/base/solana/...",
6
- "oracle setup # telegram/discord/slack + hermes messaging",
6
+ "oracle setup # telegram/discord/slack messaging",
7
7
  "oracle mcp install claude-code",
8
8
  "# optional, explicit second step — signing stays a separate local package:",
9
9
  "npm i -g @oracle-agent/operator",
@@ -13,12 +13,13 @@ const HELP_HEADER = `oracle — self-custody multichain agent control plane`;
13
13
 
14
14
  const STATIC_HELP = `READ / RESEARCH (no keys, this package)
15
15
  oracle open the plain oracle chat harness (TTY)
16
+ oracle bootstrap install the local agent runtime chat needs
16
17
  oracle init install agent lanes + read plane (dry-run by default)
17
18
  oracle doctor check read plane; checks signer too when installed
18
- oracle chat one lowercase oracle chat surface (multi-model)
19
+ oracle chat premium boxed oracle chat (multi-model)
19
20
  oracle model choose the profile's default model/provider
20
21
  oracle chain list/select working chains (hyperliquid, base, ...)
21
- oracle setup telegram/discord/slack + hermes messaging menu
22
+ oracle setup telegram/discord/slack messaging menu
22
23
  oracle data serve|call|catalog|health
23
24
  oracle data-mcp start the read-only MCP server
24
25
  oracle scan chains|head|token|pools|risk|quote|sell
@@ -26,7 +27,7 @@ const STATIC_HELP = `READ / RESEARCH (no keys, this package)
26
27
  oracle prepare build an unsigned swap (alias of route prepare)
27
28
  oracle public serve secret-free public HTTP surface
28
29
  oracle mcp install <t> wire Oracle into claude-code|claude-desktop|codex|chatgpt
29
- oracle upgrade upgrade installed Hermes lanes
30
+ oracle upgrade upgrade installed agent lanes
30
31
 
31
32
  SIGNING (dispatches to @oracle-agent/operator on THIS machine; never in this package)
32
33
  oracle sign init|doctor provision / check the local signer (opt-in)
@@ -90,7 +91,7 @@ export function renderHelp(version, commands) {
90
91
  for (const cmd of commands.values()) {
91
92
  if (["help", "version"].includes(cmd.name)) continue;
92
93
  const known = new Set([
93
- "init","doctor","chat","model","chain","setup","data","data-mcp","scan","route","prepare","public","mcp","upgrade",
94
+ "init","doctor","chat","model","chain","setup","data","data-mcp","scan","route","prepare","public","mcp","upgrade","bootstrap",
94
95
  "sign","vault","signer","runner","credential",
95
96
  ]);
96
97
  if (!known.has(cmd.name) && cmd.summary) {
@@ -174,7 +174,7 @@ export function renderSetupMenu({ profile = "oracle", statuses = {} } = {}) {
174
174
  const lines = [
175
175
  "oracle setup",
176
176
  "",
177
- "one oracle. many models. hermes messaging underneath.",
177
+ "one oracle. many models. every connected channel.",
178
178
  `profile: ${profile}`,
179
179
  "",
180
180
  "messaging platforms:",