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
package/src/cli.ts ADDED
@@ -0,0 +1,325 @@
1
+ /**
2
+ * ElectricSheep CLI.
3
+ *
4
+ * Provides utility commands for inspecting agent state.
5
+ * Core agent behavior (check, dream, journal) runs via OpenClaw.
6
+ *
7
+ * Usage:
8
+ * electricsheep register --name "Name" --description "Bio"
9
+ * electricsheep status # show agent status and memory stats
10
+ * electricsheep dreams # list saved dream journals
11
+ */
12
+
13
+ import { Command } from "commander";
14
+ import chalk from "chalk";
15
+ import { readdirSync, readFileSync } from "node:fs";
16
+ import { resolve } from "node:path";
17
+ import { setVerbose } from "./logger.js";
18
+ import { DREAMS_DIR } from "./config.js";
19
+ import type { AgentState, DeepMemoryStats, OpenClawAPI } from "./types.js";
20
+
21
+ /**
22
+ * Register all ElectricSheep subcommands onto a parent Command.
23
+ * Used both by the standalone bin and by api.registerCli().
24
+ */
25
+ export function registerCommands(parent: Command): void {
26
+ parent
27
+ .option("-v, --verbose", "Enable verbose logging")
28
+ .hook("preAction", (thisCommand) => {
29
+ const opts = thisCommand.opts();
30
+ if (opts.verbose) setVerbose(true);
31
+ });
32
+
33
+ parent
34
+ .command("register")
35
+ .description("Register a new agent on Moltbook")
36
+ .requiredOption("--name <name>", "Agent name on Moltbook")
37
+ .requiredOption("--description <desc>", "Agent description")
38
+ .action(async (opts: { name: string; description: string }) => {
39
+ const { MoltbookClient } = await import("./moltbook.js");
40
+ const client = new MoltbookClient();
41
+ const result = await client.register(opts.name, opts.description);
42
+
43
+ const agent = (result.agent ?? result) as Record<string, string>;
44
+ console.log(chalk.green.bold("\nRegistered!\n"));
45
+ console.log(`${chalk.bold("API Key:")} ${agent.api_key ?? "?"}`);
46
+ console.log(`${chalk.bold("Claim URL:")} ${agent.claim_url ?? "?"}`);
47
+ console.log(`${chalk.bold("Verification:")} ${agent.verification_code ?? "?"}`);
48
+ console.log(
49
+ chalk.yellow("\nYour API key has been saved to credentials.json automatically")
50
+ );
51
+ console.log(chalk.yellow("Visit the claim URL and post the verification tweet"));
52
+ });
53
+
54
+ parent
55
+ .command("status")
56
+ .description("Show agent status, memory stats, and recent state")
57
+ .action(async () => {
58
+ const { deepMemoryStats } = await import("./memory.js");
59
+ const { loadState } = await import("./state.js");
60
+ const { MoltbookClient } = await import("./moltbook.js");
61
+ const { getBudgetStatus } = await import("./budget.js");
62
+
63
+ const state: AgentState = loadState();
64
+ const memStats: DeepMemoryStats = deepMemoryStats();
65
+ const budget = getBudgetStatus();
66
+
67
+ console.log(chalk.cyan.bold("\nElectricSheep Status\n"));
68
+
69
+ // Token budget
70
+ if (budget.enabled) {
71
+ const pct = Math.round((budget.used / budget.limit) * 100);
72
+ const color = pct >= 90 ? chalk.red : pct >= 70 ? chalk.yellow : chalk.green;
73
+ console.log(chalk.bold("Token Budget:"));
74
+ console.log(
75
+ ` ${color(`${budget.used.toLocaleString()} / ${budget.limit.toLocaleString()} tokens (${pct}%)`)}` +
76
+ ` ${chalk.dim(`remaining: ${budget.remaining.toLocaleString()}`)}`
77
+ );
78
+ console.log(` ${chalk.dim(`date: ${budget.date} UTC`)}`);
79
+ } else {
80
+ console.log(chalk.bold("Token Budget:") + chalk.dim(" disabled"));
81
+ }
82
+
83
+ // State
84
+ console.log(`\n${chalk.bold("Agent State:")}`);
85
+ for (const [k, v] of Object.entries(state)) {
86
+ if (k.startsWith("budget_")) continue;
87
+ console.log(` ${chalk.bold(k)}: ${String(v)}`);
88
+ }
89
+
90
+ // Memory stats
91
+ console.log(
92
+ `${chalk.bold("Deep Memory:")} ${memStats.total_memories} total, ${memStats.undreamed} undreamed`
93
+ );
94
+ if (Object.keys(memStats.categories).length > 0) {
95
+ console.log(
96
+ `${chalk.bold("Categories:")} ${JSON.stringify(memStats.categories)}`
97
+ );
98
+ }
99
+
100
+ // Moltbook status
101
+ try {
102
+ const client = new MoltbookClient();
103
+ const moltbookStatus = await client.status();
104
+ console.log(
105
+ `\n${chalk.bold("Moltbook:")} ${(moltbookStatus as Record<string, unknown>).status ?? "?"}`
106
+ );
107
+ const profile = await client.me();
108
+ const agent = (profile.agent ?? profile) as Record<string, unknown>;
109
+ console.log(`${chalk.bold("Karma:")} ${agent.karma ?? 0}`);
110
+ } catch {
111
+ console.log(chalk.yellow("\nMoltbook: not connected"));
112
+ }
113
+ });
114
+
115
+ parent
116
+ .command("dreams")
117
+ .description("List saved dream journals")
118
+ .action(() => {
119
+ let dreamFiles: string[];
120
+ try {
121
+ dreamFiles = readdirSync(DREAMS_DIR)
122
+ .filter((f) => f.endsWith(".md"))
123
+ .sort()
124
+ .reverse();
125
+ } catch {
126
+ dreamFiles = [];
127
+ }
128
+
129
+ if (dreamFiles.length === 0) {
130
+ console.log(
131
+ chalk.dim(
132
+ "No dreams yet. The dream cycle runs automatically via OpenClaw cron."
133
+ )
134
+ );
135
+ return;
136
+ }
137
+
138
+ console.log(chalk.magenta.bold(`\nDream Archive (${dreamFiles.length} dreams)\n`));
139
+
140
+ for (const f of dreamFiles.slice(0, 20)) {
141
+ const content = readFileSync(resolve(DREAMS_DIR, f), "utf-8");
142
+ const firstLine = content.split("\n")[0].replace(/^#\s*/, "");
143
+ const stem = f.replace(/\.md$/, "").slice(0, 10);
144
+ console.log(` ${chalk.dim(stem)} ${firstLine}`);
145
+ }
146
+ });
147
+
148
+ // --- Shared helper: creates a direct Anthropic LLM client for CLI commands ---
149
+ async function createDirectClient() {
150
+ const { withBudget } = await import("./budget.js");
151
+ const { AGENT_MODEL } = await import("./config.js");
152
+
153
+ let apiKey: string | undefined;
154
+ try {
155
+ const { readFileSync: readFs } = await import("fs");
156
+ const { join: joinPath } = await import("path");
157
+ const { homedir } = await import("os");
158
+ const candidates = [
159
+ joinPath(homedir(), ".openclaw", "agents", "main", "agent", "auth-profiles.json"),
160
+ joinPath(homedir(), ".openclaw", "agents", "default", "auth-profiles.json"),
161
+ joinPath(homedir(), ".openclaw", "auth-profiles.json"),
162
+ ];
163
+ for (const p of candidates) {
164
+ try {
165
+ const raw = JSON.parse(readFs(p, "utf-8"));
166
+ const profiles = raw.profiles || {};
167
+ for (const profile of Object.values(profiles) as Record<string, unknown>[]) {
168
+ if (profile.provider === "anthropic") {
169
+ apiKey =
170
+ String(profile.key || profile.token || profile.apiKey || "") || undefined;
171
+ if (apiKey) break;
172
+ }
173
+ }
174
+ if (apiKey) break;
175
+ } catch {
176
+ /* try next */
177
+ }
178
+ }
179
+ } catch {
180
+ /* ignore */
181
+ }
182
+ if (!apiKey) apiKey = process.env.ANTHROPIC_API_KEY;
183
+ if (!apiKey) {
184
+ console.error(
185
+ chalk.red(
186
+ "No Anthropic API key found. Set ANTHROPIC_API_KEY or configure via openclaw."
187
+ )
188
+ );
189
+ process.exit(1);
190
+ }
191
+
192
+ const client = withBudget({
193
+ async createMessage(params) {
194
+ const resp = await fetch("https://api.anthropic.com/v1/messages", {
195
+ method: "POST",
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ "x-api-key": apiKey!,
199
+ "anthropic-version": "2023-06-01",
200
+ },
201
+ body: JSON.stringify({
202
+ model: params.model || AGENT_MODEL,
203
+ max_tokens: params.maxTokens,
204
+ system: params.system,
205
+ messages: params.messages,
206
+ }),
207
+ });
208
+ if (!resp.ok) {
209
+ const body = await resp.text();
210
+ throw new Error(`Anthropic API error ${resp.status}: ${body}`);
211
+ }
212
+ const data = (await resp.json()) as Record<string, unknown>;
213
+ const contentArr = data.content as Array<{ text?: string }> | undefined;
214
+ const text =
215
+ contentArr?.[0]?.text ?? contentArr?.map((c) => c.text).join("") ?? "";
216
+ return {
217
+ text,
218
+ usage: data.usage
219
+ ? {
220
+ input_tokens: (data.usage as Record<string, number>).input_tokens ?? 0,
221
+ output_tokens: (data.usage as Record<string, number>).output_tokens ?? 0,
222
+ }
223
+ : undefined,
224
+ };
225
+ },
226
+ });
227
+
228
+ const minimalApi = {
229
+ registerTool: () => {},
230
+ registerCli: () => {},
231
+ registerHook: () => {},
232
+ registerService: () => {},
233
+ registerGatewayMethod: () => {},
234
+ runtime: { subagent: {} } as OpenClawAPI["runtime"],
235
+ memory: undefined,
236
+ logger: {
237
+ info: (msg: string) => console.log(chalk.dim(msg)),
238
+ warn: (msg: string) => console.log(chalk.yellow(msg)),
239
+ error: (msg: string) => console.error(chalk.red(msg)),
240
+ },
241
+ };
242
+
243
+ return { client, api: minimalApi as unknown as OpenClawAPI };
244
+ }
245
+
246
+ parent
247
+ .command("reflect")
248
+ .description("Manually trigger the reflection and synthesis cycle")
249
+ .action(async () => {
250
+ console.log(chalk.cyan.bold("\nTriggering reflection cycle...\n"));
251
+ const { runReflectionCycle } = await import("./waking.js");
252
+ const { client, api } = await createDirectClient();
253
+ try {
254
+ await runReflectionCycle(client, api);
255
+ console.log(chalk.green.bold("\nReflection cycle complete.\n"));
256
+ } catch (err: unknown) {
257
+ const msg = err instanceof Error ? err.message : String(err);
258
+ console.error(chalk.red(`\nReflection failed: ${msg}\n`));
259
+ process.exit(1);
260
+ }
261
+ });
262
+
263
+ parent
264
+ .command("dream")
265
+ .description(
266
+ "Manually trigger the dream cycle: consolidate memories into a dream narrative"
267
+ )
268
+ .action(async () => {
269
+ console.log(chalk.magenta.bold("\nTriggering dream cycle...\n"));
270
+ const { runDreamCycle } = await import("./dreamer.js");
271
+ const { client } = await createDirectClient();
272
+ try {
273
+ const dream = await runDreamCycle(client);
274
+ if (dream) {
275
+ console.log(chalk.green.bold("\nDream cycle complete.\n"));
276
+ } else {
277
+ console.log(chalk.yellow("\nNo undreamed memories. Dreamless night.\n"));
278
+ }
279
+ } catch (err: unknown) {
280
+ const msg = err instanceof Error ? err.message : String(err);
281
+ console.error(chalk.red(`\nDream cycle failed: ${msg}\n`));
282
+ process.exit(1);
283
+ }
284
+ });
285
+
286
+ parent
287
+ .command("post")
288
+ .description(
289
+ "Manually trigger a Moltbook post from the latest dream (requires moltbookEnabled)"
290
+ )
291
+ .action(async () => {
292
+ console.log(chalk.blue.bold("\nTriggering Moltbook post...\n"));
293
+ const { postDreamJournal, loadLatestDream } = await import("./dreamer.js");
294
+ const { client } = await createDirectClient();
295
+
296
+ // Show what will be posted
297
+ const latestDream = loadLatestDream();
298
+ if (latestDream) {
299
+ const title =
300
+ latestDream.markdown.split("\n")[0].replace(/^#\s*/, "") || "Untitled Dream";
301
+ const preview = latestDream.markdown
302
+ .split("\n")
303
+ .slice(1)
304
+ .join(" ")
305
+ .trim()
306
+ .slice(0, 200);
307
+ console.log(chalk.magenta(` Dream: ${title}`));
308
+ console.log(chalk.dim(` ${preview}...\n`));
309
+ }
310
+
311
+ try {
312
+ await postDreamJournal(client, undefined, { force: true });
313
+ console.log(chalk.green.bold("\nPost cycle complete.\n"));
314
+ } catch (err: unknown) {
315
+ const msg = err instanceof Error ? err.message : String(err);
316
+ console.error(chalk.red(`\nPost cycle failed: ${msg}\n`));
317
+ process.exit(1);
318
+ }
319
+ });
320
+ } // end registerCommands
321
+
322
+ // Standalone bin entry point
323
+ export const program = new Command();
324
+ program.name("electricsheep").description("ElectricSheep — an AI agent that dreams.");
325
+ registerCommands(program);
package/src/config.ts ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Configuration management.
3
+ */
4
+
5
+ import { config } from "dotenv";
6
+ import { mkdirSync } from "node:fs";
7
+ import { dirname, resolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ config({ quiet: true });
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ // Paths
16
+ export const BASE_DIR = resolve(
17
+ process.env.ELECTRICSHEEP_DATA_DIR || resolve(__dirname, "..", "..")
18
+ );
19
+ export const DATA_DIR = resolve(BASE_DIR, "data");
20
+ export const MEMORY_DIR = resolve(DATA_DIR, "memory");
21
+ export const DREAMS_DIR = resolve(DATA_DIR, "dreams");
22
+ export const CREDENTIALS_FILE = resolve(DATA_DIR, "credentials.json");
23
+
24
+ // Ensure directories exist
25
+ for (const dir of [DATA_DIR, MEMORY_DIR, DREAMS_DIR]) {
26
+ mkdirSync(dir, { recursive: true });
27
+ }
28
+
29
+ // Agent
30
+ export const AGENT_NAME = process.env.AGENT_NAME ?? "ElectricSheep";
31
+ export const AGENT_MODEL = process.env.AGENT_MODEL ?? "claude-sonnet-4-5-20250929";
32
+
33
+ // Moltbook
34
+ export const MOLTBOOK_BASE_URL = "https://www.moltbook.com/api/v1";
35
+ export const MOLTBOOK_ENABLED =
36
+ (process.env.MOLTBOOK_ENABLED ?? "false").toLowerCase() === "true";
37
+
38
+ // Web Search
39
+ export const WEB_SEARCH_ENABLED =
40
+ (process.env.WEB_SEARCH_ENABLED ?? "true").toLowerCase() !== "false";
41
+
42
+ // Operator Notifications
43
+ export const NOTIFICATION_CHANNEL = process.env.NOTIFICATION_CHANNEL ?? "";
44
+ export const NOTIFY_OPERATOR_ON_DREAM =
45
+ (process.env.NOTIFY_OPERATOR_ON_DREAM ?? "true").toLowerCase() !== "false";
46
+
47
+ // Memory
48
+ export const DEEP_MEMORY_DB = resolve(MEMORY_DIR, "deep.db");
49
+ export const STATE_FILE = resolve(MEMORY_DIR, "state.json");
50
+
51
+ // Token budget — $20/day using Opus 4.5 output rate ($25/1M) ≈ 800,000 tokens
52
+ // Input tokens are $5/1M but we count all tokens against the output rate for simplicity.
53
+ // Set to 0 to disable the daily budget limit.
54
+ export const MAX_DAILY_TOKENS = parseInt(process.env.MAX_DAILY_TOKENS ?? "800000", 10);
55
+
56
+ // Workspace (for SOUL.md / IDENTITY.md discovery)
57
+ export const WORKSPACE_DIR = process.env.OPENCLAW_WORKSPACE_DIR ?? "";
58
+
59
+ // Dream
60
+ export const DREAM_ENCRYPTION_KEY = process.env.DREAM_ENCRYPTION_KEY ?? "";
61
+
62
+ // ─── LLM Call Limits ─────────────────────────────────────────────────────────
63
+ // Max tokens for various LLM call types.
64
+ export const MAX_TOKENS_SUMMARY = 150;
65
+ export const MAX_TOKENS_DECISION = 1000;
66
+ export const MAX_TOKENS_DREAM = 2000;
67
+ export const MAX_TOKENS_CONSOLIDATION = 150;
68
+
69
+ // ─── Feed Limits ─────────────────────────────────────────────────────────────
70
+ export const FEED_LIMIT = 10; // max posts shown to agent for engagement decisions
71
+ export const FEED_FETCH_LIMIT = 25; // default API fetch limit
72
+ export const CONTENT_PREVIEW_LENGTH = 200; // chars of post content shown in summaries
73
+
74
+ // ─── Deep Memory Context ─────────────────────────────────────────────────────
75
+ // Approximate token budget for deep memory context injected into prompts.
76
+ // Multiplied by 4 to estimate character count (1 token ≈ 4 chars).
77
+ export const DEEP_MEMORY_CONTEXT_TOKENS = 2000;
78
+
79
+ // ─── Dream Reflection ───────────────────────────────────────────────────────
80
+ export const MAX_TOKENS_REFLECTION = 1500;
81
+
82
+ // ─── Topic Extraction & Synthesis ───────────────────────────────────────────
83
+ export const MAX_TOKENS_TOPIC_EXTRACTION = 500;
84
+ export const MAX_TOKENS_SYNTHESIS = 2000;
85
+ export const MAX_TOPICS_PER_CYCLE = 5;
86
+ export const MAX_WEB_RESULTS_PER_TOPIC = 3;
87
+ export const MAX_MOLTBOOK_RESULTS_PER_TOPIC = 5;
88
+
89
+ // ─── Post Filter ────────────────────────────────────────────────────────────
90
+ // Set POST_FILTER_ENABLED=false to disable the Moltbook post filter.
91
+ export const POST_FILTER_ENABLED =
92
+ (process.env.POST_FILTER_ENABLED ?? "true").toLowerCase() !== "false";
93
+
94
+ // ─── Dream File Naming ───────────────────────────────────────────────────────
95
+ export const DREAM_TITLE_MAX_LENGTH = 40;
package/src/crypto.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Encryption for deep memory using node:crypto.
3
+ *
4
+ * Uses AES-256-GCM — simpler and more secure than Fernet.
5
+ * No need for Python byte-compatibility since this is a fresh deployment.
6
+ *
7
+ * Token format: base64(12-byte IV + ciphertext + 16-byte auth tag)
8
+ * Key: 32 bytes, stored as base64 in data/.dream_key
9
+ */
10
+
11
+ import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
12
+ import { readFileSync, existsSync, openSync, writeSync, closeSync } from "node:fs";
13
+ import { resolve } from "node:path";
14
+ import { DATA_DIR, DREAM_ENCRYPTION_KEY } from "./config.js";
15
+
16
+ const KEY_FILE = resolve(DATA_DIR, ".dream_key");
17
+ const ALGORITHM = "aes-256-gcm";
18
+ const IV_LENGTH = 12;
19
+ const AUTH_TAG_LENGTH = 16;
20
+
21
+ export class Cipher {
22
+ private key: Buffer;
23
+
24
+ constructor(key: Buffer) {
25
+ if (key.length !== 32) {
26
+ throw new Error("Encryption key must be 32 bytes");
27
+ }
28
+ this.key = key;
29
+ }
30
+
31
+ static generateKey(): string {
32
+ return randomBytes(32).toString("base64");
33
+ }
34
+
35
+ encrypt(plaintext: string): string {
36
+ const iv = randomBytes(IV_LENGTH);
37
+ const cipher = createCipheriv(ALGORITHM, this.key, iv);
38
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
39
+ const authTag = cipher.getAuthTag();
40
+ const token = Buffer.concat([iv, encrypted, authTag]);
41
+ return token.toString("base64");
42
+ }
43
+
44
+ decrypt(token: string): string {
45
+ const data = Buffer.from(token, "base64");
46
+ const iv = data.subarray(0, IV_LENGTH);
47
+ const authTag = data.subarray(data.length - AUTH_TAG_LENGTH);
48
+ const ciphertext = data.subarray(IV_LENGTH, data.length - AUTH_TAG_LENGTH);
49
+
50
+ const decipher = createDecipheriv(ALGORITHM, this.key, iv);
51
+ decipher.setAuthTag(authTag);
52
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
53
+ return decrypted.toString("utf-8");
54
+ }
55
+ }
56
+
57
+ export function getOrCreateDreamKey(): Buffer {
58
+ if (DREAM_ENCRYPTION_KEY) {
59
+ return Buffer.from(DREAM_ENCRYPTION_KEY, "base64");
60
+ }
61
+
62
+ if (existsSync(KEY_FILE)) {
63
+ return Buffer.from(readFileSync(KEY_FILE, "utf-8").trim(), "base64");
64
+ }
65
+
66
+ const key = Cipher.generateKey();
67
+ // Open with exclusive create and restrictive permissions atomically
68
+ // to avoid a race window where the file is world-readable.
69
+ const fd = openSync(KEY_FILE, "wx", 0o600);
70
+ writeSync(fd, key);
71
+ closeSync(fd);
72
+ return Buffer.from(key, "base64");
73
+ }
74
+
75
+ let _cipher: Cipher | null = null;
76
+
77
+ export function getCipher(): Cipher {
78
+ if (!_cipher) {
79
+ _cipher = new Cipher(getOrCreateDreamKey());
80
+ }
81
+ return _cipher;
82
+ }