openbroker 1.3.0 → 1.3.1

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 ADDED
@@ -0,0 +1,264 @@
1
+ ---
2
+ name: openbroker
3
+ description: Hyperliquid trading CLI skill for agents. Use when an agent needs to inspect markets/accounts, place or manage perp/spot/HIP-4 orders, or write and run Hyperliquid trading automations directly through the `openbroker` CLI without requiring the OpenClaw plugin.
4
+ license: MIT
5
+ compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
6
+ homepage: https://www.npmjs.com/package/openbroker
7
+ metadata: {"author": "monemetrics", "version": "1.3.1"}
8
+ allowed-tools: Bash(openbroker:*)
9
+ ---
10
+
11
+ # OpenBroker — Hyperliquid CLI skill
12
+
13
+ OpenBroker is first a CLI. The OpenClaw plugin is optional: use `ob_*` tools when present for common structured calls, but keep the CLI model in mind because it is the complete surface area and the safest fallback.
14
+
15
+ ## Operating rules
16
+
17
+ - For unfamiliar assets, **search before trading**. Hyperliquid has main perps, HIP-3 perps, spot markets, and HIP-4 outcomes that can share names.
18
+ - Prefer `--json` for machine-readable info commands.
19
+ - Before any write, verify the asset, account, open positions/orders, size, and whether the action should be reduce-only.
20
+ - For new or changed trading logic, start with `--dry`, inspect the plan/audit trail, then go live only when that matches the user’s intent.
21
+ - Treat CLI output as exchange state, not just prose: parse order IDs, balances, fills, and errors instead of assuming success.
22
+
23
+ ## Setup and identity
24
+
25
+ ```bash
26
+ npm install -g openbroker
27
+ openbroker setup
28
+ openbroker account --json
29
+ ```
30
+
31
+ `setup` supports:
32
+
33
+ 1. **Fresh wallet** — simplest for agents; builder fee approval is handled automatically.
34
+ 2. **Imported key** — use an existing wallet.
35
+ 3. **API wallet** — can trade but not withdraw; the human owner must approve it in a browser.
36
+
37
+ For API wallets, `HYPERLIQUID_PRIVATE_KEY` is the signing key and `HYPERLIQUID_ACCOUNT_ADDRESS` must be the funded master account. If account output shows `$0` equity unexpectedly, check that mapping first.
38
+
39
+ Common globals:
40
+
41
+ | Flag | Meaning |
42
+ |---|---|
43
+ | `-c, --config <path>` | Use a specific `.env` file |
44
+ | `--testnet` | Use testnet |
45
+ | `--dry` | Preview write commands without executing |
46
+ | `--verbose` | Debug output |
47
+ | `--json` | Machine-readable output on info commands |
48
+
49
+ ## Asset discovery and IDs
50
+
51
+ ```bash
52
+ openbroker search --query GOLD --json
53
+ openbroker search --query BTC --type perp --json
54
+ openbroker all-markets --type hip3 --json
55
+ openbroker outcomes --query BTC --json
56
+ ```
57
+
58
+ - HIP-3 perps use `dex:COIN`, e.g. `xyz:CL`, not bare `CL`.
59
+ - `assetId` is the canonical identifier for comparisons and persisted agent state; order placement still uses `--coin`.
60
+ - HIP-4 outcome orders use `--outcome <id|#encoding|+encoding>` plus `--outcome-side yes|no` when the reference is a plain ID.
61
+ - On testnet, HIP-3 metadata may need an explicit prefixed coin such as `dex:COIN`.
62
+
63
+ ## CLI command map
64
+
65
+ ### Info commands
66
+
67
+ Most info commands accept `--json`. Use `--coin`, `--top`, and `--address` where the command supports them rather than repeating bespoke parsing logic.
68
+
69
+ | Command | Main use | Distinct flags |
70
+ |---|---|---|
71
+ | `account` | Equity, margin, spot balances, positions | `--orders`, `--address` |
72
+ | `positions` | Open perp positions and liquidation distance | `--coin`, `--address` |
73
+ | `funding` | Funding rates | `--coin`, `--top`, `--sort annualized|hourly|oi`, `--all`, `--include-hip3` |
74
+ | `markets` | Perp market data | `--coin`, `--top`, `--sort volume|oi|change`, `--include-hip3` |
75
+ | `all-markets` | Browse every venue type | `--type perp|hip3|spot|outcome|all`, `--top` |
76
+ | `search` | Find markets across providers | `--query`, `--type` |
77
+ | `spot` | Spot markets or balances | `--coin`, `--balances`, `--address`, `--top` |
78
+ | `fills` | Recent fills | `--coin`, `--side buy|sell`, `--top`, `--address` |
79
+ | `orders` | Order history/open orders | `--coin`, `--status`, `--open`, `--top`, `--address` |
80
+ | `order-status` | One order by exchange/client ID | `--oid`, `--address` |
81
+ | `fees` | Fee tier and rates | `--address` |
82
+ | `candles` | OHLCV | `--coin`, `--interval`, `--bars` |
83
+ | `funding-history` | Historical funding | `--coin`, `--hours` |
84
+ | `trades` | Recent tape | `--coin`, `--top` |
85
+ | `rate-limit` | API usage | — |
86
+ | `funding-scan` | Cross-dex scan | `--threshold`, `--main-only`, `--hip3-only`, `--pairs`, `--watch`, `--interval`, `--top` |
87
+ | `outcomes` | HIP-4 discovery/balances | `--query`, `--outcome`, `--side`, `--balances`, `--top` |
88
+
89
+ ### Perp trading
90
+
91
+ Shared perp order flags:
92
+
93
+ | Flag | Meaning |
94
+ |---|---|
95
+ | `--coin <COIN>` | Main perp or HIP-3 `dex:COIN` |
96
+ | `--side buy|sell` | Required except `buy` / `sell` shortcuts |
97
+ | `--size <SIZE>` | Base-asset size |
98
+ | `--leverage <N>` | Main perps use cross; HIP-3 uses isolated |
99
+ | `--reduce` | Reduce-only; use when closing/reducing exposure |
100
+ | `--slippage <bps>` | Market/SL slippage tolerance |
101
+
102
+ | Command | Shape |
103
+ |---|---|
104
+ | `buy`, `sell` | Market shortcuts: `openbroker buy --coin ETH --size 0.1` |
105
+ | `market` | Explicit market order with `--side` |
106
+ | `limit` | Add `--price` and optional `--tif GTC|IOC|ALO` |
107
+ | `trigger` | Add `--trigger`, `--type tp|sl`, optional `--limit` |
108
+ | `tpsl` | Protect an existing position with `--tp` and/or `--sl`; accepts absolute, `%`, or `entry` forms |
109
+ | `cancel` | `--all`, `--coin`, or `--oid` |
110
+
111
+ ### Spot and HIP-4 outcome trading
112
+
113
+ | Family | Commands | Shared flags |
114
+ |---|---|---|
115
+ | Spot | `spot-buy`, `spot-sell`, `spot-order` | `--coin`, `--side`, `--size`, optional `--price`, `--tif Gtc|Ioc|Alo`, `--slippage` |
116
+ | Outcomes | `outcome-buy`, `outcome-sell`, `outcome-open`, `outcome-close`, `outcome-order` | `--outcome`, `--outcome-side`, `--side`, `--size`, optional `--price`, `--tif`, `--slippage`, `--sz-decimals` |
117
+
118
+ ### Advanced execution
119
+
120
+ | Command | Use | Distinct flags |
121
+ |---|---|---|
122
+ | `twap` | Exchange-managed TWAP | `--duration`, `--randomize`, `--reduce-only` |
123
+ | `twap-cancel` | Stop a TWAP | `--coin`, `--twap-id` |
124
+ | `twap-status` | Inspect TWAPs | `--active` |
125
+ | `scale` | Multi-level ladder | `--levels`, `--range`, `--distribution linear|exponential|flat`, `--tif` |
126
+ | `bracket` | Entry + TP + SL | `--entry market|limit`, `--price`, `--tp`, `--sl` |
127
+ | `chase` | Repriced ALO order | `--offset`, `--timeout`, `--interval`, `--max-chase` |
128
+
129
+ ## High-signal workflows
130
+
131
+ Inspect before trading:
132
+
133
+ ```bash
134
+ openbroker search --query HYPE --json
135
+ openbroker account --orders --json
136
+ openbroker positions --json
137
+ openbroker markets --coin HYPE --json
138
+ openbroker buy --coin HYPE --size 1 --dry
139
+ ```
140
+
141
+ Close rather than flip:
142
+
143
+ ```bash
144
+ openbroker positions --coin ETH --json
145
+ openbroker sell --coin ETH --size 0.1 --reduce --dry
146
+ ```
147
+
148
+ For large or passive execution, prefer `limit`, `chase`, `scale`, or `twap` over a blind market order.
149
+
150
+ ## Automations
151
+
152
+ Automations are TypeScript scripts run by the CLI:
153
+
154
+ ```bash
155
+ openbroker auto run <script> [--id <name>] [--set key=value] [--poll <ms>] [--dry]
156
+ ```
157
+
158
+ Management commands:
159
+
160
+ | Command | Use |
161
+ |---|---|
162
+ | `auto examples` | Inspect bundled examples and schemas |
163
+ | `auto run <script>` | Run a custom script by path or from `~/.openbroker/automations/` |
164
+ | `auto list` | List available automations |
165
+ | `auto status` | Show running automations |
166
+ | `auto stop <id>` | Unregister/stop an automation |
167
+ | `auto report <id>` | Summarize audit data |
168
+ | `auto clean` | Reconcile stale registry entries |
169
+ | `auto prune ...` | Delete old audit runs |
170
+
171
+ Run flags:
172
+
173
+ | Flag | Meaning |
174
+ |---|---|
175
+ | `--set key=value` | Repeatable typed config values |
176
+ | `--id <name>` | Stable automation ID |
177
+ | `--poll <ms>` | Poll interval, minimum 1000 ms |
178
+ | `--dry` | Intercept write methods |
179
+ | `--no-ws` | Disable WebSocket and rely on REST polling |
180
+ | `--allow-sleep` | Do not request OS sleep inhibition |
181
+
182
+ Bundled examples are **references, not production strategies**. Read them for API patterns, then write a purpose-built script with explicit sizing, exit logic, and failure behavior.
183
+
184
+ ### Automation API essentials
185
+
186
+ - `api.client` — full Hyperliquid client.
187
+ - `api.on(...)`, `api.every(...)`, `api.onStart(...)`, `api.onStop(...)`, `api.onError(...)`.
188
+ - `api.state` — persisted state; survives restarts.
189
+ - `api.audit.record(...)` / `api.audit.metric(...)` — durable observability.
190
+ - `api.publish(...)` — notify an OpenClaw agent when hooks are configured.
191
+ - `api.dryRun` — whether writes are intercepted.
192
+
193
+ Core events include `tick`, `price_change`, `funding_update`, `position_opened`, `position_closed`, `position_changed`, `pnl_threshold`, `margin_warning`, `order_filled`, `order_update`, and `liquidation`.
194
+
195
+ ### Monitoring and dashboard
196
+
197
+ `openbroker-monitoring` is optional but useful for long-running automations, live debugging, and post-run inspection.
198
+
199
+ ```bash
200
+ npm install openbroker-monitoring
201
+ openbroker auto run ./my-automation.ts --id my-auto
202
+ openbroker-monitoring serve --host 127.0.0.1 --port 3001
203
+ ```
204
+
205
+ - The local dashboard reads `~/.openbroker/automation-audit.sqlite` directly; it works for any standard `openbroker auto run` automation and does **not** need vault config, webhooks, or remote forwarding.
206
+ - Configure it with `OB_MONITOR_HOST`, `OB_MONITOR_PORT`, or `OPENBROKER_AUDIT_DB_PATH`, or the equivalent `serve --host/--port/--db` flags.
207
+ - When installed alongside OpenBroker, the package is convention-loaded as an audit observer. Remote forwarding is separate and only activates when `OB_DASHBOARD_URL` plus `HYPERSTABLE_VAULT_ADDRESS` or `VAULT` are configured; `OB_DASHBOARD_API_KEY` is optional.
208
+ - Start the dashboard when the user wants a live view, troubleshooting help, or ongoing monitoring. It is helpful infrastructure, not a prerequisite for every automation.
209
+
210
+ ### Hyperliquid automation design rules
211
+
212
+ These matter more than boilerplate:
213
+
214
+ 1. **Model the strategy as a state machine.** Persist flags, streaks, targets, and recovery state with `api.state`; handlers can fire repeatedly and processes can restart.
215
+ 2. **Use hysteresis, not one-print decisions.** Confirmation loops, separate enter/exit thresholds, and debounce logic prevent churn from noisy funding or tiny price moves.
216
+ 3. **Use the freshest correct signal.** For funding strategies, prefer `getPredictedFundings()` when available; if you fall back to instantaneous funding from metadata, ensure the metadata cache is refreshed so the signal does not freeze after startup.
217
+ 4. **Price the flip, not just the signal.** Before closing and later reopening a carry, compare expected hold cost with round-trip trading cost. The HYPE carry automation counts maker fees across both legs plus builder fees before deciding whether a mildly negative funding window is worth exiting.
218
+ 5. **Respect settlement timing.** If the current predicted funding is still positive, a close right before hourly settlement can be economically wrong even when the broader signal weakened. Add a settlement-proximity guard when the strategy depends on funding capture.
219
+ 6. **Sequence multi-leg hedges deliberately.** For spot-long / perp-short carry, build spot first, then short only up to spot-backed exposure; unwind spot first, then close the short reduce-only. Recover accidental one-sided exposure explicitly instead of pretending it cannot happen.
220
+ 7. **Separate strategy logic from execution policy.** Maker-first execution can reduce fees, but it needs bounded retries, post-only rejection handling, order cancellation, partial-fill accounting, minimum trade thresholds, and a defined IOC fallback. Measure progress from refreshed balances/positions, not only from submit responses.
221
+ 8. **Size from real NAV and hard caps.** Multi-leg strategies often need spot balances, spot USDC, and perp account value combined; a 50/50 carry target derived from total NAV must then be halved per side and still respect a hard per-side cap.
222
+ 9. **Define stop behavior intentionally.** On shutdown, always handle working orders, but do not blindly flatten every strategy. A hedged carry may need “preserve hedge and alert,” while a transient execution bot may need “cancel and flatten.”
223
+ 10. **Instrument first-class decisions.** Log and audit funding source, targets, leg notionals, settlement distance, hold/close decisions, fills, retries, and error paths. If using the plugin, publish events that need human attention.
224
+
225
+ Additional practical caveats:
226
+
227
+ - Positive-funding carry and negative-funding carry are not automatically symmetric. If the hedge requires short spot and the client/runtime cannot express that safely, do not invent an unhedged mirror trade.
228
+ - `funding_update` fires for many assets every poll; filter by coin early.
229
+ - Dust matters: if residual size falls below exchange precision or `minTradeUsd`, stop chasing it.
230
+ - `ALO` / post-only orders can be rejected when they would cross; treat that as an execution branch, not a surprise.
231
+ - Naked directional positions usually need explicit TP/SL or equivalent risk logic. Hedged multi-leg strategies need strategy-specific exits instead of cargo-cult TP/SL rules.
232
+ - For new automations, do a dry run, inspect `auto report`, and only then run live unless the user explicitly requested immediate live execution.
233
+
234
+ ## Plugin-aware use
235
+
236
+ When the OpenClaw plugin is available:
237
+
238
+ - Prefer `ob_*` tools for common structured reads and simple writes.
239
+ - Use `ob_watcher_status` for background monitoring state.
240
+ - Use `ob_auto_run`, `ob_auto_stop`, and `ob_auto_list` for supported automation actions.
241
+ - Fall back to the CLI for unsupported commands, debugging, richer flags, or if a tool returns empty/unexpected data.
242
+
243
+ Representative mappings:
244
+
245
+ | Plugin tool | CLI equivalent |
246
+ |---|---|
247
+ | `ob_account` | `openbroker account --json` |
248
+ | `ob_positions` | `openbroker positions --json` |
249
+ | `ob_funding` | `openbroker funding --json --include-hip3` |
250
+ | `ob_search` | `openbroker search --query <QUERY> --json` |
251
+ | `ob_buy` / `ob_sell` | `openbroker buy|sell --coin <COIN> --size <SIZE>` |
252
+ | `ob_limit` | `openbroker limit ...` |
253
+ | `ob_tpsl` | `openbroker tpsl ...` |
254
+ | `ob_auto_run` | `openbroker auto run <script> ...` |
255
+
256
+ Skill-only mode is fully usable through the CLI; the plugin adds agent tools, watcher notifications, and OpenClaw webhook integration.
257
+
258
+ ## Failure checks
259
+
260
+ - `No market data found` → search again; likely wrong venue prefix.
261
+ - `$0` equity on an API wallet → likely missing `HYPERLIQUID_ACCOUNT_ADDRESS`.
262
+ - Unexpected funding behavior → check whether you are reading predicted vs cached instantaneous data.
263
+ - Strategy churn → inspect confirmation loops, fee-aware hold logic, settlement guards, and min-trade thresholds before changing position size.
264
+ - Tool failure in plugin mode → rerun the equivalent CLI command with `--json` and `--verbose` if needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbroker",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "bin/",
16
16
  "scripts/",
17
17
  "config/example.env",
18
+ "SKILL.md",
18
19
  "README.md",
19
20
  "CHANGELOG.md"
20
21
  ],
@@ -49,7 +50,6 @@
49
50
  "dependencies": {
50
51
  "@nktkas/hyperliquid": "^0.30.3",
51
52
  "dotenv": "^17.2.3",
52
- "openbroker-monitoring": "file:../openbroker-monitoring",
53
53
  "tsx": "^4.19.0",
54
54
  "viem": "^2.21.0"
55
55
  },
@@ -34,6 +34,7 @@ Options (for run):
34
34
  --id <name> Custom automation ID (default: filename)
35
35
  --poll <ms> Poll interval in milliseconds (default: 10000)
36
36
  --no-ws Disable WebSocket; fall back to REST-only polling
37
+ --allow-sleep Do not request OS idle-sleep inhibition for this run
37
38
 
38
39
  Options (for prune):
39
40
  --older-than <d> Only prune runs started before this duration ago (e.g. 7d, 24h)
@@ -125,6 +126,7 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
125
126
  const dryRun = args.dry === true;
126
127
  const verbose = args.verbose === true;
127
128
  const useWebSocket = args['no-ws'] !== true;
129
+ const keepAwake = args['allow-sleep'] === true ? false : undefined;
128
130
  const pollIntervalMs = args.poll ? parseInt(String(args.poll), 10) : undefined;
129
131
  const id = args.id ? String(args.id) : undefined;
130
132
 
@@ -150,6 +152,7 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
150
152
  verbose,
151
153
  pollIntervalMs,
152
154
  useWebSocket,
155
+ keepAwake,
153
156
  initialState: Object.keys(initialState).length > 0 ? initialState : undefined,
154
157
  hooksToken: envHooksToken,
155
158
  gatewayPort: envGatewayPort && !isNaN(envGatewayPort) ? envGatewayPort : undefined,
@@ -0,0 +1,100 @@
1
+ import { spawn, type ChildProcess } from 'child_process';
2
+
3
+ type KeepAwakeLogger = {
4
+ info(message: string): void;
5
+ warn(message: string): void;
6
+ };
7
+
8
+ export interface KeepAwakeHandle {
9
+ backend: string;
10
+ stop(): void;
11
+ }
12
+
13
+ function createHandle(
14
+ child: ChildProcess,
15
+ backend: string,
16
+ log: KeepAwakeLogger,
17
+ ): KeepAwakeHandle {
18
+ let stopping = false;
19
+
20
+ child.once('error', (error) => {
21
+ if (!stopping) {
22
+ log.warn(`keep-awake unavailable via ${backend}: ${error.message}`);
23
+ }
24
+ });
25
+
26
+ child.once('exit', (code, signal) => {
27
+ if (!stopping) {
28
+ const suffix = signal ? `signal ${signal}` : `code ${code ?? 'unknown'}`;
29
+ log.warn(`keep-awake helper ${backend} exited unexpectedly (${suffix}); host may sleep.`);
30
+ }
31
+ });
32
+
33
+ return {
34
+ backend,
35
+ stop() {
36
+ stopping = true;
37
+ if (!child.killed) child.kill();
38
+ },
39
+ };
40
+ }
41
+
42
+ function startDarwin(log: KeepAwakeLogger): KeepAwakeHandle {
43
+ const child = spawn('caffeinate', ['-i', '-w', String(process.pid)], {
44
+ stdio: 'ignore',
45
+ });
46
+ return createHandle(child, 'caffeinate', log);
47
+ }
48
+
49
+ function startLinux(reason: string, log: KeepAwakeLogger): KeepAwakeHandle {
50
+ const child = spawn(
51
+ 'systemd-inhibit',
52
+ [
53
+ '--what=sleep',
54
+ '--who=OpenBroker',
55
+ `--why=${reason}`,
56
+ '--mode=block',
57
+ 'sh',
58
+ '-c',
59
+ 'while kill -0 "$1" 2>/dev/null; do sleep 30; done',
60
+ 'sh',
61
+ String(process.pid),
62
+ ],
63
+ { stdio: 'ignore' },
64
+ );
65
+ return createHandle(child, 'systemd-inhibit', log);
66
+ }
67
+
68
+ function startWindows(log: KeepAwakeLogger): KeepAwakeHandle {
69
+ const script = [
70
+ 'Add-Type -Namespace OpenBroker -Name Native -MemberDefinition',
71
+ '\'"[DllImport(\\\"kernel32.dll\\\")] public static extern uint SetThreadExecutionState(uint esFlags);"\';',
72
+ '[OpenBroker.Native]::SetThreadExecutionState(0x80000001) | Out-Null;',
73
+ `Wait-Process -Id ${process.pid};`,
74
+ '[OpenBroker.Native]::SetThreadExecutionState(0x80000000) | Out-Null;',
75
+ ].join(' ');
76
+
77
+ const child = spawn(
78
+ 'powershell.exe',
79
+ ['-NoProfile', '-NonInteractive', '-Command', script],
80
+ {
81
+ stdio: 'ignore',
82
+ windowsHide: true,
83
+ },
84
+ );
85
+ return createHandle(child, 'SetThreadExecutionState', log);
86
+ }
87
+
88
+ export function startKeepAwake(reason: string, log: KeepAwakeLogger): KeepAwakeHandle | null {
89
+ switch (process.platform) {
90
+ case 'darwin':
91
+ return startDarwin(log);
92
+ case 'linux':
93
+ return startLinux(reason, log);
94
+ case 'win32':
95
+ return startWindows(log);
96
+ default:
97
+ log.warn(`keep-awake is not supported on platform ${process.platform}; host may sleep.`);
98
+ return null;
99
+ }
100
+ }
@@ -15,6 +15,7 @@ import { AutomationEventBus } from './events.js';
15
15
  import { loadAutomation } from './loader.js';
16
16
  import { registerAutomation, unregisterAutomation, getRegisteredAutomations as getRegisteredFromFile } from './registry.js';
17
17
  import { createAutomationAudit, toSerializable, type AutomationAuditSink } from './audit.js';
18
+ import { startKeepAwake, type KeepAwakeHandle } from './keep-awake.js';
18
19
  import type {
19
20
  AutomationAPI,
20
21
  AutomationAuditObserver,
@@ -451,6 +452,11 @@ export interface RuntimeOptions {
451
452
  * @default true
452
453
  */
453
454
  useWebSocket?: boolean;
455
+ /**
456
+ * Best-effort host idle-sleep inhibition for the lifetime of the automation.
457
+ * Enabled by default for live runs and disabled by default for dry runs.
458
+ */
459
+ keepAwake?: boolean;
454
460
  }
455
461
 
456
462
  /** Registry of all running automations */
@@ -516,6 +522,14 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
516
522
  stateController.attachAudit(audit);
517
523
 
518
524
  const log = createLogger(id, verbose, audit);
525
+ const keepAwakeEnabled = options.keepAwake ?? !dryRun;
526
+ let keepAwake: KeepAwakeHandle | null = null;
527
+ if (keepAwakeEnabled) {
528
+ keepAwake = startKeepAwake(`OpenBroker automation ${id} is running`, log);
529
+ if (keepAwake) {
530
+ log.info(`keep-awake enabled via ${keepAwake.backend}.`);
531
+ }
532
+ }
519
533
  const observers = await loadConventionObservers(log);
520
534
  const baseClient = dryRun ? createDryClient(rawClient, log) : rawClient;
521
535
  const client = createAuditedClient(baseClient, audit, dryRun, observers);
@@ -576,6 +590,7 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
576
590
  pollCount: 0,
577
591
  eventsEmitted: 0,
578
592
  });
593
+ keepAwake?.stop();
579
594
  throw error;
580
595
  }
581
596
 
@@ -949,6 +964,8 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
949
964
  }
950
965
  }
951
966
 
967
+ keepAwake?.stop();
968
+
952
969
  eventBus.removeAll();
953
970
  registry.delete(id);
954
971