@reefclaw/connect 0.1.39 → 0.1.41
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 +23 -0
- package/assets/bridge/config.d.ts +7 -0
- package/assets/bridge/config.js +13 -0
- package/assets/bridge/gateway/agent-scope.d.ts +30 -0
- package/assets/bridge/gateway/agent-scope.js +67 -0
- package/assets/bridge/gateway/gateway-config.d.ts +18 -3
- package/assets/bridge/gateway/gateway-config.js +42 -4
- package/assets/bridge/gateway/gateway-ws-client.d.ts +24 -0
- package/assets/bridge/gateway/gateway-ws-client.js +87 -4
- package/assets/bridge/index.js +35 -30
- package/assets/bridge/providers/connector-update.d.ts +43 -0
- package/assets/bridge/providers/connector-update.js +149 -0
- package/assets/bridge/providers/gateway.d.ts +10 -5
- package/assets/bridge/providers/gateway.js +96 -79
- package/assets/plugin/exchange-adapter.d.ts +7 -2
- package/assets/plugin/index.js +12 -0
- package/assets/plugin/ingest/readiness-reporter.d.ts +8 -1
- package/assets/plugin/ingest/readiness-reporter.js +7 -3
- package/assets/plugin/live/stop-watcher.js +7 -1
- package/assets/plugin/openclaw.plugin.json +1 -1
- package/assets/plugin/paper-adapter.d.ts +1 -1
- package/assets/plugin/paper-adapter.js +2 -2
- package/assets/plugin/plugin-version.d.ts +21 -0
- package/assets/plugin/plugin-version.js +58 -0
- package/assets/plugin/simulator/exchange-simulator.js +5 -1
- package/assets/plugin/simulator/realistic-fills.d.ts +16 -0
- package/assets/plugin/simulator/realistic-fills.js +26 -2
- package/assets/plugin/simulator/types.d.ts +4 -0
- package/assets/shared/readiness.d.ts +5 -0
- package/dist/agents.js +92 -0
- package/dist/cli.js +16 -0
- package/package.json +5 -2
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Which ReefClaw plugin RELEASE is running, and how it was installed — the two
|
|
2
|
+
// facts the dashboard needs to (a) tell the trader an update exists and (b)
|
|
3
|
+
// offer the right update path (one click for npx installs, ClawHub steps for
|
|
4
|
+
// ClawHub installs, silence for the operator's own source/dist deploys).
|
|
5
|
+
//
|
|
6
|
+
// Version source: the `openclaw.plugin.json` that ships NEXT TO index.js in
|
|
7
|
+
// every installed layout. Both release channels stamp it with the package
|
|
8
|
+
// release version — installer/scripts/bundle-assets.mjs for `npx
|
|
9
|
+
// @reefclaw/connect`, plugin-package/scripts/assemble.mjs for ClawHub. The
|
|
10
|
+
// repo's own manifest carries the unstamped 0.1.0, which is what tells a
|
|
11
|
+
// source-tree / script-deployed box apart from a packaged install.
|
|
12
|
+
//
|
|
13
|
+
// History: the readiness report used to send a hardcoded internal constant
|
|
14
|
+
// ('3.8.0') that lived in a different namespace from the release versions
|
|
15
|
+
// ('0.1.x'), so the dashboard's update banner compared apples to oranges and
|
|
16
|
+
// never fired (memory feedback_plugin_update_banner_inert_version_namespace).
|
|
17
|
+
// The webapp still recognises that legacy value and nudges those boxes once.
|
|
18
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { join, resolve, sep } from 'node:path';
|
|
20
|
+
import { homedir } from 'node:os';
|
|
21
|
+
/** The repo manifest's placeholder version. A box reporting it was not
|
|
22
|
+
* installed from a release package (no release will ever be 0.1.0 — the
|
|
23
|
+
* release line passed it long ago). */
|
|
24
|
+
export const UNSTAMPED_VERSION = '0.1.0';
|
|
25
|
+
const RELEASE_VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
26
|
+
export function resolvePluginInstallFacts(input) {
|
|
27
|
+
let version;
|
|
28
|
+
try {
|
|
29
|
+
const raw = readFileSync(join(input.pluginRoot, 'openclaw.plugin.json'), 'utf-8');
|
|
30
|
+
const v = JSON.parse(raw).version;
|
|
31
|
+
if (typeof v === 'string' && RELEASE_VERSION_RE.test(v.trim()))
|
|
32
|
+
version = v.trim();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// No (readable) manifest next to index.js — legacy deploy layout. Report
|
|
36
|
+
// nothing rather than a guess; the dashboard fails open (no banner).
|
|
37
|
+
}
|
|
38
|
+
return { version, channel: detectChannel(input, version) };
|
|
39
|
+
}
|
|
40
|
+
function detectChannel(input, version) {
|
|
41
|
+
// Unstamped or absent manifest → the repo's own: source tree or a dist deploy
|
|
42
|
+
// (the operator's rigs, updated by deploy scripts — never by the installer).
|
|
43
|
+
if (version === undefined || version === UNSTAMPED_VERSION)
|
|
44
|
+
return 'source';
|
|
45
|
+
// ClawHub packages carry the bootstrap skill at the package root
|
|
46
|
+
// (plugin-package/scripts/assemble.mjs); the npx layout deliberately does
|
|
47
|
+
// not. Checked FIRST: a box that once ran the npx installer and later
|
|
48
|
+
// installed from ClawHub loads the ClawHub copy.
|
|
49
|
+
if (existsSync(join(input.pluginRoot, 'skills')))
|
|
50
|
+
return 'clawhub';
|
|
51
|
+
const home = resolve(input.reefclawHome ?? join(homedir(), '.reefclaw'));
|
|
52
|
+
const root = resolve(input.pluginRoot);
|
|
53
|
+
if (input.connectorSupervisor === 'on' || root.startsWith(home + sep))
|
|
54
|
+
return 'npx';
|
|
55
|
+
// Stamped release package without an installer marker (placed by hand).
|
|
56
|
+
// The installer still updates it in place, so it gets the one-click path.
|
|
57
|
+
return 'npx';
|
|
58
|
+
}
|
|
@@ -9,6 +9,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
9
9
|
import { logger, formatError } from '../logger.js';
|
|
10
10
|
import { MAX_TRADE_HISTORY, DEFAULT_SIMULATION_CONFIG } from './types.js';
|
|
11
11
|
import { fillMarketOrder, fillLimitOrder, parseSymbol } from './fill-engine.js';
|
|
12
|
+
import { MAX_BOOK_DRIFT_BPS } from './realistic-fills.js';
|
|
12
13
|
import { updateMfe } from '../mfe.js';
|
|
13
14
|
import { computeInvalidationHit } from '../pinned-plan.js';
|
|
14
15
|
const TAG = 'simulator';
|
|
@@ -805,7 +806,10 @@ export class ExchangeSimulator extends EventEmitter {
|
|
|
805
806
|
logger.info(TAG, `Market order filled: ${order.side} ${order.amount} ${order.symbol} @ ${result.order.average}` +
|
|
806
807
|
` (decision: ${eq.decisionPrice.toFixed(2)}, slippage: ${eq.slippageBps.toFixed(2)}bps` +
|
|
807
808
|
`, latency: ${eq.latencyMs.toFixed(0)}ms, fee: ${eq.feeRate * 100}%` +
|
|
808
|
-
`, book: ${eq.bookDepthAvailable ? `${eq.bookLevelsConsumed} levels` : 'unavailable'}
|
|
809
|
+
`, book: ${eq.bookDepthAvailable ? `${eq.bookLevelsConsumed} levels` : 'unavailable'}` +
|
|
810
|
+
`${eq.bookDriftBps !== undefined && Math.abs(eq.bookDriftBps) > MAX_BOOK_DRIFT_BPS
|
|
811
|
+
? `, stale book ${eq.bookDriftBps.toFixed(0)}bps off the decision price: fill anchored`
|
|
812
|
+
: ''})`);
|
|
809
813
|
}
|
|
810
814
|
else {
|
|
811
815
|
logger.info(TAG, `Market order filled: ${order.side} ${order.amount} ${order.symbol} @ ${result.order.average}`);
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { OrderBookDepth, SimulationConfig, ExecutionQuality } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* How far (bps) the cached book's mid may sit from the decision price before
|
|
4
|
+
* the fill is anchored to the decision price instead of the raw book VWAP.
|
|
5
|
+
*
|
|
6
|
+
* The book is refreshed only when a tool fetches it (tools/helpers.ts
|
|
7
|
+
* fetchOrderBook, typically at entry), so a stop or target hours later used to
|
|
8
|
+
* fill against an hours-old book: an ADA/USDT short stop with a 0.2123 breach
|
|
9
|
+
* mark filled at 0.207103, 245 bps in the trader's favour, booking a -1.04R
|
|
10
|
+
* loss as -0.02R (2026-09-16 audit). Inside the band the book is fresh enough
|
|
11
|
+
* to price the fill directly (unchanged behaviour); outside it the book only
|
|
12
|
+
* shapes the impact (VWAP vs mid), applied to the price the decision was
|
|
13
|
+
* actually made on.
|
|
14
|
+
*/
|
|
15
|
+
export declare const MAX_BOOK_DRIFT_BPS = 10;
|
|
2
16
|
/**
|
|
3
17
|
* Walk the order book to compute a volume-weighted average fill price.
|
|
4
18
|
*
|
|
@@ -45,6 +59,8 @@ export declare function getFeeRate(orderType: 'market' | 'limit', config: Simula
|
|
|
45
59
|
*
|
|
46
60
|
* Combines: orderbook VWAP + latency drift + appropriate fee rate.
|
|
47
61
|
* Falls back to simple random slippage if no orderbook is available.
|
|
62
|
+
* A book whose mid has drifted more than MAX_BOOK_DRIFT_BPS from the decision
|
|
63
|
+
* price is stale: its impact is kept, the level is re-anchored.
|
|
48
64
|
*
|
|
49
65
|
* @returns fillPrice and ExecutionQuality metrics
|
|
50
66
|
*/
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
// Phase 9a: Realistic fill simulation.
|
|
2
2
|
// Order book-aware VWAP fills, latency modeling, maker/taker fees.
|
|
3
3
|
import { DEFAULT_SIMULATION_CONFIG, priceToBps } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* How far (bps) the cached book's mid may sit from the decision price before
|
|
6
|
+
* the fill is anchored to the decision price instead of the raw book VWAP.
|
|
7
|
+
*
|
|
8
|
+
* The book is refreshed only when a tool fetches it (tools/helpers.ts
|
|
9
|
+
* fetchOrderBook, typically at entry), so a stop or target hours later used to
|
|
10
|
+
* fill against an hours-old book: an ADA/USDT short stop with a 0.2123 breach
|
|
11
|
+
* mark filled at 0.207103, 245 bps in the trader's favour, booking a -1.04R
|
|
12
|
+
* loss as -0.02R (2026-09-16 audit). Inside the band the book is fresh enough
|
|
13
|
+
* to price the fill directly (unchanged behaviour); outside it the book only
|
|
14
|
+
* shapes the impact (VWAP vs mid), applied to the price the decision was
|
|
15
|
+
* actually made on.
|
|
16
|
+
*/
|
|
17
|
+
export const MAX_BOOK_DRIFT_BPS = 10;
|
|
4
18
|
/**
|
|
5
19
|
* Walk the order book to compute a volume-weighted average fill price.
|
|
6
20
|
*
|
|
@@ -92,6 +106,8 @@ export function getFeeRate(orderType, config) {
|
|
|
92
106
|
*
|
|
93
107
|
* Combines: orderbook VWAP + latency drift + appropriate fee rate.
|
|
94
108
|
* Falls back to simple random slippage if no orderbook is available.
|
|
109
|
+
* A book whose mid has drifted more than MAX_BOOK_DRIFT_BPS from the decision
|
|
110
|
+
* price is stale: its impact is kept, the level is re-anchored.
|
|
95
111
|
*
|
|
96
112
|
* @returns fillPrice and ExecutionQuality metrics
|
|
97
113
|
*/
|
|
@@ -103,6 +119,7 @@ export function computeRealisticMarketFill(side, amount, decisionPrice, orderboo
|
|
|
103
119
|
let latencyImpactBps;
|
|
104
120
|
let bookLevelsConsumed;
|
|
105
121
|
let bookDepthAvailable;
|
|
122
|
+
let bookDriftBps;
|
|
106
123
|
if (orderbook && orderbook.asks.length > 0 && orderbook.bids.length > 0) {
|
|
107
124
|
// Book-aware VWAP fill
|
|
108
125
|
const { vwap, levelsConsumed } = computeBookAwareFillPrice(side, amount, orderbook);
|
|
@@ -113,8 +130,14 @@ export function computeRealisticMarketFill(side, amount, decisionPrice, orderboo
|
|
|
113
130
|
marketImpactBps = priceToBps(vwap, midPrice);
|
|
114
131
|
// For sells, impact is negative (received less), so take absolute for the metric
|
|
115
132
|
// but keep signed for the actual price
|
|
116
|
-
//
|
|
117
|
-
|
|
133
|
+
// A book that has drifted from the decision price is stale: keep its
|
|
134
|
+
// impact, re-anchor the level (see MAX_BOOK_DRIFT_BPS).
|
|
135
|
+
bookDriftBps = priceToBps(midPrice, decisionPrice);
|
|
136
|
+
const level = Math.abs(bookDriftBps) > MAX_BOOK_DRIFT_BPS
|
|
137
|
+
? decisionPrice * (1 + marketImpactBps / 10_000)
|
|
138
|
+
: vwap;
|
|
139
|
+
// Apply latency drift on top of the (possibly re-anchored) VWAP
|
|
140
|
+
const latencyResult = applyLatencyDrift(level, side, latencyMs, volFactor);
|
|
118
141
|
fillPrice = latencyResult.adjustedPrice;
|
|
119
142
|
latencyImpactBps = latencyResult.latencyImpactBps;
|
|
120
143
|
}
|
|
@@ -147,6 +170,7 @@ export function computeRealisticMarketFill(side, amount, decisionPrice, orderboo
|
|
|
147
170
|
feePaid,
|
|
148
171
|
bookLevelsConsumed,
|
|
149
172
|
bookDepthAvailable,
|
|
173
|
+
...(bookDriftBps !== undefined ? { bookDriftBps } : {}),
|
|
150
174
|
};
|
|
151
175
|
return { fillPrice, executionQuality };
|
|
152
176
|
}
|
|
@@ -39,6 +39,10 @@ export interface ExecutionQuality {
|
|
|
39
39
|
feePaid: number;
|
|
40
40
|
bookLevelsConsumed: number;
|
|
41
41
|
bookDepthAvailable: boolean;
|
|
42
|
+
/** Signed bps between the cached book's mid and the decision price at fill
|
|
43
|
+
* time. Beyond MAX_BOOK_DRIFT_BPS the fill was anchored to the decision
|
|
44
|
+
* price (stale book). Absent when no book was available. */
|
|
45
|
+
bookDriftBps?: number;
|
|
42
46
|
/** Age of the quote the fill priced against (fill time − ticker.timestamp).
|
|
43
47
|
* Surfaces feed staleness (issue #202); absent on records from before the
|
|
44
48
|
* field existed or when the ticker carried no usable timestamp. */
|
|
@@ -48,7 +48,12 @@ export interface ReadinessReport {
|
|
|
48
48
|
checks: ReadinessCheck[];
|
|
49
49
|
/** Best-effort agent facts for display. */
|
|
50
50
|
agent?: {
|
|
51
|
+
/** Release version from the shipped manifest ('0.1.27'). Legacy plugins
|
|
52
|
+
* sent an internal '3.x' constant — the webapp treats that as "behind". */
|
|
51
53
|
pluginVersion?: string;
|
|
54
|
+
/** 'npx' | 'clawhub' | 'source' — how the plugin was installed. Kept
|
|
55
|
+
* `string` so an older webapp renders reports from a newer plugin. */
|
|
56
|
+
installChannel?: string;
|
|
52
57
|
toolCount?: number;
|
|
53
58
|
/** Trading venue this agent executes on ('binance' | 'hyperliquid').
|
|
54
59
|
* Kept `string` so an older webapp renders reports from a newer plugin.
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Which OpenClaw agent ReefClaw attaches to, and a loud notice when the
|
|
2
|
+
// gateway hosts more than one.
|
|
3
|
+
//
|
|
4
|
+
// The installer targets the gateway's DEFAULT agent: `openclaw skills install`
|
|
5
|
+
// puts the trading skill in its workspace, the bridge's heartbeat cron runs on
|
|
6
|
+
// it, and the bridge addresses chat to it and shows only its runs
|
|
7
|
+
// (skill/src/gateway/agent-scope.ts applies the same rule). On a single-agent
|
|
8
|
+
// box that is simply `main`. On a gateway that already runs, say, a business
|
|
9
|
+
// agent as its default, a fresh "trading" agent added alongside gets NOTHING
|
|
10
|
+
// and the business agent starts trading — silently, until 2026-09-16. This
|
|
11
|
+
// module makes that choice visible at install time. A full "pick the agent"
|
|
12
|
+
// flow (skill placed with --agent, heartbeat cron with agentId, per-agent
|
|
13
|
+
// paths in the bridge) is a later feature.
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { REEFCLAW_HOME } from './paths.js';
|
|
17
|
+
export const DEFAULT_AGENT_ID = 'main';
|
|
18
|
+
function normalizeAgentId(value) {
|
|
19
|
+
if (typeof value !== 'string')
|
|
20
|
+
return undefined;
|
|
21
|
+
const trimmed = value.trim().toLowerCase();
|
|
22
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
23
|
+
}
|
|
24
|
+
function agentEntries(cfg) {
|
|
25
|
+
const list = cfg?.agents?.list;
|
|
26
|
+
if (!Array.isArray(list))
|
|
27
|
+
return [];
|
|
28
|
+
return list.filter((e) => !!e && typeof e === 'object' && !Array.isArray(e));
|
|
29
|
+
}
|
|
30
|
+
/** Every agent id the gateway lists; empty on a single-agent box. */
|
|
31
|
+
export function listAgentIds(cfg) {
|
|
32
|
+
const ids = [];
|
|
33
|
+
for (const entry of agentEntries(cfg)) {
|
|
34
|
+
const id = normalizeAgentId(entry.id);
|
|
35
|
+
if (id && !ids.includes(id))
|
|
36
|
+
ids.push(id);
|
|
37
|
+
}
|
|
38
|
+
return ids;
|
|
39
|
+
}
|
|
40
|
+
/** OpenClaw's own rule (agent-scope-config.ts `resolveDefaultAgentId`): the
|
|
41
|
+
* `agents.list` entry flagged `default: true`, else the first, else `main`. */
|
|
42
|
+
export function resolveDefaultAgentId(cfg) {
|
|
43
|
+
const entries = agentEntries(cfg);
|
|
44
|
+
if (entries.length === 0)
|
|
45
|
+
return DEFAULT_AGENT_ID;
|
|
46
|
+
const chosen = entries.find((e) => e.default === true) ?? entries[0];
|
|
47
|
+
return normalizeAgentId(chosen.id) ?? DEFAULT_AGENT_ID;
|
|
48
|
+
}
|
|
49
|
+
/** Best-effort read of ~/.reefclaw/plugin-config.json (the bridge reads the
|
|
50
|
+
* same file for its `agentId` override). Null when absent or unreadable. */
|
|
51
|
+
export function readPluginConfig(path = join(REEFCLAW_HOME, 'plugin-config.json')) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
54
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function describeAgentAttachment(cfg, pluginConfig) {
|
|
61
|
+
const defaultAgentId = resolveDefaultAgentId(cfg);
|
|
62
|
+
const override = normalizeAgentId(pluginConfig?.agentId);
|
|
63
|
+
const agentId = override ?? defaultAgentId;
|
|
64
|
+
const hosted = listAgentIds(cfg);
|
|
65
|
+
return {
|
|
66
|
+
agentId,
|
|
67
|
+
source: override ? 'override' : 'default',
|
|
68
|
+
defaultAgentId,
|
|
69
|
+
hosted,
|
|
70
|
+
shared: hosted.some((id) => id !== agentId),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** The lines the installer prints. Pure, so the copy is unit-tested. */
|
|
74
|
+
export function agentAttachmentNotice(a) {
|
|
75
|
+
if (!a.shared) {
|
|
76
|
+
return { level: 'ok', lines: [`ReefClaw attaches to agent '${a.agentId}'`] };
|
|
77
|
+
}
|
|
78
|
+
const others = a.hosted.filter((id) => id !== a.agentId).join(', ');
|
|
79
|
+
const lines = [
|
|
80
|
+
`this gateway also hosts: ${others}. ReefClaw attaches to '${a.agentId}' `
|
|
81
|
+
+ (a.source === 'override'
|
|
82
|
+
? '(the agentId set in ~/.reefclaw/plugin-config.json).'
|
|
83
|
+
: '(the gateway default agent).'),
|
|
84
|
+
`The trading skill goes into that agent's workspace, the heartbeat runs on it, and the dashboard shows only its runs.`,
|
|
85
|
+
`To trade on a different agent, flag it "default": true in agents.list of openclaw.json and re-run this installer, `
|
|
86
|
+
+ `or give the trading agent its own OpenClaw instance (cleanest).`,
|
|
87
|
+
];
|
|
88
|
+
if (a.source === 'override' && a.agentId !== a.defaultAgentId) {
|
|
89
|
+
lines.push(`Note: the override only steers the bridge on this version; the skill and heartbeat still land on the default agent '${a.defaultAgentId}'.`);
|
|
90
|
+
}
|
|
91
|
+
return { level: 'warn', lines };
|
|
92
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { installPlugin } from './plugin.js';
|
|
|
14
14
|
import { installBridge } from './bridge.js';
|
|
15
15
|
import { installSkill } from './skill.js';
|
|
16
16
|
import { enableConnectorSupervisor } from './supervisor-config.js';
|
|
17
|
+
import { describeAgentAttachment, readPluginConfig, agentAttachmentNotice } from './agents.js';
|
|
17
18
|
import { tuneGatewayMemory } from './gateway-tuning.js';
|
|
18
19
|
import { checkGateway, checkBinanceRegion, checkHyperliquidRegion } from './validate.js';
|
|
19
20
|
import { readConfig, writeConfig, mergeReefClawConfig, gatewayAuthAlreadyRelaxed, openClawInstalled, openClawConfigPath, readGatewayPort, } from './openclaw.js';
|
|
@@ -149,6 +150,20 @@ function parseVenueArg(argv) {
|
|
|
149
150
|
}
|
|
150
151
|
return null;
|
|
151
152
|
}
|
|
153
|
+
/** Say which agent ReefClaw is about to attach to. On a gateway that hosts
|
|
154
|
+
* several agents this is the difference between the trading agent trading
|
|
155
|
+
* and the business agent trading (see agents.ts). */
|
|
156
|
+
function noticeAgentAttachment(config) {
|
|
157
|
+
step('Choosing the agent ReefClaw attaches to');
|
|
158
|
+
const notice = agentAttachmentNotice(describeAgentAttachment(config, readPluginConfig()));
|
|
159
|
+
if (notice.level === 'ok') {
|
|
160
|
+
ok(notice.lines[0]);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
warn(notice.lines[0]);
|
|
164
|
+
for (const line of notice.lines.slice(1))
|
|
165
|
+
info(line);
|
|
166
|
+
}
|
|
152
167
|
async function main() {
|
|
153
168
|
banner(bold(cyan('ReefClaw connect')) + dim(' — link your OpenClaw agent to ReefClaw (paper trading)'));
|
|
154
169
|
preflight();
|
|
@@ -165,6 +180,7 @@ async function main() {
|
|
|
165
180
|
binanceReachable = await checkBinanceRegion();
|
|
166
181
|
}
|
|
167
182
|
const pre = readConfig();
|
|
183
|
+
noticeAgentAttachment(pre);
|
|
168
184
|
const plugin = installPlugin();
|
|
169
185
|
const merged = wireConfig(pre);
|
|
170
186
|
// After wireConfig: the config is schema-valid (flat legacy keys migrated),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reefclaw/connect",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"description": "One-command installer that connects your OpenClaw agent to ReefClaw (paper trading, no exchange keys).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -29,5 +29,8 @@
|
|
|
29
29
|
"vitest": "^4.0.18"
|
|
30
30
|
},
|
|
31
31
|
"license": "MIT",
|
|
32
|
-
"homepage": "https://reefclaw.com"
|
|
32
|
+
"homepage": "https://reefclaw.com",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"email": "support@reefclaw.com"
|
|
35
|
+
}
|
|
33
36
|
}
|