@reefclaw/openclaw-plugin 0.1.8 → 0.1.9
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/bridge/gateway/heartbeat-cron.d.ts +27 -0
- package/bridge/gateway/heartbeat-cron.js +85 -0
- package/bridge/providers/gateway.d.ts +2 -0
- package/bridge/providers/gateway.js +57 -16
- package/bridge/setup.js +6 -51
- package/index.js +2 -2
- package/openclaw.plugin.json +3 -3
- package/package.json +2 -2
- package/types.js +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const HEARTBEAT_CRON_NAME = "reefclaw-heartbeat";
|
|
2
|
+
export declare const HEARTBEAT_EVERY_MS: number;
|
|
3
|
+
export declare const HEARTBEAT_MESSAGE: string;
|
|
4
|
+
export interface CronRpcClient {
|
|
5
|
+
sendRpc(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Whether an existing cron job counts as "the heartbeat already exists".
|
|
9
|
+
* Union of the two matchers already in the codebase: the old setup path
|
|
10
|
+
* matched /reefclaw/, while resolveHeartbeatSeconds() (the cadence reader that
|
|
11
|
+
* drives the dashboard header) matches /heart\s*beat/. A job satisfying either
|
|
12
|
+
* must suppress creation — prod/wisekid boxes carry heartbeat jobs whose names
|
|
13
|
+
* predate the reefclaw- prefix, and a duplicate 15m heartbeat would double the
|
|
14
|
+
* agent's token burn.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isHeartbeatLikeName(name: unknown): boolean;
|
|
17
|
+
export type EnsureHeartbeatOutcome = 'already_ensured' | 'found_existing' | 'created' | 'failed';
|
|
18
|
+
/**
|
|
19
|
+
* Ensure a heartbeat cron exists. Non-fatal by contract: every failure path
|
|
20
|
+
* returns 'failed' rather than throwing, and only success writes the marker so
|
|
21
|
+
* a transient gateway error retries on the next boot.
|
|
22
|
+
*/
|
|
23
|
+
export declare function ensureHeartbeatCron(opts: {
|
|
24
|
+
rpc: CronRpcClient;
|
|
25
|
+
markerPath: string;
|
|
26
|
+
log: (msg: string) => void;
|
|
27
|
+
}): Promise<EnsureHeartbeatOutcome>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Ensures the ReefClaw heartbeat cron job exists — once per install — via
|
|
2
|
+
// gateway RPC (`cron.list` / `cron.add`).
|
|
3
|
+
//
|
|
4
|
+
// This replaces the old setup-time shell-out to the openclaw CLI. The CLI's
|
|
5
|
+
// cron commands are themselves gateway RPC clients, so shelling out bought
|
|
6
|
+
// nothing except a subprocess + PATH dependency — and it ran at
|
|
7
|
+
// setup time, when the gateway is typically NOT up yet (setup's printed next
|
|
8
|
+
// step is "Start your OpenClaw agent"), which is why creation silently
|
|
9
|
+
// no-op'd on fresh installs without a running gateway. The bridge holds an
|
|
10
|
+
// authenticated gateway WS by construction, so the ensure runs there instead.
|
|
11
|
+
//
|
|
12
|
+
// Once-per-install semantics: a marker file records that the ensure has run to
|
|
13
|
+
// completion. Without it, a user who deliberately deleted their heartbeat cron
|
|
14
|
+
// would get it resurrected on every bridge boot — the old setup-time path ran
|
|
15
|
+
// once, and that operator-override behaviour must be preserved.
|
|
16
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
17
|
+
import { dirname } from 'path';
|
|
18
|
+
export const HEARTBEAT_CRON_NAME = 'reefclaw-heartbeat';
|
|
19
|
+
export const HEARTBEAT_EVERY_MS = 15 * 60 * 1000;
|
|
20
|
+
// Byte-identical to the message the setup-time CLI path created, so new jobs
|
|
21
|
+
// match every existing install's job.
|
|
22
|
+
export const HEARTBEAT_MESSAGE = 'Heartbeat. Run SESSION START mandatory checks, then the full Decision Loop (Steps 0-9) from SKILL.md. ' +
|
|
23
|
+
'Check tradingMode from fetch_balance — it is your source of truth for paper vs live. ' +
|
|
24
|
+
'Use tools in parallel where possible.';
|
|
25
|
+
/**
|
|
26
|
+
* Whether an existing cron job counts as "the heartbeat already exists".
|
|
27
|
+
* Union of the two matchers already in the codebase: the old setup path
|
|
28
|
+
* matched /reefclaw/, while resolveHeartbeatSeconds() (the cadence reader that
|
|
29
|
+
* drives the dashboard header) matches /heart\s*beat/. A job satisfying either
|
|
30
|
+
* must suppress creation — prod/wisekid boxes carry heartbeat jobs whose names
|
|
31
|
+
* predate the reefclaw- prefix, and a duplicate 15m heartbeat would double the
|
|
32
|
+
* agent's token burn.
|
|
33
|
+
*/
|
|
34
|
+
export function isHeartbeatLikeName(name) {
|
|
35
|
+
return typeof name === 'string' && /reefclaw|heart\s*beat/i.test(name);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Ensure a heartbeat cron exists. Non-fatal by contract: every failure path
|
|
39
|
+
* returns 'failed' rather than throwing, and only success writes the marker so
|
|
40
|
+
* a transient gateway error retries on the next boot.
|
|
41
|
+
*/
|
|
42
|
+
export async function ensureHeartbeatCron(opts) {
|
|
43
|
+
const { rpc, markerPath, log } = opts;
|
|
44
|
+
try {
|
|
45
|
+
if (existsSync(markerPath))
|
|
46
|
+
return 'already_ensured';
|
|
47
|
+
// includeDisabled: a job the user disabled still means "exists" — re-adding
|
|
48
|
+
// a duplicate next to a deliberately-disabled one is worse than doing nothing.
|
|
49
|
+
const listResp = (await rpc.sendRpc('cron.list', { includeDisabled: true }));
|
|
50
|
+
const jobs = Array.isArray(listResp?.jobs) ? listResp.jobs : null;
|
|
51
|
+
if (!jobs) {
|
|
52
|
+
log('Heartbeat cron: skipped (cron.list returned no job list)');
|
|
53
|
+
return 'failed';
|
|
54
|
+
}
|
|
55
|
+
const existing = jobs.find((j) => isHeartbeatLikeName(j?.name));
|
|
56
|
+
if (existing) {
|
|
57
|
+
writeMarker(markerPath, { ensuredAt: new Date().toISOString(), found: String(existing.name) });
|
|
58
|
+
log(`Heartbeat cron: already exists (${String(existing.name)})`);
|
|
59
|
+
return 'found_existing';
|
|
60
|
+
}
|
|
61
|
+
// Parity with the params the CLI built for the old setup command
|
|
62
|
+
// (`openclaw cron add --name reefclaw-heartbeat --every 15m --session isolated --message ...`):
|
|
63
|
+
// isolated agentTurn defaults to delivery announce on the 'last' channel.
|
|
64
|
+
await rpc.sendRpc('cron.add', {
|
|
65
|
+
name: HEARTBEAT_CRON_NAME,
|
|
66
|
+
enabled: true,
|
|
67
|
+
schedule: { kind: 'every', everyMs: HEARTBEAT_EVERY_MS },
|
|
68
|
+
sessionTarget: 'isolated',
|
|
69
|
+
wakeMode: 'now',
|
|
70
|
+
payload: { kind: 'agentTurn', message: HEARTBEAT_MESSAGE },
|
|
71
|
+
delivery: { mode: 'announce', channel: 'last' },
|
|
72
|
+
});
|
|
73
|
+
writeMarker(markerPath, { ensuredAt: new Date().toISOString(), created: HEARTBEAT_CRON_NAME });
|
|
74
|
+
log(`Heartbeat cron: created (${HEARTBEAT_CRON_NAME}, every 15m)`);
|
|
75
|
+
return 'created';
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
log(`Heartbeat cron: skipped (${err instanceof Error ? err.message.split('\n')[0] : String(err)})`);
|
|
79
|
+
return 'failed';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function writeMarker(markerPath, data) {
|
|
83
|
+
mkdirSync(dirname(markerPath), { recursive: true });
|
|
84
|
+
writeFileSync(markerPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
85
|
+
}
|
|
@@ -29,6 +29,8 @@ export declare class GatewayProvider implements OpenClawProvider {
|
|
|
29
29
|
private agentIdentity;
|
|
30
30
|
/** Emit the one-time rollout probe (gateway response shape) only on the first attempt. */
|
|
31
31
|
private loggedAgentIdentityProbe;
|
|
32
|
+
/** One-shot latch for the boot-time heartbeat-cron ensure (re-armed on failure). */
|
|
33
|
+
private heartbeatCronEnsureStarted;
|
|
32
34
|
/** Live heartbeat cadence (seconds), read from the OpenClaw cron store; cached 60s. */
|
|
33
35
|
private heartbeatSeconds?;
|
|
34
36
|
private heartbeatReadAtMs;
|
|
@@ -11,16 +11,26 @@ import { GatewayWsClient } from '../gateway/gateway-ws-client.js';
|
|
|
11
11
|
import { discoverTools } from '../gateway/tool-discovery.js';
|
|
12
12
|
import { EventParser, mapCcxtBalance, mapCcxtOrder, extractLiquidationFields, extractBracketField, } from '../gateway/event-parser.js';
|
|
13
13
|
import { Poller } from '../gateway/poller.js';
|
|
14
|
+
import { ensureHeartbeatCron } from '../gateway/heartbeat-cron.js';
|
|
14
15
|
import { computeEquity as _computeEquity, computePositionNotional as _computePositionNotional, computeRiskMetrics as _computeRiskMetrics, DEFAULT_RISK_LIMITS, } from './risk-calculator.js';
|
|
15
16
|
import { executeKill as _executeKill, executeFlatten as _executeFlatten, executePause as _executePause, executeResume as _executeResume, } from './emergency-commands.js';
|
|
16
17
|
import { executeSetTradingMode, executeGetBracketConfig, executeSetBracketRequirement, executeSetExchangeCredentials, executeTestExchangeCredentials, executeClearExchangeCredentials, } from './onboarding-commands.js';
|
|
17
18
|
const TAG = 'gateway';
|
|
18
|
-
// ----
|
|
19
|
-
// Persists sessionStartNav per date so Day P&L
|
|
20
|
-
|
|
19
|
+
// ---- Day-start NAV persistence ----
|
|
20
|
+
// Persists sessionStartNav (the UTC-day P&L anchor) per date so Day P&L
|
|
21
|
+
// survives skill restarts. The file was historically named session-nav.json;
|
|
22
|
+
// renamed to day-start-nav.json 2026-07-22 — "session" was a misnomer (the
|
|
23
|
+
// value anchors the trading DAY, not any auth/session state) and tripped
|
|
24
|
+
// ClawHub's sensitive-file-read heuristic. Reads fall back to the legacy name
|
|
25
|
+
// once, writes go to the new name only.
|
|
26
|
+
const DAY_NAV_FILENAME = 'day-start-nav.json';
|
|
27
|
+
const LEGACY_DAY_NAV_FILENAME = 'session-nav.json';
|
|
21
28
|
const TRADING_MODE_FILENAME = 'trading-mode.json';
|
|
22
|
-
function
|
|
23
|
-
return join(homedir(), '.openclaw', 'workspace',
|
|
29
|
+
function getDayNavPath() {
|
|
30
|
+
return join(homedir(), '.openclaw', 'workspace', DAY_NAV_FILENAME);
|
|
31
|
+
}
|
|
32
|
+
function getLegacyDayNavPath() {
|
|
33
|
+
return join(homedir(), '.openclaw', 'workspace', LEGACY_DAY_NAV_FILENAME);
|
|
24
34
|
}
|
|
25
35
|
function getTradingModePath() {
|
|
26
36
|
return join(homedir(), '.openclaw', 'workspace', TRADING_MODE_FILENAME);
|
|
@@ -40,17 +50,20 @@ export function shouldAnchorSessionNav(args) {
|
|
|
40
50
|
return args.mode === 'PAPER' && args.sessionDate !== args.today; // paper midnight rollover
|
|
41
51
|
}
|
|
42
52
|
function loadSessionStartNav() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
for (const path of [getDayNavPath(), getLegacyDayNavPath()]) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = readFileSync(path, 'utf-8');
|
|
56
|
+
const data = JSON.parse(raw);
|
|
57
|
+
if (data.date === todayDateStr() && typeof data.sessionStartNav === 'number' && data.sessionStartNav > 0) {
|
|
58
|
+
return data.sessionStartNav;
|
|
59
|
+
}
|
|
60
|
+
return null; // Different day or invalid — don't let a stale legacy file shadow it
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* missing or corrupt — try the legacy name */
|
|
48
64
|
}
|
|
49
|
-
return null; // Different day or invalid
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return null; // File doesn't exist or is corrupt
|
|
53
65
|
}
|
|
66
|
+
return null;
|
|
54
67
|
}
|
|
55
68
|
/**
|
|
56
69
|
* Persisted across skill restarts so the FIRST snapshot the gateway emits
|
|
@@ -90,7 +103,11 @@ function saveSessionStartNav(nav) {
|
|
|
90
103
|
const dir = join(homedir(), '.openclaw', 'workspace');
|
|
91
104
|
mkdirSync(dir, { recursive: true });
|
|
92
105
|
const data = { date: todayDateStr(), sessionStartNav: nav };
|
|
93
|
-
writeFileSync(
|
|
106
|
+
writeFileSync(getDayNavPath(), JSON.stringify(data, null, 2), 'utf-8');
|
|
107
|
+
try {
|
|
108
|
+
unlinkSync(getLegacyDayNavPath());
|
|
109
|
+
}
|
|
110
|
+
catch { /* legacy file already gone */ }
|
|
94
111
|
}
|
|
95
112
|
catch (err) {
|
|
96
113
|
logger.warn(TAG, `Failed to save session NAV: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -122,6 +139,8 @@ export class GatewayProvider {
|
|
|
122
139
|
agentIdentity = {};
|
|
123
140
|
/** Emit the one-time rollout probe (gateway response shape) only on the first attempt. */
|
|
124
141
|
loggedAgentIdentityProbe = false;
|
|
142
|
+
/** One-shot latch for the boot-time heartbeat-cron ensure (re-armed on failure). */
|
|
143
|
+
heartbeatCronEnsureStarted = false;
|
|
125
144
|
/** Live heartbeat cadence (seconds), read from the OpenClaw cron store; cached 60s. */
|
|
126
145
|
heartbeatSeconds;
|
|
127
146
|
heartbeatReadAtMs = 0;
|
|
@@ -1070,6 +1089,24 @@ export class GatewayProvider {
|
|
|
1070
1089
|
// every (re)connect so a model swap — which restarts the gateway and drops
|
|
1071
1090
|
// this socket — is reflected on the next handshake.
|
|
1072
1091
|
void this.refreshAgentIdentity();
|
|
1092
|
+
// Ensure the heartbeat cron exists — via gateway RPC, once per install
|
|
1093
|
+
// (marker-file guarded; a deliberate operator delete is never resurrected).
|
|
1094
|
+
// Lives here instead of setup because setup runs before the gateway is up.
|
|
1095
|
+
if (!this.heartbeatCronEnsureStarted) {
|
|
1096
|
+
this.heartbeatCronEnsureStarted = true;
|
|
1097
|
+
const ws = this.wsClient;
|
|
1098
|
+
if (ws) {
|
|
1099
|
+
void ensureHeartbeatCron({
|
|
1100
|
+
rpc: ws,
|
|
1101
|
+
markerPath: join(homedir(), '.openclaw', 'workspace', 'heartbeat-cron-ensured.json'),
|
|
1102
|
+
log: (msg) => logger.info(TAG, msg),
|
|
1103
|
+
}).then((outcome) => {
|
|
1104
|
+
// A transient failure retries on the next (re)connect, not just next boot.
|
|
1105
|
+
if (outcome === 'failed')
|
|
1106
|
+
this.heartbeatCronEnsureStarted = false;
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1073
1110
|
// Start periodic agent state emission (every 5s)
|
|
1074
1111
|
if (!this.agentStateInterval) {
|
|
1075
1112
|
this.agentStateInterval = setInterval(() => {
|
|
@@ -1589,7 +1626,11 @@ export class GatewayProvider {
|
|
|
1589
1626
|
this.hasReceivedPositions = false;
|
|
1590
1627
|
this.pendingEmptyPoll = false;
|
|
1591
1628
|
try {
|
|
1592
|
-
unlinkSync(
|
|
1629
|
+
unlinkSync(getDayNavPath());
|
|
1630
|
+
}
|
|
1631
|
+
catch { /* ok if missing */ }
|
|
1632
|
+
try {
|
|
1633
|
+
unlinkSync(getLegacyDayNavPath());
|
|
1593
1634
|
}
|
|
1594
1635
|
catch { /* ok if missing */ }
|
|
1595
1636
|
logger.info(TAG, `Session start NAV reset on mode change (${prevMode} → ${this.tradingMode})`);
|
package/bridge/setup.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Interactive setup helper for the ReefClaw skill.
|
|
2
2
|
// Validates token format, tests relay connection, saves to OpenClaw config.
|
|
3
3
|
import WebSocket from 'ws';
|
|
4
|
-
import { execSync } from 'node:child_process';
|
|
5
4
|
import { writeOpenClawConfig } from './config.js';
|
|
6
5
|
import { logger } from './logger.js';
|
|
7
6
|
const TAG = 'setup';
|
|
@@ -149,8 +148,12 @@ export async function runSetup(tokenOrBundle, userId, relayUrl) {
|
|
|
149
148
|
}
|
|
150
149
|
// Save to config
|
|
151
150
|
writeOpenClawConfig(token, resolvedUserId, resolvedRelayUrl);
|
|
152
|
-
//
|
|
153
|
-
|
|
151
|
+
// Heartbeat cron is ensured by the bridge at first gateway connect (via
|
|
152
|
+
// cron.list/cron.add RPC — see gateway/heartbeat-cron.ts). Setup can't do it:
|
|
153
|
+
// the gateway isn't running yet at this point, which is also why the old
|
|
154
|
+
// setup-time shell-out to the openclaw CLI silently no-op'd on fresh
|
|
155
|
+
// installs (the CLI's cron commands are gateway RPC clients too).
|
|
156
|
+
console.log(' Heartbeat cron: created automatically when the agent first connects (every 15m)');
|
|
154
157
|
console.log('');
|
|
155
158
|
console.log(' Setup complete! Token saved to ~/.openclaw/config.json');
|
|
156
159
|
console.log('');
|
|
@@ -161,54 +164,6 @@ export async function runSetup(tokenOrBundle, userId, relayUrl) {
|
|
|
161
164
|
console.log(' 4. Your agent checks the market every 15 minutes (adjustable via chat)');
|
|
162
165
|
console.log('');
|
|
163
166
|
}
|
|
164
|
-
// ---- Heartbeat cron ----
|
|
165
|
-
/**
|
|
166
|
-
* Ensures a heartbeat cron job exists for ReefClaw.
|
|
167
|
-
* Checks `openclaw cron list` for an existing job with "reefclaw" in the name.
|
|
168
|
-
* If none found, creates one with 15m default interval.
|
|
169
|
-
* Non-fatal — if openclaw CLI isn't available or cron fails, setup still succeeds.
|
|
170
|
-
*/
|
|
171
|
-
function ensureHeartbeatCron() {
|
|
172
|
-
try {
|
|
173
|
-
// Check if a reefclaw cron already exists
|
|
174
|
-
const listOutput = execSync('openclaw cron list --json 2>/dev/null', {
|
|
175
|
-
encoding: 'utf-8',
|
|
176
|
-
timeout: 15_000,
|
|
177
|
-
});
|
|
178
|
-
// Look for any job with "reefclaw" in the name
|
|
179
|
-
try {
|
|
180
|
-
const jobs = JSON.parse(listOutput);
|
|
181
|
-
const existing = (Array.isArray(jobs) ? jobs : []).find((j) => j.name?.toLowerCase().includes('reefclaw'));
|
|
182
|
-
if (existing) {
|
|
183
|
-
console.log(` Heartbeat cron: already exists (${existing.name})`);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
// JSON parse failed — maybe not JSON output, check raw text
|
|
189
|
-
if (listOutput.toLowerCase().includes('reefclaw')) {
|
|
190
|
-
console.log(' Heartbeat cron: already exists');
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// No existing job — create one
|
|
195
|
-
console.log(' Creating heartbeat cron (every 15m)...');
|
|
196
|
-
execSync('openclaw cron add --name reefclaw-heartbeat --every 15m --session isolated ' +
|
|
197
|
-
"--message 'Heartbeat. Run SESSION START mandatory checks, then the full Decision Loop (Steps 0-9) from SKILL.md. Check tradingMode from fetch_balance — it is your source of truth for paper vs live. Use tools in parallel where possible.' 2>&1", { encoding: 'utf-8', timeout: 15_000 });
|
|
198
|
-
console.log(' Heartbeat cron: created (every 15 minutes)');
|
|
199
|
-
console.log(' Tip: Ask your agent "change heartbeat to 10 minutes" to adjust');
|
|
200
|
-
}
|
|
201
|
-
catch (err) {
|
|
202
|
-
// Non-fatal — user can create cron manually or agent can create it on first session
|
|
203
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
204
|
-
if (msg.includes('not found') || msg.includes('ENOENT')) {
|
|
205
|
-
console.log(' Heartbeat cron: skipped (openclaw CLI not in PATH)');
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
console.log(` Heartbeat cron: skipped (${msg.split('\n')[0]})`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
167
|
function printSetupInstructions() {
|
|
213
168
|
console.log(' To connect your OpenClaw agent to ReefClaw:');
|
|
214
169
|
console.log('');
|
package/index.js
CHANGED
|
@@ -863,8 +863,8 @@ let pluginToolsFactory = null;
|
|
|
863
863
|
let pluginToolNames = [];
|
|
864
864
|
const paperTradingPlugin = {
|
|
865
865
|
id: PLUGIN_ID,
|
|
866
|
-
name: 'ReefClaw
|
|
867
|
-
description: '
|
|
866
|
+
name: 'ReefClaw Trading',
|
|
867
|
+
description: 'Supervised trading plugin for the ReefClaw dashboard: paper trading with real market data (no API keys required), and optional live trading on Binance or Hyperliquid behind explicit operator opt-in, exchange API credentials, and always-on protective stop brackets.',
|
|
868
868
|
configSchema: {
|
|
869
869
|
type: 'object',
|
|
870
870
|
properties: {
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "reefclaw-paper-trading",
|
|
3
|
-
"name": "ReefClaw
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "
|
|
3
|
+
"name": "ReefClaw Trading",
|
|
4
|
+
"version": "0.1.9",
|
|
5
|
+
"description": "Supervised trading plugin for the ReefClaw dashboard: paper trading with real market data (no API keys required), and optional live trading on Binance or Hyperliquid behind explicit operator opt-in, exchange API credentials, and always-on protective stop brackets. Includes the dashboard connector bridge, heartbeat automation, and remote SKILL.md instruction updates from the ReefClaw webapp.",
|
|
6
6
|
"author": "ReefClaw",
|
|
7
7
|
"activation": {
|
|
8
8
|
"onStartup": true
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reefclaw/openclaw-plugin",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "ReefClaw trading plugin for OpenClaw \u2014 paper trading with real
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "ReefClaw supervised trading plugin for OpenClaw \u2014 paper trading with real market data, optional live trading on Binance or Hyperliquid (operator opt-in, API keys, always-on protective brackets), plus the ReefClaw dashboard connector with heartbeat automation and remote SKILL.md updates from the ReefClaw webapp. Install: /plugins install clawhub:@reefclaw/openclaw-plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"openclaw": {
|
package/types.js
CHANGED