openbroker 1.0.80 → 1.0.82

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/SKILL.md CHANGED
@@ -4,7 +4,7 @@ description: Hyperliquid trading plugin with background position monitoring and
4
4
  license: MIT
5
5
  compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
6
6
  homepage: https://www.npmjs.com/package/openbroker
7
- metadata: {"author": "monemetrics", "version": "1.0.80", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker", "bins": ["openbroker"], "label": "Install openbroker (npm)"}]}}
7
+ metadata: {"author": "monemetrics", "version": "1.0.82", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker", "bins": ["openbroker"], "label": "Install openbroker (npm)"}]}}
8
8
  allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot ob_fills ob_orders ob_order_status ob_fees ob_candles ob_funding_history ob_trades ob_rate_limit ob_funding_scan ob_buy ob_sell ob_limit ob_trigger ob_tpsl ob_cancel ob_spot_buy ob_spot_sell ob_twap ob_twap_cancel ob_twap_status ob_bracket ob_chase ob_watcher_status ob_auto_run ob_auto_stop ob_auto_list Bash(openbroker:*)
9
9
  ---
10
10
 
package/bin/cli.ts CHANGED
@@ -112,6 +112,8 @@ Automations:
112
112
  auto status Show running automations
113
113
 
114
114
  Options:
115
+ -c, --config <path> Use a specific .env config file
116
+ --testnet Use testnet
115
117
  --help, -h Show help for a command
116
118
  --dry Preview without executing
117
119
  --verbose Show debug output
@@ -134,11 +136,30 @@ Documentation: https://github.com/aurracloud/open-broker
134
136
  function runScript(scriptPath: string, args: string[]) {
135
137
  const fullPath = path.join(scriptsDir, scriptPath);
136
138
 
139
+ // Handle global flags: set env vars and strip from args
140
+ const env = { ...process.env };
141
+ const testnetIdx = args.indexOf('--testnet');
142
+ if (testnetIdx !== -1) {
143
+ env.HYPERLIQUID_NETWORK = 'testnet';
144
+ args = [...args.slice(0, testnetIdx), ...args.slice(testnetIdx + 1)];
145
+ }
146
+
147
+ // Handle -c / --config flag: load the specified .env file
148
+ // Resolve relative to the user's original working directory (not the package root)
149
+ let configIdx = args.indexOf('-c');
150
+ if (configIdx === -1) configIdx = args.indexOf('--config');
151
+ if (configIdx !== -1 && args[configIdx + 1]) {
152
+ const originalCwd = process.env.OPENBROKER_CWD || process.cwd();
153
+ const configPath = path.resolve(originalCwd, args[configIdx + 1]);
154
+ env.OPENBROKER_CONFIG = configPath;
155
+ args = [...args.slice(0, configIdx), ...args.slice(configIdx + 2)];
156
+ }
157
+
137
158
  // Use tsx to run TypeScript directly
138
159
  const child = spawn('npx', ['tsx', fullPath, ...args], {
139
160
  stdio: 'inherit',
140
161
  cwd: path.resolve(__dirname, '..'),
141
- env: { ...process.env },
162
+ env,
142
163
  });
143
164
 
144
165
  child.on('error', (err) => {
package/bin/openbroker.js CHANGED
@@ -56,6 +56,10 @@ child.on('error', (err) => {
56
56
  }
57
57
  });
58
58
 
59
+ // Let the child handle SIGINT (Ctrl+C) — don't exit the wrapper early
60
+ process.on('SIGINT', () => {});
61
+ process.on('SIGTERM', () => { child.kill('SIGTERM'); });
62
+
59
63
  child.on('exit', (code) => {
60
64
  process.exit(code ?? 0);
61
65
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openbroker",
3
3
  "name": "OpenBroker — Hyperliquid Trading",
4
- "version": "1.0.80",
4
+ "version": "1.0.82",
5
5
  "description": "Trade on Hyperliquid DEX with background position monitoring",
6
6
  "configSchema": {
7
7
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbroker",
3
- "version": "1.0.80",
3
+ "version": "1.0.82",
4
4
  "description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -71,11 +71,13 @@ function parseSetFlags(rawArgs: string[]): Record<string, unknown> {
71
71
  }
72
72
  const key = pair.slice(0, eqIdx);
73
73
  const raw = pair.slice(eqIdx + 1);
74
+ const isHexLike = /^0x[0-9a-fA-F]+$/.test(raw);
75
+ const isDecimalLike = /^-?(?:\d+|\d+\.\d+|\.\d+)$/.test(raw);
74
76
 
75
77
  // Auto-parse numbers and booleans
76
78
  if (raw === 'true') config[key] = true;
77
79
  else if (raw === 'false') config[key] = false;
78
- else if (raw !== '' && !isNaN(Number(raw))) config[key] = Number(raw);
80
+ else if (!isHexLike && isDecimalLike) config[key] = Number(raw);
79
81
  else config[key] = raw;
80
82
 
81
83
  i++; // skip the value
@@ -116,6 +118,12 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
116
118
  const pollIntervalMs = args.poll ? parseInt(String(args.poll), 10) : undefined;
117
119
  const id = args.id ? String(args.id) : undefined;
118
120
 
121
+ if (args.testnet === true) {
122
+ process.env.HYPERLIQUID_NETWORK = 'testnet';
123
+ } else if (args.mainnet === true) {
124
+ process.env.HYPERLIQUID_NETWORK = 'mainnet';
125
+ }
126
+
119
127
  if (pollIntervalMs !== undefined && (isNaN(pollIntervalMs) || pollIntervalMs < 1000)) {
120
128
  console.error('Error: --poll must be at least 1000ms');
121
129
  process.exit(1);
@@ -70,8 +70,9 @@ export async function loadExampleConfigs(): Promise<Record<string, AutomationCon
70
70
  for (const example of examples) {
71
71
  try {
72
72
  const mod = await import(example.path);
73
- if (mod.config && typeof mod.config === 'object' && mod.config.description) {
74
- configs[example.name] = mod.config as AutomationConfig;
73
+ const config = resolveAutomationConfig(mod);
74
+ if (config && typeof config === 'object' && config.description) {
75
+ configs[example.name] = config;
75
76
  }
76
77
  } catch {
77
78
  // Skip examples that fail to load
@@ -81,6 +82,39 @@ export async function loadExampleConfigs(): Promise<Record<string, AutomationCon
81
82
  return configs;
82
83
  }
83
84
 
85
+ function resolveAutomationFactory(mod: Record<string, unknown>): AutomationFactory | null {
86
+ const candidates = [
87
+ mod.default,
88
+ (mod.default as Record<string, unknown> | undefined)?.default,
89
+ mod["module.exports"],
90
+ ((mod["module.exports"] as Record<string, unknown> | undefined)?.default)
91
+ ];
92
+
93
+ for (const candidate of candidates) {
94
+ if (typeof candidate === "function") {
95
+ return candidate as AutomationFactory;
96
+ }
97
+ }
98
+
99
+ return null;
100
+ }
101
+
102
+ function resolveAutomationConfig(mod: Record<string, unknown>): AutomationConfig | null {
103
+ const candidates = [
104
+ mod.config,
105
+ (mod.default as Record<string, unknown> | undefined)?.config,
106
+ (mod["module.exports"] as Record<string, unknown> | undefined)?.config
107
+ ];
108
+
109
+ for (const candidate of candidates) {
110
+ if (candidate && typeof candidate === "object" && "description" in candidate) {
111
+ return candidate as AutomationConfig;
112
+ }
113
+ }
114
+
115
+ return null;
116
+ }
117
+
84
118
  /** Load an automation module and validate the default export */
85
119
  export async function loadAutomation(scriptPath: string): Promise<AutomationFactory> {
86
120
  const absolutePath = path.resolve(scriptPath);
@@ -88,7 +122,7 @@ export async function loadAutomation(scriptPath: string): Promise<AutomationFact
88
122
  // Dynamic import — tsx handles TypeScript transpilation
89
123
  const mod = await import(absolutePath);
90
124
 
91
- const factory = mod.default;
125
+ const factory = resolveAutomationFactory(mod as Record<string, unknown>);
92
126
  if (typeof factory !== 'function') {
93
127
  throw new Error(
94
128
  `Automation script must export a default function.\n` +
@@ -388,12 +388,10 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
388
388
 
389
389
  const stateController = createState(id);
390
390
 
391
- // Pre-seed state from --set flags (doesn't overwrite already-persisted keys)
391
+ // Apply --set flags before the factory function runs so CLI overrides win over persisted state.
392
392
  if (initialState) {
393
393
  for (const [key, value] of Object.entries(initialState)) {
394
- if (stateController.state.get(key) === undefined) {
395
- stateController.state.set(key, value);
396
- }
394
+ stateController.state.set(key, value);
397
395
  }
398
396
  }
399
397
 
@@ -626,6 +624,9 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
626
624
  } catch (err) {
627
625
  const error = err instanceof Error ? err : new Error(String(err));
628
626
  audit.recordError('websocket_setup', error);
627
+ if (verbose && error.stack) {
628
+ log.debug(`WebSocket setup stack: ${error.stack}`);
629
+ }
629
630
  log.warn(`WebSocket setup failed: ${error.message} — using REST polling only`);
630
631
  ws = null;
631
632
  wsConnected = false;