@oracle-agent/oracle 0.11.0 → 0.12.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.
Files changed (308) hide show
  1. package/LICENSE +115 -201
  2. package/README.md +66 -84
  3. package/SECURITY.md +37 -12
  4. package/SETUP.md +207 -272
  5. package/dist/action-receipts.mjs +1 -0
  6. package/dist/action-semantics.mjs +1 -0
  7. package/dist/address-book.mjs +2 -0
  8. package/dist/bin/desk-server.mjs +14 -0
  9. package/dist/bin/oracle-data-mcp.mjs +8 -0
  10. package/dist/bin/oracle-init.mjs +24 -0
  11. package/dist/bin/oracle-public-server.mjs +5 -0
  12. package/dist/bin/oracle-route.mjs +22 -0
  13. package/dist/bin/oracle-scan.mjs +18 -0
  14. package/dist/bin/oracle-upgrade.mjs +21 -0
  15. package/dist/bin/oracle.mjs +68 -0
  16. package/dist/chains.mjs +1 -0
  17. package/dist/cli/commands/auth.mjs +16 -0
  18. package/dist/cli/commands/bootstrap.mjs +18 -0
  19. package/dist/cli/commands/chain.mjs +21 -0
  20. package/dist/cli/commands/chat.mjs +35 -0
  21. package/dist/cli/commands/credential.mjs +1 -0
  22. package/dist/cli/commands/data-mcp.mjs +2 -0
  23. package/dist/cli/commands/data.mjs +12 -0
  24. package/dist/cli/commands/doctor.mjs +5 -0
  25. package/dist/cli/commands/gate.mjs +13 -0
  26. package/dist/cli/commands/help.mjs +1 -0
  27. package/dist/cli/commands/init.mjs +3 -0
  28. package/dist/cli/commands/mcp.mjs +33 -0
  29. package/dist/cli/commands/model.mjs +44 -0
  30. package/dist/cli/commands/prepare.mjs +2 -0
  31. package/dist/cli/commands/public.mjs +3 -0
  32. package/dist/cli/commands/route.mjs +2 -0
  33. package/dist/cli/commands/runner.mjs +1 -0
  34. package/dist/cli/commands/scan.mjs +2 -0
  35. package/dist/cli/commands/setup.mjs +34 -0
  36. package/dist/cli/commands/sign.mjs +4 -0
  37. package/dist/cli/commands/signer.mjs +1 -0
  38. package/dist/cli/commands/upgrade.mjs +2 -0
  39. package/dist/cli/commands/vault.mjs +1 -0
  40. package/dist/cli/commands/version.mjs +2 -0
  41. package/dist/data/desk-data.mjs +12 -0
  42. package/dist/index.mjs +17 -0
  43. package/dist/nft-gas-war-guard.mjs +1 -0
  44. package/dist/onboarding/harness-configs.mjs +4 -0
  45. package/dist/portfolio-risk.mjs +1 -0
  46. package/dist/prepare-envelope.mjs +1 -0
  47. package/dist/public-control/policy-schema.mjs +1 -0
  48. package/dist/router/index.mjs +2 -0
  49. package/dist/scanner/index.mjs +4 -0
  50. package/dist/signals/index.mjs +1 -0
  51. package/dist/watch-preferences.mjs +1 -0
  52. package/package.json +27 -65
  53. package/public/oracle-splash/index.html +11 -1
  54. package/CONTRIBUTING.md +0 -98
  55. package/bin/desk-server.mjs +0 -440
  56. package/bin/oracle-data-mcp.mjs +0 -1019
  57. package/bin/oracle-init.mjs +0 -496
  58. package/bin/oracle-public-server.mjs +0 -36
  59. package/bin/oracle-route.mjs +0 -254
  60. package/bin/oracle-scan.mjs +0 -192
  61. package/bin/oracle-upgrade.mjs +0 -42
  62. package/bin/oracle.mjs +0 -33
  63. package/docs/adding-a-chain.md +0 -229
  64. package/docs/architecture.md +0 -138
  65. package/docs/cli.md +0 -47
  66. package/docs/connectors.md +0 -13
  67. package/docs/oracle-pack-standard.md +0 -29
  68. package/docs/profiles.md +0 -151
  69. package/docs/public-surface.md +0 -133
  70. package/examples/add-a-chain.mjs +0 -65
  71. package/examples/oracle-pack-template.mjs +0 -38
  72. package/examples/research-a-token.mjs +0 -70
  73. package/plugins/oracle-owner-gate/__init__.py +0 -227
  74. package/plugins/oracle-owner-gate/plugin.yaml +0 -9
  75. package/profiles/_template/SOUL.md +0 -54
  76. package/profiles/_template/profile.json +0 -22
  77. package/profiles/bitcoin-agent/SOUL.md +0 -31
  78. package/profiles/bitcoin-agent/profile.json +0 -32
  79. package/profiles/hyperliquid-agent/SOUL.md +0 -34
  80. package/profiles/hyperliquid-agent/profile.json +0 -37
  81. package/profiles/oracle/SOUL.md +0 -107
  82. package/profiles/oracle/profile.json +0 -39
  83. package/profiles/polymarket-agent/SOUL.md +0 -35
  84. package/profiles/polymarket-agent/profile.json +0 -34
  85. package/profiles/profile.schema.json +0 -90
  86. package/profiles/protocol-builder/SOUL.md +0 -57
  87. package/profiles/protocol-builder/profile.json +0 -39
  88. package/profiles/robinhood-agent/SOUL.md +0 -53
  89. package/profiles/robinhood-agent/profile.json +0 -40
  90. package/profiles/solana-agent/SOUL.md +0 -37
  91. package/profiles/solana-agent/profile.json +0 -37
  92. package/profiles/stable-agent/SOUL.md +0 -43
  93. package/profiles/stable-agent/profile.json +0 -37
  94. package/protocols/templates/safe-erc20/SECURITY.md +0 -16
  95. package/protocols/templates/safe-erc20/foundry.toml +0 -12
  96. package/protocols/templates/safe-erc20/remappings.txt +0 -2
  97. package/protocols/templates/safe-erc20/src/SafeERC20.sol +0 -53
  98. package/protocols/templates/safe-erc20/test/SafeERC20.t.sol +0 -72
  99. package/scripts/adversarial-bench.mjs +0 -114
  100. package/scripts/build-inscription.py +0 -230
  101. package/scripts/check-doc-drift.mjs +0 -123
  102. package/scripts/check-test-count.mjs +0 -105
  103. package/scripts/e2e-hl-markets.mjs +0 -21
  104. package/scripts/e2e-hl-perps.mjs +0 -48
  105. package/scripts/e2e-hypercore-staking.mjs +0 -128
  106. package/scripts/e2e-solana-bitcoin.mjs +0 -183
  107. package/scripts/protocol-template-gate.mjs +0 -15
  108. package/scripts/public-api-scan.mjs +0 -23
  109. package/scripts/secret-scan.mjs +0 -181
  110. package/scripts/verify-v3-venues.mjs +0 -192
  111. package/skills/balance/SKILL.md +0 -176
  112. package/skills/oracle-action-semantics/SKILL.md +0 -40
  113. package/skills/oracle-best-execution/SKILL.md +0 -127
  114. package/skills/oracle-bitcoin/SKILL.md +0 -53
  115. package/skills/oracle-chain-graphs-telegram-cards/SKILL.md +0 -59
  116. package/skills/oracle-chat/SKILL.md +0 -61
  117. package/skills/oracle-chat/chain.SKILL.md +0 -31
  118. package/skills/oracle-chat/setup.SKILL.md +0 -37
  119. package/skills/oracle-circuit-breaker/SKILL.md +0 -51
  120. package/skills/oracle-contract-research/SKILL.md +0 -55
  121. package/skills/oracle-desk/SKILL.md +0 -58
  122. package/skills/oracle-dex-launch/SKILL.md +0 -38
  123. package/skills/oracle-grants/SKILL.md +0 -69
  124. package/skills/oracle-hypercore-staking/SKILL.md +0 -57
  125. package/skills/oracle-hyperliquid/SKILL.md +0 -56
  126. package/skills/oracle-meme-token-sniper/SKILL.md +0 -73
  127. package/skills/oracle-multichain-nft-launch/SKILL.md +0 -338
  128. package/skills/oracle-multichain-token-launch/SKILL.md +0 -300
  129. package/skills/oracle-nft-gacha-launch/SKILL.md +0 -48
  130. package/skills/oracle-nft-mint-gas-war/SKILL.md +0 -63
  131. package/skills/oracle-polymarket/SKILL.md +0 -60
  132. package/skills/oracle-protocol-builder/SKILL.md +0 -59
  133. package/skills/oracle-protocol-security/SKILL.md +0 -60
  134. package/skills/oracle-public-product/SKILL.md +0 -44
  135. package/skills/oracle-receipts/SKILL.md +0 -52
  136. package/skills/oracle-rfq-tokenized-assets/SKILL.md +0 -69
  137. package/skills/oracle-smart-wallet-scanner/SKILL.md +0 -49
  138. package/skills/oracle-solana/SKILL.md +0 -65
  139. package/skills/oracle-solana-nft/SKILL.md +0 -54
  140. package/skills/oracle-token-research/SKILL.md +0 -67
  141. package/skins/oracle.yaml +0 -56
  142. package/src/action-receipts.mjs +0 -265
  143. package/src/action-semantics.mjs +0 -62
  144. package/src/address-book.mjs +0 -208
  145. package/src/agent-auth.mjs +0 -206
  146. package/src/approval-guard.mjs +0 -282
  147. package/src/attestation-secret.mjs +0 -88
  148. package/src/audit-log.mjs +0 -196
  149. package/src/auth/oauth.mjs +0 -672
  150. package/src/auto-slippage.mjs +0 -378
  151. package/src/capability-posture.mjs +0 -125
  152. package/src/chains.mjs +0 -62
  153. package/src/cli/chain-catalog.mjs +0 -215
  154. package/src/cli/chain-state.mjs +0 -74
  155. package/src/cli/commands/auth.mjs +0 -193
  156. package/src/cli/commands/bootstrap.mjs +0 -87
  157. package/src/cli/commands/chain.mjs +0 -143
  158. package/src/cli/commands/chat.mjs +0 -434
  159. package/src/cli/commands/credential.mjs +0 -9
  160. package/src/cli/commands/data-mcp.mjs +0 -18
  161. package/src/cli/commands/data.mjs +0 -75
  162. package/src/cli/commands/doctor.mjs +0 -136
  163. package/src/cli/commands/help.mjs +0 -7
  164. package/src/cli/commands/init.mjs +0 -27
  165. package/src/cli/commands/mcp.mjs +0 -142
  166. package/src/cli/commands/model.mjs +0 -99
  167. package/src/cli/commands/prepare.mjs +0 -12
  168. package/src/cli/commands/public.mjs +0 -20
  169. package/src/cli/commands/route.mjs +0 -12
  170. package/src/cli/commands/runner.mjs +0 -9
  171. package/src/cli/commands/scan.mjs +0 -12
  172. package/src/cli/commands/setup.mjs +0 -275
  173. package/src/cli/commands/sign.mjs +0 -21
  174. package/src/cli/commands/signer.mjs +0 -9
  175. package/src/cli/commands/upgrade.mjs +0 -12
  176. package/src/cli/commands/vault.mjs +0 -9
  177. package/src/cli/commands/version.mjs +0 -22
  178. package/src/cli/first-run.mjs +0 -23
  179. package/src/cli/kernel.mjs +0 -239
  180. package/src/cli/mcp-targets/chatgpt.mjs +0 -51
  181. package/src/cli/mcp-targets/claude-code.mjs +0 -39
  182. package/src/cli/mcp-targets/claude-desktop.mjs +0 -24
  183. package/src/cli/mcp-targets/codex.mjs +0 -41
  184. package/src/cli/mcp-targets/shared.mjs +0 -61
  185. package/src/cli/messaging-platforms.mjs +0 -209
  186. package/src/cli/model-config.mjs +0 -70
  187. package/src/cli/operator-dispatch.mjs +0 -258
  188. package/src/cli/oracle-harness.py +0 -415
  189. package/src/cli/paths.mjs +0 -83
  190. package/src/cli/runtime.mjs +0 -351
  191. package/src/cli/setup-state.mjs +0 -168
  192. package/src/cli/spawn-child.mjs +0 -43
  193. package/src/data/catalog.mjs +0 -585
  194. package/src/data/desk-data.mjs +0 -695
  195. package/src/data/http.mjs +0 -253
  196. package/src/data/provider-endpoint.mjs +0 -94
  197. package/src/data/providers/aerodrome.mjs +0 -245
  198. package/src/data/providers/balancer.mjs +0 -209
  199. package/src/data/providers/bitcoin-esplora.mjs +0 -216
  200. package/src/data/providers/bitcoin-meta.mjs +0 -378
  201. package/src/data/providers/blockscout.mjs +0 -14
  202. package/src/data/providers/bridges.mjs +0 -242
  203. package/src/data/providers/cowswap.mjs +0 -415
  204. package/src/data/providers/curve.mjs +0 -201
  205. package/src/data/providers/defillama.mjs +0 -88
  206. package/src/data/providers/dexscreener.mjs +0 -43
  207. package/src/data/providers/evm-rpc.mjs +0 -225
  208. package/src/data/providers/geckoterminal.mjs +0 -34
  209. package/src/data/providers/gmx.mjs +0 -496
  210. package/src/data/providers/hl-assets.mjs +0 -165
  211. package/src/data/providers/hl-info.mjs +0 -108
  212. package/src/data/providers/hl-markets.mjs +0 -210
  213. package/src/data/providers/hl-outcome.mjs +0 -61
  214. package/src/data/providers/hl-perps.mjs +0 -387
  215. package/src/data/providers/hl-staking.mjs +0 -353
  216. package/src/data/providers/hl-ws.mjs +0 -119
  217. package/src/data/providers/hyperevm-dex.mjs +0 -49
  218. package/src/data/providers/jupiter-venues.mjs +0 -119
  219. package/src/data/providers/jupiter.mjs +0 -315
  220. package/src/data/providers/lifi.mjs +0 -151
  221. package/src/data/providers/magiceden-sol.mjs +0 -406
  222. package/src/data/providers/morpho.mjs +0 -177
  223. package/src/data/providers/nft-gallery.mjs +0 -163
  224. package/src/data/providers/nft-portfolio.mjs +0 -494
  225. package/src/data/providers/odos.mjs +0 -156
  226. package/src/data/providers/oneinch.mjs +0 -174
  227. package/src/data/providers/opensea-multichain.mjs +0 -136
  228. package/src/data/providers/opensea-nft.mjs +0 -371
  229. package/src/data/providers/paraswap.mjs +0 -118
  230. package/src/data/providers/pendle.mjs +0 -188
  231. package/src/data/providers/poly-clob.mjs +0 -283
  232. package/src/data/providers/poly-public.mjs +0 -96
  233. package/src/data/providers/poly-ws.mjs +0 -103
  234. package/src/data/providers/portfolio-history.mjs +0 -394
  235. package/src/data/providers/portfolio.mjs +0 -594
  236. package/src/data/providers/rfq.mjs +0 -15
  237. package/src/data/providers/rh-agent.mjs +0 -59
  238. package/src/data/providers/satflow.mjs +0 -341
  239. package/src/data/providers/signed-material-guard.mjs +0 -108
  240. package/src/data/providers/solana-rpc.mjs +0 -197
  241. package/src/data/providers/uniswap-v3.mjs +0 -349
  242. package/src/data/providers/uniswap-v4.mjs +0 -414
  243. package/src/data/providers/zerox.mjs +0 -167
  244. package/src/data/public-api-scan.mjs +0 -61
  245. package/src/data/quote-placeholder.mjs +0 -31
  246. package/src/exact-integer.mjs +0 -72
  247. package/src/exec-policy.mjs +0 -455
  248. package/src/flags.mjs +0 -15
  249. package/src/fresh-window.mjs +0 -76
  250. package/src/gmx-attestation.mjs +0 -176
  251. package/src/index.mjs +0 -100
  252. package/src/nft-gas-war-guard.mjs +0 -139
  253. package/src/onboarding/agent-keys.mjs +0 -161
  254. package/src/onboarding/harness-configs.mjs +0 -102
  255. package/src/onboarding/index.mjs +0 -18
  256. package/src/onboarding/tiers.mjs +0 -139
  257. package/src/oracle-env.mjs +0 -47
  258. package/src/portfolio-risk.mjs +0 -170
  259. package/src/prepare-envelope.mjs +0 -204
  260. package/src/profile-upgrade.mjs +0 -277
  261. package/src/protocol-execution.mjs +0 -84
  262. package/src/protocol-templates/gate.mjs +0 -179
  263. package/src/protocol-templates/prepare-deploy.mjs +0 -81
  264. package/src/public-api/buzz-integration.mjs +0 -256
  265. package/src/public-api/connect-agent.mjs +0 -416
  266. package/src/public-api/grants.mjs +0 -142
  267. package/src/public-api/http.mjs +0 -389
  268. package/src/public-control/aa-adapter.mjs +0 -402
  269. package/src/public-control/build-registry.mjs +0 -227
  270. package/src/public-control/bundler-client.mjs +0 -357
  271. package/src/public-control/grant-indexer.mjs +0 -296
  272. package/src/public-control/policy-render.mjs +0 -69
  273. package/src/public-control/policy-schema.mjs +0 -318
  274. package/src/public-control/runtime-config.mjs +0 -275
  275. package/src/public-control/session-key-model.mjs +0 -374
  276. package/src/public-control/session-orchestrator.mjs +0 -412
  277. package/src/rfq/intent.mjs +0 -180
  278. package/src/rfq/sources.mjs +0 -181
  279. package/src/route-attestation.mjs +0 -132
  280. package/src/router/best-execution.mjs +0 -326
  281. package/src/router/index.mjs +0 -226
  282. package/src/router/prepare-bridge.mjs +0 -289
  283. package/src/router/prepare-route.mjs +0 -398
  284. package/src/router/proposal.mjs +0 -311
  285. package/src/router/risk-classifier.mjs +0 -119
  286. package/src/router/route-sources.mjs +0 -364
  287. package/src/scanner/chains.config.mjs +0 -430
  288. package/src/scanner/contract.mjs +0 -270
  289. package/src/scanner/evm-scanner.mjs +0 -394
  290. package/src/scanner/index.mjs +0 -9
  291. package/src/scanner/v2-venue.mjs +0 -335
  292. package/src/scanner/v3-venue.mjs +0 -290
  293. package/src/scopes.mjs +0 -44
  294. package/src/sell-simulation.mjs +0 -167
  295. package/src/signals/engine.mjs +0 -146
  296. package/src/signals/index.mjs +0 -1
  297. package/src/token-transfer-guard.mjs +0 -188
  298. package/src/tui/app.mjs +0 -438
  299. package/src/tui/backend.mjs +0 -145
  300. package/src/tui/format.mjs +0 -272
  301. package/src/tui/gateway-client.mjs +0 -177
  302. package/src/tui/input.mjs +0 -501
  303. package/src/tui/renderer.mjs +0 -443
  304. package/src/tui/standalone-client.mjs +0 -666
  305. package/src/tui/theme.mjs +0 -81
  306. package/src/vault-attestation.mjs +0 -146
  307. package/src/venues.mjs +0 -206
  308. package/src/watch-preferences.mjs +0 -85
@@ -1,496 +0,0 @@
1
- #!/usr/bin/env node
2
- // oracle-init -- install Oracle's agent lanes into Hermes.
3
- //
4
- // Reads profiles/<lane>/profile.json, validates each against the shipped schema,
5
- // then creates the Hermes profile, installs its SOUL.md, registers the repo's
6
- // skills, and wires the oracle-data MCP server.
7
- //
8
- // Deliberate omissions:
9
- // * No model or provider is written. Oracle makes no model calls; each lane
10
- // inherits whatever the operator's Hermes already serves. profile.json carries
11
- // a capability CLASS ("strong-reasoner"), never a model id, so this installer
12
- // never pins a vendor.
13
- // * No grant is armed. Every lane lands DISARMED. Arming is a signed, scoped,
14
- // expiring act the user performs later.
15
- //
16
- // Default is a DRY RUN. Pass --apply to make changes.
17
-
18
- import fs from "node:fs";
19
- import path from "node:path";
20
- import os from "node:os";
21
- import { randomBytes } from "node:crypto";
22
- import { execFileSync } from "node:child_process";
23
- import { fileURLToPath } from "node:url";
24
-
25
- const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
26
- const PROFILES_DIR = path.join(ROOT, "profiles");
27
- const SKILLS_DIR = path.join(ROOT, "skills");
28
-
29
- const args = process.argv.slice(2);
30
- const APPLY = args.includes("--apply");
31
- const FORCE = args.includes("--force");
32
- const JSON_OUT = args.includes("--json");
33
- const only = (() => {
34
- const i = args.indexOf("--only");
35
- return i >= 0 ? args[i + 1] : null;
36
- })();
37
-
38
- const log = (...m) => {
39
- if (!JSON_OUT) console.log(...m);
40
- };
41
-
42
- // ---------------------------------------------------------------- validation
43
-
44
- function loadSchema() {
45
- return JSON.parse(
46
- fs.readFileSync(path.join(PROFILES_DIR, "profile.schema.json"), "utf8"),
47
- );
48
- }
49
-
50
- // Focused validator for the fields we actually depend on. Not a general JSON
51
- // Schema engine -- pulling one in for eight files would add a dependency to a
52
- // zero-dependency installer.
53
- function validateProfile(def, schema, skillNames) {
54
- const errors = [];
55
- const req = schema.required || [];
56
- for (const k of req) {
57
- if (def[k] === undefined) errors.push(`missing required field: ${k}`);
58
- }
59
- if (def.id && !/^[a-z_][a-z0-9-]*$/.test(def.id)) {
60
- errors.push(`id "${def.id}" must be lowercase, no dots (Hermes rejects dots)`);
61
- }
62
- const roles = schema.properties.role.enum;
63
- if (def.role && !roles.includes(def.role)) {
64
- errors.push(`role "${def.role}" not one of ${roles.join(", ")}`);
65
- }
66
- const p = def.posture || {};
67
- if (p.default !== "DISARMED") {
68
- errors.push("posture.default must be DISARMED -- a lane that ships armed is a bug");
69
- }
70
- if (p.signing && !["none", "user-wallet"].includes(p.signing)) {
71
- errors.push(`posture.signing "${p.signing}" invalid -- Oracle holds no keys`);
72
- }
73
- const allowedActions =
74
- schema.properties.posture.properties.grantActions.items.enum;
75
- for (const a of p.grantActions || []) {
76
- if (!allowedActions.includes(a)) {
77
- errors.push(`grantAction "${a}" not allowed (broadcast/sign actions are forbidden)`);
78
- }
79
- }
80
- if (typeof p.rationale === "string" && p.rationale.length < 20) {
81
- errors.push("posture.rationale too short -- justify the grant");
82
- }
83
- // Every named skill must actually exist. This is the check that would have
84
- // caught the phantom-skill problem in the original pack manifest.
85
- for (const s of def.skills || []) {
86
- if (!skillNames.has(s)) errors.push(`skill "${s}" does not exist in skills/`);
87
- }
88
- return errors;
89
- }
90
-
91
- // ---------------------------------------------------------------- discovery
92
-
93
- function discoverSkills() {
94
- if (!fs.existsSync(SKILLS_DIR)) return new Map();
95
- const out = new Map();
96
- for (const e of fs.readdirSync(SKILLS_DIR, { withFileTypes: true })) {
97
- if (!e.isDirectory()) continue;
98
- const skillFile = path.join(SKILLS_DIR, e.name, "SKILL.md");
99
- if (fs.existsSync(skillFile)) out.set(e.name, skillFile);
100
- }
101
- return out;
102
- }
103
-
104
- function discoverProfiles() {
105
- if (!fs.existsSync(PROFILES_DIR)) return [];
106
- const out = [];
107
- for (const e of fs.readdirSync(PROFILES_DIR, { withFileTypes: true })) {
108
- if (!e.isDirectory()) continue;
109
- const f = path.join(PROFILES_DIR, e.name, "profile.json");
110
- if (!fs.existsSync(f)) continue;
111
- const def = JSON.parse(fs.readFileSync(f, "utf8"));
112
- def.__dir = path.join(PROFILES_DIR, e.name);
113
- out.push(def);
114
- }
115
- return out;
116
- }
117
-
118
- // ---------------------------------------------------------------- hermes glue
119
-
120
- function hermesAvailable() {
121
- try {
122
- execFileSync("hermes", ["--version"], { stdio: "pipe", timeout: 20_000 });
123
- return true;
124
- } catch {
125
- return false;
126
- }
127
- }
128
-
129
- // Resolve the GLOBAL Hermes root, not a profile-scoped one.
130
- //
131
- // Trap worth knowing: when this runs inside an agent session that is itself
132
- // profile-scoped, HERMES_HOME points at ~/.hermes/profiles/<name>, not ~/.hermes.
133
- // Using it verbatim writes ~/.hermes/profiles/<name>/profiles/<lane>/SOUL.md --
134
- // a nested path Hermes never reads, so the install silently does nothing.
135
- // Detect that shape and walk back up to the real root.
136
- function hermesRoot() {
137
- const env = (process.env.HERMES_HOME || "").trim();
138
- if (env) {
139
- const parts = env.split(path.sep);
140
- const i = parts.lastIndexOf("profiles");
141
- // ".../profiles/<name>" -> strip the last two segments
142
- if (i > 0 && i === parts.length - 2) {
143
- return parts.slice(0, i).join(path.sep);
144
- }
145
- return env;
146
- }
147
- return path.join(os.homedir(), ".hermes");
148
- }
149
-
150
- function profileExists(id) {
151
- try {
152
- const out = execFileSync("hermes", ["profile", "list"], {
153
- encoding: "utf8",
154
- timeout: 30_000,
155
- });
156
- return new RegExp(`(^|\\s)${id}(\\s|$)`, "m").test(out);
157
- } catch {
158
- return false;
159
- }
160
- }
161
-
162
- const actions = [];
163
- const skipped = [];
164
- const mcpManual = [];
165
-
166
- function sameFile(a, b) {
167
- try {
168
- return fs.readFileSync(a, "utf8") === fs.readFileSync(b, "utf8");
169
- } catch {
170
- return false;
171
- }
172
- }
173
-
174
- function record(kind, detail) {
175
- actions.push({ kind, detail, applied: APPLY });
176
- log(`${APPLY ? " ✔" : " ·"} ${kind}: ${detail}`);
177
- }
178
-
179
- // Write Hermes mcp_servers.oracle-data directly into the profile config.
180
- // Prefer this over `hermes mcp add`: current Hermes takes --command and --args
181
- // as separate tokens, prompts interactively for tool enablement, and the old
182
- // installer form `--command "node <path>"` silently no-ops under execFileSync.
183
- function yamlScalar(value) {
184
- if (
185
- value === "" ||
186
- /[:#\[\]{},&*!|>'"%@`\s]/.test(value) ||
187
- /^(?:null|true|false|\d+)$/i.test(value)
188
- ) {
189
- return JSON.stringify(value);
190
- }
191
- return value;
192
- }
193
-
194
- function mcpServerBlock(serverName, scriptPath) {
195
- return [
196
- ` ${serverName}:`,
197
- " command: node",
198
- " args:",
199
- ` - ${yamlScalar(scriptPath)}`,
200
- " enabled: true",
201
- ].join("\n");
202
- }
203
-
204
- function wireMcpIntoConfig(configPath, serverName, scriptPath) {
205
- const entry = mcpServerBlock(serverName, scriptPath);
206
- if (!fs.existsSync(configPath)) {
207
- fs.writeFileSync(configPath, `mcp_servers:\n${entry}\n`, "utf8");
208
- return "created";
209
- }
210
-
211
- let txt = fs.readFileSync(configPath, "utf8");
212
- if (
213
- txt.includes(scriptPath) &&
214
- new RegExp(`^\\s*${serverName}:\\s*$`, "m").test(txt)
215
- ) {
216
- return "present";
217
- }
218
-
219
- // Replace an existing server block of the same name (simple indented map).
220
- const serverRe = new RegExp(
221
- `^([ \\t]*)${serverName}:\\s*\\n(?:\\1[ \\t]+.*\\n)*`,
222
- "m",
223
- );
224
- if (serverRe.test(txt)) {
225
- txt = txt.replace(serverRe, `${entry}\n`);
226
- fs.writeFileSync(configPath, txt, "utf8");
227
- return "updated";
228
- }
229
-
230
- if (/^mcp_servers:\s*$/m.test(txt) || /^mcp_servers:\s*\n/m.test(txt)) {
231
- txt = txt.replace(/^(mcp_servers:\s*\n)/m, `$1${entry}\n`);
232
- } else if (/^_config_version:.*$/m.test(txt)) {
233
- txt = txt.replace(
234
- /^(_config_version:.*\n)/m,
235
- `$1mcp_servers:\n${entry}\n`,
236
- );
237
- } else {
238
- txt = `mcp_servers:\n${entry}\n${txt}`;
239
- }
240
- fs.writeFileSync(configPath, txt, "utf8");
241
- return "wired";
242
- }
243
-
244
- // ---------------------------------------------------------------- main
245
-
246
- const schema = loadSchema();
247
- const skills = discoverSkills();
248
- const skillNames = new Set(skills.keys());
249
- let profiles = discoverProfiles();
250
-
251
- // The template is documentation, not a lane to install.
252
- profiles = profiles.filter((p) => !p.template);
253
- if (only) profiles = profiles.filter((p) => p.id === only);
254
-
255
- log(`oracle-init ${APPLY ? "(APPLY)" : "(dry run -- pass --apply to install)"}`);
256
- log(` profiles: ${profiles.length} skills: ${skills.size}`);
257
-
258
- // 1. validate everything BEFORE touching the system. A partial install across a
259
- // profile mesh is worse than no install.
260
- const invalid = [];
261
- for (const def of profiles) {
262
- const errs = validateProfile(def, schema, skillNames);
263
- if (errs.length) invalid.push({ id: def.id, errors: errs });
264
- }
265
- if (invalid.length) {
266
- if (JSON_OUT) {
267
- process.stdout.write(`${JSON.stringify({ ok: false, invalid }, null, 2)}\n`);
268
- } else {
269
- console.error("\nvalidation failed:");
270
- for (const v of invalid) {
271
- console.error(` ${v.id}:`);
272
- for (const e of v.errors) console.error(` - ${e}`);
273
- }
274
- }
275
- process.exit(1);
276
- }
277
- log(" validation: ok");
278
-
279
- // Ensure a route/slippage attestation secret exists for fresh installs.
280
- // Without it, swap sign/broadcast fails closed (unsigned guards are refused).
281
- // Never overwrite an existing secret.
282
- {
283
- const cfgDir = path.join(os.homedir(), ".config", "oracle");
284
- const envPath = path.join(cfgDir, "exec.env");
285
- let hasSecret = Boolean(
286
- process.env.ORACLE_ROUTE_ATTESTATION_SECRET || process.env.MAD_ROUTE_ATTESTATION_SECRET
287
- );
288
- if (!hasSecret && fs.existsSync(envPath)) {
289
- try {
290
- const txt = fs.readFileSync(envPath, "utf8");
291
- hasSecret = /^ORACLE_ROUTE_ATTESTATION_SECRET=.+$/m.test(txt) || /^MAD_ROUTE_ATTESTATION_SECRET=.+$/m.test(txt);
292
- } catch {
293
- /* treat as missing */
294
- }
295
- }
296
- if (hasSecret) {
297
- record("attestation secret", "present");
298
- } else {
299
- record("attestation secret", `generate -> ${path.relative(os.homedir(), envPath)}`);
300
- if (APPLY) {
301
- fs.mkdirSync(cfgDir, { recursive: true, mode: 0o700 });
302
- try {
303
- fs.chmodSync(cfgDir, 0o700);
304
- } catch {
305
- /* best-effort */
306
- }
307
- const secret = randomBytes(32).toString("hex");
308
- const line = `ORACLE_ROUTE_ATTESTATION_SECRET=${secret}\n`;
309
- if (fs.existsSync(envPath)) {
310
- const prev = fs.readFileSync(envPath, "utf8");
311
- if (!/ORACLE_ROUTE_ATTESTATION_SECRET=/.test(prev) && !/MAD_ROUTE_ATTESTATION_SECRET=/.test(prev)) {
312
- fs.appendFileSync(envPath, prev.endsWith("\n") || prev.length === 0 ? line : `\n${line}`, { mode: 0o600 });
313
- }
314
- } else {
315
- fs.writeFileSync(envPath, line, { mode: 0o600 });
316
- }
317
- try {
318
- fs.chmodSync(envPath, 0o600);
319
- } catch {
320
- /* best-effort */
321
- }
322
- }
323
- }
324
- }
325
-
326
- const hermes = hermesAvailable();
327
- // The hermes CLI is a CONVENIENCE, not a requirement.
328
- //
329
- // Verified behaviour: Hermes discovers any profile directory under
330
- // <root>/profiles/<name>/, so writing SOUL.md and skills/ is sufficient to create a
331
- // working lane. `hermes profile create` only pre-seeds boilerplate, and
332
- // `hermes mcp add` only edits that profile's config.
333
- //
334
- // Making the CLI mandatory would mean this installer -- and its tests -- could only
335
- // run on a machine with Hermes already installed, which is both a worse contributor
336
- // experience and untestable in CI. So: install files always, use the CLI when it is
337
- // there, and say plainly what was skipped when it is not.
338
- if (!hermes) {
339
- log(
340
- " hermes CLI: not found -- installing profile files directly.\n" +
341
- " (Hermes discovers any directory under <root>/profiles/, so this works.\n" +
342
- " MCP wiring is the one step that needs the CLI; instructions printed at the end.)",
343
- );
344
- } else {
345
- log(" hermes CLI: found");
346
- }
347
-
348
- // 2. install
349
- for (const def of profiles) {
350
- log(`\n${def.label} (${def.id})`);
351
-
352
- // Snapshot what exists BEFORE we create anything. This is the reliable signal
353
- // for the no-clobber guard below.
354
- //
355
- // Why not just check for the file later: `hermes profile create` writes its own
356
- // boilerplate SOUL.md, so after creation a SOUL.md always exists. Treating that
357
- // as "the user's file" would make the guard refuse to install the lane persona,
358
- // the install would silently no-op, and every lane would run generic Hermes.
359
- // Only a file that predates our run can hold work worth protecting.
360
- const laneDir = path.join(hermesRoot(), "profiles", def.id);
361
- const soulDest = path.join(laneDir, "SOUL.md");
362
- const soulExistedBefore = fs.existsSync(soulDest);
363
- const skillExistedBefore = new Map(
364
- (def.skills || []).map((s) => [
365
- s,
366
- fs.existsSync(path.join(laneDir, "skills", s, "SKILL.md")),
367
- ]),
368
- );
369
-
370
- const preExisting = hermes ? profileExists(def.id) : fs.existsSync(laneDir);
371
- if (preExisting) {
372
- record("profile exists", def.id);
373
- } else if (hermes) {
374
- record("create profile", def.id);
375
- if (APPLY) {
376
- try {
377
- execFileSync("hermes", ["profile", "create", def.id], {
378
- stdio: "pipe",
379
- timeout: 120_000,
380
- });
381
- } catch (err) {
382
- // Not fatal: writing the directory below is what actually creates the lane.
383
- record("create note", `hermes profile create failed for ${def.id}; writing files directly`);
384
- }
385
- }
386
- } else {
387
- record("create profile dir", def.id);
388
- if (APPLY) fs.mkdirSync(laneDir, { recursive: true });
389
- }
390
-
391
- // SOUL.md -- never clobber a persona the user wrote.
392
- const soulSrc = path.join(def.__dir, "SOUL.md");
393
- if (fs.existsSync(soulSrc)) {
394
- const collision = soulExistedBefore && !sameFile(soulSrc, soulDest);
395
- if (collision && !FORCE) {
396
- record(
397
- "SKIP SOUL.md (exists)",
398
- `${path.relative(os.homedir(), soulDest)} -- kept yours; --force to overwrite`,
399
- );
400
- skipped.push({ lane: def.id, file: "SOUL.md", reason: "already exists" });
401
- } else {
402
- record("install SOUL.md", path.relative(os.homedir(), soulDest));
403
- if (APPLY) {
404
- fs.mkdirSync(path.dirname(soulDest), { recursive: true });
405
- if (collision) {
406
- const bak = `${soulDest}.bak-oracle-init-${Date.now()}`;
407
- fs.copyFileSync(soulDest, bak);
408
- record("backed up", path.relative(os.homedir(), bak));
409
- }
410
- fs.copyFileSync(soulSrc, soulDest);
411
- }
412
- }
413
- }
414
-
415
- // skills -- same discipline
416
- for (const s of def.skills || []) {
417
- const dest = path.join(laneDir, "skills", s, "SKILL.md");
418
- const collision = skillExistedBefore.get(s) && !sameFile(skills.get(s), dest);
419
- if (collision && !FORCE) {
420
- record("SKIP skill (exists)", `${s} -> ${def.id}`);
421
- skipped.push({ lane: def.id, file: `skills/${s}`, reason: "already exists" });
422
- continue;
423
- }
424
- record("install skill", `${s} -> ${def.id}`);
425
- if (APPLY) {
426
- fs.mkdirSync(path.dirname(dest), { recursive: true });
427
- fs.copyFileSync(skills.get(s), dest);
428
- }
429
- }
430
-
431
- // MCP: write Hermes config directly. CLI is optional convenience only.
432
- for (const m of def.mcp || []) {
433
- const scriptPath = path.join(ROOT, "bin", "oracle-data-mcp.mjs");
434
- const configPath = path.join(laneDir, "config.yaml");
435
- const manual = `hermes -p ${def.id} mcp add ${m} --command node --args ${scriptPath}`;
436
-
437
- if (m !== "oracle-data") {
438
- record("mcp manual", manual);
439
- mcpManual.push({ lane: def.id, server: m, command: manual });
440
- continue;
441
- }
442
-
443
- record(
444
- "wire mcp",
445
- `${m} (${def.id}) -> ${path.relative(os.homedir(), configPath)}`,
446
- );
447
- if (APPLY) {
448
- try {
449
- fs.mkdirSync(laneDir, { recursive: true });
450
- const how = wireMcpIntoConfig(configPath, m, scriptPath);
451
- record(
452
- "mcp config",
453
- `${how}: ${path.relative(os.homedir(), configPath)}`,
454
- );
455
- } catch (err) {
456
- record("mcp note", `could not write ${configPath}: ${err.message}`);
457
- mcpManual.push({ lane: def.id, server: m, command: manual });
458
- }
459
- }
460
- }
461
-
462
- record("posture", `DISARMED (${(def.posture.grantActions || []).join(", ")})`);
463
- }
464
-
465
- const summary = {
466
- ok: true,
467
- applied: APPLY,
468
- profiles: profiles.map((p) => p.id),
469
- skills: [...skillNames],
470
- actions: actions.length,
471
- skipped,
472
- mcpManual,
473
- hermesDetected: hermes,
474
- hermesRoot: hermesRoot(),
475
- };
476
-
477
- if (JSON_OUT) {
478
- process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
479
- } else {
480
- log(`\n${APPLY ? "installed" : "would install"} ${profiles.length} lanes.`);
481
- if (skipped.length) {
482
- log(`${skipped.length} file(s) already existed and were kept:`);
483
- for (const s of skipped) log(` - ${s.lane}/${s.file}`);
484
- log("pass --force to overwrite (a timestamped .bak is written first).");
485
- }
486
- if (mcpManual.length) {
487
- log("\nMCP wiring needed a manual follow-up:");
488
- for (const m of mcpManual) log(` ${m.command}`);
489
- }
490
- if (!APPLY) log("re-run with --apply to make changes.");
491
- log("\nEvery lane is DISARMED. Start the read plane, then chat a lane:");
492
- log(" npx oracle-data # 127.0.0.1:8787 — MCP tools need this");
493
- log(" hermes -p oracle chat");
494
- log("\nSet each lane's model in:");
495
- log(` ${path.join(hermesRoot(), "profiles", "<lane>", "config.yaml")}`);
496
- }
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env node
2
- // Oracle Public HTTP server (Slice G) — secret-free BFF boot script.
3
- //
4
- // ORACLE_PUBLIC_HOST=127.0.0.1 ORACLE_PUBLIC_PORT=8799 node bin/oracle-public-server.mjs
5
- //
6
- // Loopback by default. Serves ONLY the public read/assemble contract; no
7
- // signing, no keys, no executor imports. Every response body is scanned for
8
- // secret material before send (see src/public-api/http.mjs).
9
-
10
- import { startPublicServer, resolvePublicBind, VERSION, PUBLIC_PLANE } from "../src/public-api/http.mjs";
11
-
12
- const bind = resolvePublicBind();
13
- const server = startPublicServer(bind);
14
-
15
- server.on("listening", () => {
16
- const addr = server.address();
17
- const host = typeof addr === "object" && addr ? addr.address : bind.host;
18
- const port = typeof addr === "object" && addr ? addr.port : bind.port;
19
- // NOTE: log line intentionally carries only public bind info + version.
20
- console.log(
21
- `oracle-public-server listening on http://${host}:${port} (plane=${PUBLIC_PLANE}, version=${VERSION}, unsigned-only)`
22
- );
23
- });
24
-
25
- server.on("error", (err) => {
26
- console.error(`oracle-public-server failed to bind: ${err.code || err.message}`);
27
- process.exit(1);
28
- });
29
-
30
- for (const sig of ["SIGINT", "SIGTERM"]) {
31
- process.on(sig, () => {
32
- server.close(() => process.exit(0));
33
- // Fallback hard-exit if sockets linger.
34
- setTimeout(() => process.exit(0), 1000).unref();
35
- });
36
- }