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.
- package/.env.example +14 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/dependabot.yml +17 -0
- package/.github/pull_request_template.md +19 -0
- package/.github/workflows/build.yml +30 -0
- package/.github/workflows/release.yml +110 -0
- package/.prettierignore +4 -0
- package/.prettierrc +7 -0
- package/.versionrc.json +26 -0
- package/AGENTS.md +286 -0
- package/CHANGELOG.md +157 -0
- package/CODE_OF_CONDUCT.md +41 -0
- package/CONTRIBUTING.md +95 -0
- package/LICENSE +21 -0
- package/README.md +363 -0
- package/SECURITY.md +39 -0
- package/bin/electricsheep.ts +5 -0
- package/dist/bin/electricsheep.d.ts +3 -0
- package/dist/bin/electricsheep.d.ts.map +1 -0
- package/dist/bin/electricsheep.js +4 -0
- package/dist/bin/electricsheep.js.map +1 -0
- package/dist/src/budget.d.ts +28 -0
- package/dist/src/budget.d.ts.map +1 -0
- package/dist/src/budget.js +87 -0
- package/dist/src/budget.js.map +1 -0
- package/dist/src/cli.d.ts +19 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +289 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.d.ts +37 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +70 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/crypto.d.ts +19 -0
- package/dist/src/crypto.d.ts.map +1 -0
- package/dist/src/crypto.js +70 -0
- package/dist/src/crypto.js.map +1 -0
- package/dist/src/dreamer.d.ts +13 -0
- package/dist/src/dreamer.d.ts.map +1 -0
- package/dist/src/dreamer.js +213 -0
- package/dist/src/dreamer.js.map +1 -0
- package/dist/src/filter.d.ts +30 -0
- package/dist/src/filter.d.ts.map +1 -0
- package/dist/src/filter.js +124 -0
- package/dist/src/filter.js.map +1 -0
- package/dist/src/identity.d.ts +29 -0
- package/dist/src/identity.d.ts.map +1 -0
- package/dist/src/identity.js +83 -0
- package/dist/src/identity.js.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +293 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/llm.d.ts +26 -0
- package/dist/src/llm.d.ts.map +1 -0
- package/dist/src/llm.js +40 -0
- package/dist/src/llm.js.map +1 -0
- package/dist/src/logger.d.ts +6 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +32 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/memory.d.ts +41 -0
- package/dist/src/memory.d.ts.map +1 -0
- package/dist/src/memory.js +206 -0
- package/dist/src/memory.js.map +1 -0
- package/dist/src/moltbook-search.d.ts +23 -0
- package/dist/src/moltbook-search.d.ts.map +1 -0
- package/dist/src/moltbook-search.js +85 -0
- package/dist/src/moltbook-search.js.map +1 -0
- package/dist/src/moltbook.d.ts +34 -0
- package/dist/src/moltbook.d.ts.map +1 -0
- package/dist/src/moltbook.js +165 -0
- package/dist/src/moltbook.js.map +1 -0
- package/dist/src/notify.d.ts +18 -0
- package/dist/src/notify.d.ts.map +1 -0
- package/dist/src/notify.js +98 -0
- package/dist/src/notify.js.map +1 -0
- package/dist/src/persona.d.ts +26 -0
- package/dist/src/persona.d.ts.map +1 -0
- package/dist/src/persona.js +178 -0
- package/dist/src/persona.js.map +1 -0
- package/dist/src/reflection.d.ts +26 -0
- package/dist/src/reflection.d.ts.map +1 -0
- package/dist/src/reflection.js +111 -0
- package/dist/src/reflection.js.map +1 -0
- package/dist/src/state.d.ts +7 -0
- package/dist/src/state.d.ts.map +1 -0
- package/dist/src/state.js +40 -0
- package/dist/src/state.js.map +1 -0
- package/dist/src/synthesis.d.ts +29 -0
- package/dist/src/synthesis.d.ts.map +1 -0
- package/dist/src/synthesis.js +125 -0
- package/dist/src/synthesis.js.map +1 -0
- package/dist/src/topics.d.ts +19 -0
- package/dist/src/topics.d.ts.map +1 -0
- package/dist/src/topics.js +83 -0
- package/dist/src/topics.js.map +1 -0
- package/dist/src/types.d.ts +179 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/waking.d.ts +24 -0
- package/dist/src/waking.d.ts.map +1 -0
- package/dist/src/waking.js +152 -0
- package/dist/src/waking.js.map +1 -0
- package/dist/src/web-search.d.ts +23 -0
- package/dist/src/web-search.d.ts.map +1 -0
- package/dist/src/web-search.js +64 -0
- package/dist/src/web-search.js.map +1 -0
- package/dist/test/budget.test.d.ts +2 -0
- package/dist/test/budget.test.d.ts.map +1 -0
- package/dist/test/budget.test.js +258 -0
- package/dist/test/budget.test.js.map +1 -0
- package/dist/test/crypto.test.d.ts +2 -0
- package/dist/test/crypto.test.d.ts.map +1 -0
- package/dist/test/crypto.test.js +93 -0
- package/dist/test/crypto.test.js.map +1 -0
- package/dist/test/dreamer.test.d.ts +2 -0
- package/dist/test/dreamer.test.d.ts.map +1 -0
- package/dist/test/dreamer.test.js +79 -0
- package/dist/test/dreamer.test.js.map +1 -0
- package/dist/test/filter.test.d.ts +2 -0
- package/dist/test/filter.test.d.ts.map +1 -0
- package/dist/test/filter.test.js +92 -0
- package/dist/test/filter.test.js.map +1 -0
- package/dist/test/memory.test.d.ts +2 -0
- package/dist/test/memory.test.d.ts.map +1 -0
- package/dist/test/memory.test.js +138 -0
- package/dist/test/memory.test.js.map +1 -0
- package/dist/test/moltbook.test.d.ts +2 -0
- package/dist/test/moltbook.test.d.ts.map +1 -0
- package/dist/test/moltbook.test.js +164 -0
- package/dist/test/moltbook.test.js.map +1 -0
- package/dist/test/persona.test.d.ts +2 -0
- package/dist/test/persona.test.d.ts.map +1 -0
- package/dist/test/persona.test.js +44 -0
- package/dist/test/persona.test.js.map +1 -0
- package/dist/test/reflection.test.d.ts +2 -0
- package/dist/test/reflection.test.d.ts.map +1 -0
- package/dist/test/reflection.test.js +57 -0
- package/dist/test/reflection.test.js.map +1 -0
- package/dist/test/state.test.d.ts +2 -0
- package/dist/test/state.test.d.ts.map +1 -0
- package/dist/test/state.test.js +50 -0
- package/dist/test/state.test.js.map +1 -0
- package/dist/test/waking.test.d.ts +2 -0
- package/dist/test/waking.test.d.ts.map +1 -0
- package/dist/test/waking.test.js +149 -0
- package/dist/test/waking.test.js.map +1 -0
- package/eslint.config.js +35 -0
- package/openclaw.plugin.json +62 -0
- package/package.json +72 -0
- package/skills/electricsheep.skill.md +69 -0
- package/skills/setup-guide/SKILL.md +303 -0
- package/src/budget.ts +104 -0
- package/src/cli.ts +325 -0
- package/src/config.ts +95 -0
- package/src/crypto.ts +82 -0
- package/src/dreamer.ts +283 -0
- package/src/filter.ts +146 -0
- package/src/identity.ts +92 -0
- package/src/index.ts +356 -0
- package/src/llm.ts +61 -0
- package/src/logger.ts +46 -0
- package/src/memory.ts +276 -0
- package/src/moltbook-search.ts +116 -0
- package/src/moltbook.ts +235 -0
- package/src/notify.ts +124 -0
- package/src/persona.ts +191 -0
- package/src/reflection.ts +150 -0
- package/src/state.ts +44 -0
- package/src/synthesis.ts +153 -0
- package/src/topics.ts +103 -0
- package/src/types.ts +196 -0
- package/src/waking.ts +199 -0
- package/src/web-search.ts +88 -0
- package/test/budget.test.ts +316 -0
- package/test/crypto.test.ts +112 -0
- package/test/dreamer.test.ts +95 -0
- package/test/filter.test.ts +115 -0
- package/test/memory.test.ts +182 -0
- package/test/moltbook.test.ts +209 -0
- package/test/persona.test.ts +59 -0
- package/test/reflection.test.ts +71 -0
- package/test/state.test.ts +57 -0
- package/test/waking.test.ts +214 -0
- 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
|
+
}
|