openclawdreams 0.7.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 (188) hide show
  1. package/.env.example +14 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. package/.github/dependabot.yml +17 -0
  5. package/.github/pull_request_template.md +19 -0
  6. package/.github/workflows/build.yml +30 -0
  7. package/.github/workflows/release.yml +110 -0
  8. package/.prettierignore +4 -0
  9. package/.prettierrc +7 -0
  10. package/.versionrc.json +26 -0
  11. package/AGENTS.md +286 -0
  12. package/CHANGELOG.md +157 -0
  13. package/CODE_OF_CONDUCT.md +41 -0
  14. package/CONTRIBUTING.md +95 -0
  15. package/LICENSE +21 -0
  16. package/README.md +363 -0
  17. package/SECURITY.md +39 -0
  18. package/bin/electricsheep.ts +5 -0
  19. package/dist/bin/electricsheep.d.ts +3 -0
  20. package/dist/bin/electricsheep.d.ts.map +1 -0
  21. package/dist/bin/electricsheep.js +4 -0
  22. package/dist/bin/electricsheep.js.map +1 -0
  23. package/dist/src/budget.d.ts +28 -0
  24. package/dist/src/budget.d.ts.map +1 -0
  25. package/dist/src/budget.js +87 -0
  26. package/dist/src/budget.js.map +1 -0
  27. package/dist/src/cli.d.ts +19 -0
  28. package/dist/src/cli.d.ts.map +1 -0
  29. package/dist/src/cli.js +289 -0
  30. package/dist/src/cli.js.map +1 -0
  31. package/dist/src/config.d.ts +37 -0
  32. package/dist/src/config.d.ts.map +1 -0
  33. package/dist/src/config.js +70 -0
  34. package/dist/src/config.js.map +1 -0
  35. package/dist/src/crypto.d.ts +19 -0
  36. package/dist/src/crypto.d.ts.map +1 -0
  37. package/dist/src/crypto.js +70 -0
  38. package/dist/src/crypto.js.map +1 -0
  39. package/dist/src/dreamer.d.ts +13 -0
  40. package/dist/src/dreamer.d.ts.map +1 -0
  41. package/dist/src/dreamer.js +213 -0
  42. package/dist/src/dreamer.js.map +1 -0
  43. package/dist/src/filter.d.ts +30 -0
  44. package/dist/src/filter.d.ts.map +1 -0
  45. package/dist/src/filter.js +124 -0
  46. package/dist/src/filter.js.map +1 -0
  47. package/dist/src/identity.d.ts +29 -0
  48. package/dist/src/identity.d.ts.map +1 -0
  49. package/dist/src/identity.js +83 -0
  50. package/dist/src/identity.js.map +1 -0
  51. package/dist/src/index.d.ts +14 -0
  52. package/dist/src/index.d.ts.map +1 -0
  53. package/dist/src/index.js +293 -0
  54. package/dist/src/index.js.map +1 -0
  55. package/dist/src/llm.d.ts +26 -0
  56. package/dist/src/llm.d.ts.map +1 -0
  57. package/dist/src/llm.js +40 -0
  58. package/dist/src/llm.js.map +1 -0
  59. package/dist/src/logger.d.ts +6 -0
  60. package/dist/src/logger.d.ts.map +1 -0
  61. package/dist/src/logger.js +32 -0
  62. package/dist/src/logger.js.map +1 -0
  63. package/dist/src/memory.d.ts +41 -0
  64. package/dist/src/memory.d.ts.map +1 -0
  65. package/dist/src/memory.js +206 -0
  66. package/dist/src/memory.js.map +1 -0
  67. package/dist/src/moltbook-search.d.ts +23 -0
  68. package/dist/src/moltbook-search.d.ts.map +1 -0
  69. package/dist/src/moltbook-search.js +85 -0
  70. package/dist/src/moltbook-search.js.map +1 -0
  71. package/dist/src/moltbook.d.ts +34 -0
  72. package/dist/src/moltbook.d.ts.map +1 -0
  73. package/dist/src/moltbook.js +165 -0
  74. package/dist/src/moltbook.js.map +1 -0
  75. package/dist/src/notify.d.ts +18 -0
  76. package/dist/src/notify.d.ts.map +1 -0
  77. package/dist/src/notify.js +98 -0
  78. package/dist/src/notify.js.map +1 -0
  79. package/dist/src/persona.d.ts +26 -0
  80. package/dist/src/persona.d.ts.map +1 -0
  81. package/dist/src/persona.js +178 -0
  82. package/dist/src/persona.js.map +1 -0
  83. package/dist/src/reflection.d.ts +26 -0
  84. package/dist/src/reflection.d.ts.map +1 -0
  85. package/dist/src/reflection.js +111 -0
  86. package/dist/src/reflection.js.map +1 -0
  87. package/dist/src/state.d.ts +7 -0
  88. package/dist/src/state.d.ts.map +1 -0
  89. package/dist/src/state.js +40 -0
  90. package/dist/src/state.js.map +1 -0
  91. package/dist/src/synthesis.d.ts +29 -0
  92. package/dist/src/synthesis.d.ts.map +1 -0
  93. package/dist/src/synthesis.js +125 -0
  94. package/dist/src/synthesis.js.map +1 -0
  95. package/dist/src/topics.d.ts +19 -0
  96. package/dist/src/topics.d.ts.map +1 -0
  97. package/dist/src/topics.js +83 -0
  98. package/dist/src/topics.js.map +1 -0
  99. package/dist/src/types.d.ts +179 -0
  100. package/dist/src/types.d.ts.map +1 -0
  101. package/dist/src/types.js +5 -0
  102. package/dist/src/types.js.map +1 -0
  103. package/dist/src/waking.d.ts +24 -0
  104. package/dist/src/waking.d.ts.map +1 -0
  105. package/dist/src/waking.js +152 -0
  106. package/dist/src/waking.js.map +1 -0
  107. package/dist/src/web-search.d.ts +23 -0
  108. package/dist/src/web-search.d.ts.map +1 -0
  109. package/dist/src/web-search.js +64 -0
  110. package/dist/src/web-search.js.map +1 -0
  111. package/dist/test/budget.test.d.ts +2 -0
  112. package/dist/test/budget.test.d.ts.map +1 -0
  113. package/dist/test/budget.test.js +258 -0
  114. package/dist/test/budget.test.js.map +1 -0
  115. package/dist/test/crypto.test.d.ts +2 -0
  116. package/dist/test/crypto.test.d.ts.map +1 -0
  117. package/dist/test/crypto.test.js +93 -0
  118. package/dist/test/crypto.test.js.map +1 -0
  119. package/dist/test/dreamer.test.d.ts +2 -0
  120. package/dist/test/dreamer.test.d.ts.map +1 -0
  121. package/dist/test/dreamer.test.js +79 -0
  122. package/dist/test/dreamer.test.js.map +1 -0
  123. package/dist/test/filter.test.d.ts +2 -0
  124. package/dist/test/filter.test.d.ts.map +1 -0
  125. package/dist/test/filter.test.js +92 -0
  126. package/dist/test/filter.test.js.map +1 -0
  127. package/dist/test/memory.test.d.ts +2 -0
  128. package/dist/test/memory.test.d.ts.map +1 -0
  129. package/dist/test/memory.test.js +138 -0
  130. package/dist/test/memory.test.js.map +1 -0
  131. package/dist/test/moltbook.test.d.ts +2 -0
  132. package/dist/test/moltbook.test.d.ts.map +1 -0
  133. package/dist/test/moltbook.test.js +164 -0
  134. package/dist/test/moltbook.test.js.map +1 -0
  135. package/dist/test/persona.test.d.ts +2 -0
  136. package/dist/test/persona.test.d.ts.map +1 -0
  137. package/dist/test/persona.test.js +44 -0
  138. package/dist/test/persona.test.js.map +1 -0
  139. package/dist/test/reflection.test.d.ts +2 -0
  140. package/dist/test/reflection.test.d.ts.map +1 -0
  141. package/dist/test/reflection.test.js +57 -0
  142. package/dist/test/reflection.test.js.map +1 -0
  143. package/dist/test/state.test.d.ts +2 -0
  144. package/dist/test/state.test.d.ts.map +1 -0
  145. package/dist/test/state.test.js +50 -0
  146. package/dist/test/state.test.js.map +1 -0
  147. package/dist/test/waking.test.d.ts +2 -0
  148. package/dist/test/waking.test.d.ts.map +1 -0
  149. package/dist/test/waking.test.js +149 -0
  150. package/dist/test/waking.test.js.map +1 -0
  151. package/eslint.config.js +35 -0
  152. package/openclaw.plugin.json +62 -0
  153. package/package.json +72 -0
  154. package/skills/electricsheep.skill.md +69 -0
  155. package/skills/setup-guide/SKILL.md +303 -0
  156. package/src/budget.ts +104 -0
  157. package/src/cli.ts +325 -0
  158. package/src/config.ts +95 -0
  159. package/src/crypto.ts +82 -0
  160. package/src/dreamer.ts +283 -0
  161. package/src/filter.ts +146 -0
  162. package/src/identity.ts +92 -0
  163. package/src/index.ts +356 -0
  164. package/src/llm.ts +61 -0
  165. package/src/logger.ts +46 -0
  166. package/src/memory.ts +276 -0
  167. package/src/moltbook-search.ts +116 -0
  168. package/src/moltbook.ts +235 -0
  169. package/src/notify.ts +124 -0
  170. package/src/persona.ts +191 -0
  171. package/src/reflection.ts +150 -0
  172. package/src/state.ts +44 -0
  173. package/src/synthesis.ts +153 -0
  174. package/src/topics.ts +103 -0
  175. package/src/types.ts +196 -0
  176. package/src/waking.ts +199 -0
  177. package/src/web-search.ts +88 -0
  178. package/test/budget.test.ts +316 -0
  179. package/test/crypto.test.ts +112 -0
  180. package/test/dreamer.test.ts +95 -0
  181. package/test/filter.test.ts +115 -0
  182. package/test/memory.test.ts +182 -0
  183. package/test/moltbook.test.ts +209 -0
  184. package/test/persona.test.ts +59 -0
  185. package/test/reflection.test.ts +71 -0
  186. package/test/state.test.ts +57 -0
  187. package/test/waking.test.ts +214 -0
  188. package/tsconfig.json +20 -0
@@ -0,0 +1,303 @@
1
+ ---
2
+ name: setup-guide
3
+ description: Install and configure ElectricSheep on an OpenClaw instance
4
+ user-invocable: true
5
+ disable-model-invocation: true
6
+ metadata:
7
+ { "openclaw": { "emoji": "🔧", "requires": { "bins": ["node", "npm"] } } }
8
+ ---
9
+
10
+ # OpenClawDreams Setup Guide
11
+
12
+ You are configuring the OpenClawDreams extension on this OpenClaw instance. Follow each section in order. Run every command shown and verify the expected output before moving on.
13
+
14
+ > **Prerequisite:** The operator needs a Moltbook account. If you do not have one, create an account at https://moltbook.com. The `electricsheep register` command will handle agent registration and API key setup.
15
+
16
+ ---
17
+
18
+ ## 1. Check Prerequisites
19
+
20
+ Verify Node.js >= 24 (Active LTS) is installed:
21
+
22
+ ```bash
23
+ node --version
24
+ ```
25
+
26
+ The output must start with `v24` or higher. If not, install Node.js 24 from https://nodejs.org before continuing.
27
+
28
+ Verify this OpenClaw instance is running:
29
+
30
+ ```bash
31
+ openclaw status
32
+ ```
33
+
34
+ ---
35
+
36
+ ## 2. Clone and Build
37
+
38
+ Clone the repository and build:
39
+
40
+ ```bash
41
+ git clone https://github.com/RogueCtrl/OpenClawDreams.git
42
+ cd OpenClawDreams
43
+ npm install
44
+ npm run build
45
+ ```
46
+
47
+ Verify the build succeeded with no errors. The `dist/` directory should now exist (built output goes to `dist/src/`):
48
+
49
+ ```bash
50
+ ls dist/src/index.js
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 3. Install as OpenClaw Extension
56
+
57
+ For development (symlink — changes are picked up automatically):
58
+
59
+ ```bash
60
+ openclaw plugins install -l /path/to/OpenClawDreams
61
+ ```
62
+
63
+ For production (copies files into `~/.openclaw/extensions/electricsheep/`):
64
+
65
+ ```bash
66
+ openclaw plugins install /path/to/OpenClawDreams
67
+ ```
68
+
69
+ Replace `/path/to/OpenClawDreams` with the actual absolute path to the cloned repo.
70
+
71
+ ---
72
+
73
+ ## 4. Configure the Extension
74
+
75
+ Add the ElectricSheep plugin entry to your OpenClaw config file (`~/.openclaw/config.json5` or `config.json`):
76
+
77
+ ```json5
78
+ {
79
+ plugins: {
80
+ entries: {
81
+ "electricsheep": {
82
+ enabled: true,
83
+ config: {
84
+ // Agent identity on Moltbook
85
+ agentName: "OpenClawDreams",
86
+
87
+ // Model for AI decisions (waking engagement + dream generation)
88
+ agentModel: "claude-sonnet-4-5-20250929",
89
+
90
+ // Optional: custom data directory (defaults to ./data inside the extension)
91
+ // dataDir: "/path/to/custom/data",
92
+
93
+ // Optional: encryption key for deep memory (auto-generated on first run)
94
+ // dreamEncryptionKey: "",
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ All config fields have sensible defaults. The Moltbook API key is obtained during registration and stored automatically in `credentials.json`.
103
+
104
+ ---
105
+
106
+ ## 5. Set Environment Variables (Optional)
107
+
108
+ Optionally create a `.env` file in the OpenClawDreams directory to customize settings:
109
+
110
+ ```bash
111
+ cp .env.example .env
112
+ ```
113
+
114
+ ```bash
115
+ # Daily token budget kill switch (best-effort, resets midnight UTC)
116
+ # Default: 800000 tokens ≈ $20/day at Opus 4.5 output pricing
117
+ # Set to 0 to disable
118
+ MAX_DAILY_TOKENS=800000
119
+ ```
120
+
121
+ All LLM calls route through the OpenClaw gateway — no separate API key is needed.
122
+
123
+ ---
124
+
125
+ ## 6. Verify Installation
126
+
127
+ Check that the plugin loaded:
128
+
129
+ ```bash
130
+ openclaw plugins list
131
+ ```
132
+
133
+ Verify `electricsheep` appears as enabled. Then inspect what it registered:
134
+
135
+ ```bash
136
+ openclaw plugins info electricsheep
137
+ ```
138
+
139
+ You should see:
140
+
141
+ | Type | Count | Names |
142
+ |---|---|---|
143
+ | Tools | 5 | `electricsheep_check`, `electricsheep_dream`, `electricsheep_journal`, `electricsheep_status`, `electricsheep_memories` |
144
+ | Hooks | 2 | `before_agent_start`, `agent_end` |
145
+ | Services | 1 | `electricsheep-scheduler` |
146
+
147
+ If the plugin is not listed or shows errors, check the OpenClaw logs and verify the build completed successfully.
148
+
149
+ ---
150
+
151
+ ## 7. Test Run
152
+
153
+ Run a status check to verify connectivity:
154
+
155
+ ```bash
156
+ electricsheep status
157
+ ```
158
+
159
+ Expected output includes:
160
+ - Token budget (usage and remaining)
161
+ - Agent state (may be empty on first run)
162
+ - Working memory count (0 on first run)
163
+ - Deep memory stats (0 total on first run)
164
+ - Moltbook connection status (should show "claimed" if the agent is registered)
165
+
166
+ If Moltbook shows "not connected", verify your API key is correct.
167
+
168
+ The daytime check, dream cycle, and journal posting run automatically via the internal scheduler service. After the first daytime check runs, `electricsheep status` will show working memory entries and deep memory counts increasing.
169
+
170
+ ---
171
+
172
+ ## 8. Token Budget
173
+
174
+ ElectricSheep includes a best-effort daily token budget that halts LLM calls when the tracked total exceeds the limit. This is checked before each call, not during, so the final call that crosses the threshold will complete.
175
+
176
+ Check current budget usage:
177
+
178
+ ```bash
179
+ electricsheep status
180
+ ```
181
+
182
+ The token budget section shows used/remaining/limit for the current UTC day.
183
+
184
+ To adjust the limit, set `MAX_DAILY_TOKENS` in `.env`:
185
+
186
+ ```bash
187
+ MAX_DAILY_TOKENS=400000 # ~$10/day at Opus 4.5 output pricing
188
+ MAX_DAILY_TOKENS=0 # disable the budget entirely
189
+ ```
190
+
191
+ When the budget is exhausted, all LLM calls throw `BudgetExceededError` until midnight UTC. Non-LLM operations (memory reads, status, posting cached journals) continue to work.
192
+
193
+ **This is a safety net, not a guarantee.** Always set a spending limit on the Anthropic account as the authoritative safeguard.
194
+
195
+ ---
196
+
197
+ ## 9. Schedule
198
+
199
+ When running as an OpenClaw extension, three scheduled tasks run automatically via the internal service:
200
+
201
+ | Job | Schedule | What it does |
202
+ |---|---|---|
203
+ | Daytime check | 8am, 12pm, 4pm, 8pm | Fetch Moltbook feed, decide engagements, store memories |
204
+ | Dream cycle | 2:00 AM | Decrypt deep memories, generate dream narrative, consolidate insights |
205
+ | Morning journal | 7:00 AM | Post the latest dream to Moltbook |
206
+
207
+ All times are in the system timezone of the host machine. No system-level cron configuration is needed — the extension manages the schedule internally.
208
+
209
+ To verify the scheduler is active, check `openclaw plugins info electricsheep` and confirm the `electricsheep-scheduler` service appears.
210
+
211
+ ---
212
+
213
+ ## 10. Troubleshooting
214
+
215
+ **Build fails with native module errors:**
216
+ `better-sqlite3` requires a C++ compiler. On macOS, run `xcode-select --install`. On Linux, install `build-essential`.
217
+
218
+ **"Agent not yet claimed" during check:**
219
+ The Moltbook agent exists but hasn't been verified. The operator needs to visit their claim URL and complete the verification step on Moltbook.
220
+
221
+ **"Moltbook: not connected" in status:**
222
+ The API key is missing or invalid. Run `electricsheep register` to obtain and store a valid key.
223
+
224
+ **Node version mismatch:**
225
+ ElectricSheep requires Node.js >= 24. Run `node --version` to check. Use `nvm install 24` or download from https://nodejs.org.
226
+
227
+ **BudgetExceededError:**
228
+ The daily token budget has been reached. Wait until midnight UTC for the reset, or increase `MAX_DAILY_TOKENS` in `.env`. Set to `0` to disable the limit entirely.
229
+
230
+ **Empty feed / "Quiet day" message:**
231
+ The Moltbook feed had no posts. This is normal on a new or quiet instance. The agent stores an observation and moves on.
232
+
233
+ **Plugin not appearing in `openclaw plugins list`:**
234
+ Verify the path is correct and `npm run build` completed. Check that `openclaw.plugin.json` exists in the extension root and is valid JSON.
235
+
236
+ ---
237
+
238
+ ## 11. Uninstall
239
+
240
+ To remove ElectricSheep from an OpenClaw instance:
241
+
242
+ **1. Disable and uninstall the plugin:**
243
+
244
+ ```bash
245
+ openclaw plugins uninstall electricsheep
246
+ ```
247
+
248
+ This removes the plugin entry, deregisters all tools, hooks, and the scheduler service. OpenClaw's own memory, session transcripts, and configuration are unaffected.
249
+
250
+ **2. Remove the data directory (optional):**
251
+
252
+ The `data/` directory inside the ElectricSheep repo (or the custom path set via `dataDir`) contains all runtime state:
253
+
254
+ ```
255
+ data/
256
+ ├── memory/
257
+ │ ├── working.json # working memory entries
258
+ │ └── deep.db # encrypted deep memories (SQLite)
259
+ ├── dreams/ # saved dream journal markdown files
260
+ ├── .dream_key # AES-256 encryption key
261
+ ├── state.json # cycle state (last check, dream count, budget)
262
+ └── credentials.json # Moltbook API key
263
+ ```
264
+
265
+ To delete everything:
266
+
267
+ ```bash
268
+ rm -rf /path/to/ElectricSheep/data
269
+ ```
270
+
271
+ **Warning:** `data/.dream_key` is the encryption key for deep memories. If you delete it, the contents of `deep.db` become permanently unrecoverable. Back it up first if you want to preserve the ability to decrypt historical memories.
272
+
273
+ **3. Remove the source code (optional):**
274
+
275
+ ```bash
276
+ rm -rf /path/to/ElectricSheep
277
+ ```
278
+
279
+ **4. Clean up OpenClaw config (if needed):**
280
+
281
+ If you manually added an `electricsheep` entry to `~/.openclaw/config.json5`, remove it:
282
+
283
+ ```json5
284
+ {
285
+ plugins: {
286
+ entries: {
287
+ // delete the "electricsheep": { ... } block
288
+ }
289
+ }
290
+ }
291
+ ```
292
+
293
+ **5. Remove the Moltbook agent (optional):**
294
+
295
+ ElectricSheep does not provide a command to delete the Moltbook agent. To remove the agent profile from Moltbook, log in at https://moltbook.com and delete it from your account settings.
296
+
297
+ ---
298
+
299
+ ## Setup Complete
300
+
301
+ ElectricSheep is now installed and configured. The scheduled tasks will run automatically. The agent will check Moltbook four times during the day, dream at 2am, and post its dream journal at 7am.
302
+
303
+ Monitor the first few days via `electricsheep status` to verify memories are accumulating, dreams are generating, and the token budget is tracking correctly.
package/src/budget.ts ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Daily token budget tracker and kill switch.
3
+ *
4
+ * Tracks cumulative token usage per day in state.json.
5
+ * When the daily limit is reached, all LLM calls are refused
6
+ * until the next calendar day (UTC).
7
+ */
8
+
9
+ import { loadState, saveState } from "./state.js";
10
+ import { MAX_DAILY_TOKENS } from "./config.js";
11
+ import logger from "./logger.js";
12
+ import type { LLMClient, TokenUsage } from "./types.js";
13
+
14
+ export class BudgetExceededError extends Error {
15
+ constructor(used: number, limit: number) {
16
+ super(
17
+ `Daily token budget exceeded: ${used.toLocaleString()} / ${limit.toLocaleString()} tokens used. ` +
18
+ `Resets at midnight UTC. Override with MAX_DAILY_TOKENS env var (0 to disable).`
19
+ );
20
+ this.name = "BudgetExceededError";
21
+ }
22
+ }
23
+
24
+ function getTodayUTC(): string {
25
+ return new Date().toISOString().slice(0, 10);
26
+ }
27
+
28
+ export function getTokensUsedToday(): number {
29
+ const state = loadState();
30
+ const today = getTodayUTC();
31
+ if (state.budget_date !== today) return 0;
32
+ return (state.budget_tokens_used as number) ?? 0;
33
+ }
34
+
35
+ export function getTokensRemaining(): number {
36
+ if (MAX_DAILY_TOKENS <= 0) return Infinity;
37
+ return Math.max(0, MAX_DAILY_TOKENS - getTokensUsedToday());
38
+ }
39
+
40
+ export function getBudgetStatus(): {
41
+ enabled: boolean;
42
+ limit: number;
43
+ used: number;
44
+ remaining: number;
45
+ date: string;
46
+ } {
47
+ const used = getTokensUsedToday();
48
+ const remaining = getTokensRemaining();
49
+ return {
50
+ enabled: MAX_DAILY_TOKENS > 0,
51
+ limit: MAX_DAILY_TOKENS,
52
+ used,
53
+ remaining: MAX_DAILY_TOKENS <= 0 ? -1 : remaining,
54
+ date: getTodayUTC(),
55
+ };
56
+ }
57
+
58
+ export function recordUsage(usage: TokenUsage): void {
59
+ const state = loadState();
60
+ const today = getTodayUTC();
61
+
62
+ if (state.budget_date !== today) {
63
+ state.budget_date = today;
64
+ state.budget_tokens_used = 0;
65
+ }
66
+
67
+ const total = usage.input_tokens + usage.output_tokens;
68
+ state.budget_tokens_used = ((state.budget_tokens_used as number) ?? 0) + total;
69
+ saveState(state);
70
+ }
71
+
72
+ function checkBudget(): void {
73
+ if (MAX_DAILY_TOKENS <= 0) return;
74
+ const used = getTokensUsedToday();
75
+ if (used >= MAX_DAILY_TOKENS) {
76
+ throw new BudgetExceededError(used, MAX_DAILY_TOKENS);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Wraps an LLMClient with daily token budget enforcement.
82
+ * Checks budget before each call, records usage after.
83
+ * Returns the client unchanged if MAX_DAILY_TOKENS is 0 (disabled).
84
+ */
85
+ export function withBudget(client: LLMClient): LLMClient {
86
+ if (MAX_DAILY_TOKENS <= 0) return client;
87
+
88
+ return {
89
+ async createMessage(params) {
90
+ checkBudget();
91
+ const result = await client.createMessage(params);
92
+ if (result.usage) {
93
+ recordUsage(result.usage);
94
+ const remaining = getTokensRemaining();
95
+ logger.debug(
96
+ `Token budget: used ${result.usage.input_tokens + result.usage.output_tokens} tokens this call, ${remaining.toLocaleString()} remaining today`
97
+ );
98
+ } else {
99
+ logger.debug("Token budget: no usage data returned from LLM call");
100
+ }
101
+ return result;
102
+ },
103
+ };
104
+ }