spora 0.1.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 (62) hide show
  1. package/README.md +87 -0
  2. package/bin/spora.js +2 -0
  3. package/dist/account-creator-PZW5JLHS.js +498 -0
  4. package/dist/account-creator-PZW5JLHS.js.map +1 -0
  5. package/dist/chunk-3JEDGXEM.js +32 -0
  6. package/dist/chunk-3JEDGXEM.js.map +1 -0
  7. package/dist/chunk-53YLFYJF.js +59 -0
  8. package/dist/chunk-53YLFYJF.js.map +1 -0
  9. package/dist/chunk-7CR4ID6P.js +614 -0
  10. package/dist/chunk-7CR4ID6P.js.map +1 -0
  11. package/dist/chunk-AHXZIGQE.js +156 -0
  12. package/dist/chunk-AHXZIGQE.js.map +1 -0
  13. package/dist/chunk-DJJWHOL3.js +162 -0
  14. package/dist/chunk-DJJWHOL3.js.map +1 -0
  15. package/dist/chunk-EBO4F5NU.js +105 -0
  16. package/dist/chunk-EBO4F5NU.js.map +1 -0
  17. package/dist/chunk-ERTBXYOP.js +81 -0
  18. package/dist/chunk-ERTBXYOP.js.map +1 -0
  19. package/dist/chunk-KELPENM3.js +47 -0
  20. package/dist/chunk-KELPENM3.js.map +1 -0
  21. package/dist/chunk-NFDZ47AG.js +57 -0
  22. package/dist/chunk-NFDZ47AG.js.map +1 -0
  23. package/dist/chunk-O23NWMYU.js +124 -0
  24. package/dist/chunk-O23NWMYU.js.map +1 -0
  25. package/dist/chunk-YEKHNTQO.js +80 -0
  26. package/dist/chunk-YEKHNTQO.js.map +1 -0
  27. package/dist/chunk-ZJZKH7N7.js +56 -0
  28. package/dist/chunk-ZJZKH7N7.js.map +1 -0
  29. package/dist/cli.js +675 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/client-3AQCA4YE.js +401 -0
  32. package/dist/client-3AQCA4YE.js.map +1 -0
  33. package/dist/client-RBGZWS3Q.js +373 -0
  34. package/dist/client-RBGZWS3Q.js.map +1 -0
  35. package/dist/colony-J5KQIV6M.js +229 -0
  36. package/dist/colony-J5KQIV6M.js.map +1 -0
  37. package/dist/config-NZAFARS6.js +14 -0
  38. package/dist/config-NZAFARS6.js.map +1 -0
  39. package/dist/crypto-FHSQ72NU.js +14 -0
  40. package/dist/crypto-FHSQ72NU.js.map +1 -0
  41. package/dist/heartbeat-J4JLYH2B.js +358 -0
  42. package/dist/heartbeat-J4JLYH2B.js.map +1 -0
  43. package/dist/init-BG4Z4XQU.js +205 -0
  44. package/dist/init-BG4Z4XQU.js.map +1 -0
  45. package/dist/llm-RDNC5Y3G.js +16 -0
  46. package/dist/llm-RDNC5Y3G.js.map +1 -0
  47. package/dist/mcp-server.js +773 -0
  48. package/dist/mcp-server.js.map +1 -0
  49. package/dist/memory-7FBE26K3.js +26 -0
  50. package/dist/memory-7FBE26K3.js.map +1 -0
  51. package/dist/memory-O3AJIKBX.js +24 -0
  52. package/dist/memory-O3AJIKBX.js.map +1 -0
  53. package/dist/paths-5GFUUHCZ.js +13 -0
  54. package/dist/paths-5GFUUHCZ.js.map +1 -0
  55. package/dist/prompt-builder-WNMZ2QCN.js +17 -0
  56. package/dist/prompt-builder-WNMZ2QCN.js.map +1 -0
  57. package/dist/queue-ELK5ZX7J.js +14 -0
  58. package/dist/queue-ELK5ZX7J.js.map +1 -0
  59. package/dist/x-client-J4GE5A7P.js +12 -0
  60. package/dist/x-client-J4GE5A7P.js.map +1 -0
  61. package/package.json +57 -0
  62. package/templates/SKILL.md +335 -0
@@ -0,0 +1,14 @@
1
+ import {
2
+ ConfigSchema,
3
+ createDefaultConfig,
4
+ loadConfig,
5
+ saveConfig
6
+ } from "./chunk-YEKHNTQO.js";
7
+ import "./chunk-53YLFYJF.js";
8
+ export {
9
+ ConfigSchema,
10
+ createDefaultConfig,
11
+ loadConfig,
12
+ saveConfig
13
+ };
14
+ //# sourceMappingURL=config-NZAFARS6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,14 @@
1
+ import {
2
+ decrypt,
3
+ encrypt,
4
+ loadCredentials,
5
+ saveCredentials
6
+ } from "./chunk-ZJZKH7N7.js";
7
+ import "./chunk-53YLFYJF.js";
8
+ export {
9
+ decrypt,
10
+ encrypt,
11
+ loadCredentials,
12
+ saveCredentials
13
+ };
14
+ //# sourceMappingURL=crypto-FHSQ72NU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,358 @@
1
+ import {
2
+ addToQueue,
3
+ flushQueue
4
+ } from "./chunk-O23NWMYU.js";
5
+ import {
6
+ getXClient
7
+ } from "./chunk-3JEDGXEM.js";
8
+ import {
9
+ generateResponse
10
+ } from "./chunk-ERTBXYOP.js";
11
+ import {
12
+ buildHeartbeatUserMessage,
13
+ buildSystemPrompt
14
+ } from "./chunk-DJJWHOL3.js";
15
+ import {
16
+ rateLimiter
17
+ } from "./chunk-NFDZ47AG.js";
18
+ import {
19
+ loadIdentity,
20
+ saveIdentity
21
+ } from "./chunk-7CR4ID6P.js";
22
+ import {
23
+ logger
24
+ } from "./chunk-KELPENM3.js";
25
+ import {
26
+ loadConfig
27
+ } from "./chunk-YEKHNTQO.js";
28
+ import {
29
+ addLearning,
30
+ logInteraction
31
+ } from "./chunk-EBO4F5NU.js";
32
+ import {
33
+ paths
34
+ } from "./chunk-53YLFYJF.js";
35
+
36
+ // src/runtime/heartbeat.ts
37
+ import { existsSync, unlinkSync, writeFileSync, readFileSync } from "fs";
38
+
39
+ // src/runtime/decision-engine.ts
40
+ function parseActions(llmResponse) {
41
+ const jsonMatch = llmResponse.match(/\[[\s\S]*?\]/);
42
+ if (!jsonMatch) {
43
+ const objMatch = llmResponse.match(/\{[\s\S]*?\}/);
44
+ if (objMatch) {
45
+ try {
46
+ return [JSON.parse(objMatch[0])];
47
+ } catch {
48
+ logger.warn("Could not parse LLM response as action object");
49
+ return [];
50
+ }
51
+ }
52
+ logger.warn("No JSON found in LLM response");
53
+ return [];
54
+ }
55
+ try {
56
+ const actions = JSON.parse(jsonMatch[0]);
57
+ return Array.isArray(actions) ? actions : [actions];
58
+ } catch {
59
+ logger.warn("Failed to parse actions JSON from LLM response");
60
+ return [];
61
+ }
62
+ }
63
+ async function executeAction(action) {
64
+ const { action: type } = action;
65
+ try {
66
+ switch (type) {
67
+ case "post": {
68
+ if (!action.content) return { action: type, success: false, error: "No content provided" };
69
+ if (action.content.length > 280) {
70
+ return { action: type, success: false, error: `Tweet too long: ${action.content.length} chars (max 280)` };
71
+ }
72
+ if (!rateLimiter.canPost()) {
73
+ return { action: type, success: false, error: "No credits remaining this month" };
74
+ }
75
+ const client = await getXClient();
76
+ const result = await client.postTweet(action.content);
77
+ if (result.success) {
78
+ rateLimiter.consume(1);
79
+ logInteraction({
80
+ id: `int-${Date.now()}`,
81
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
82
+ type: "post",
83
+ tweetId: result.tweetId,
84
+ content: action.content,
85
+ creditsUsed: 1,
86
+ success: true
87
+ });
88
+ logger.info(`Posted: "${action.content.slice(0, 50)}..."`);
89
+ }
90
+ return { action: type, success: result.success, detail: result.tweetId, error: result.error };
91
+ }
92
+ case "reply": {
93
+ if (!action.tweetId || !action.content) {
94
+ return { action: type, success: false, error: "Missing tweetId or content" };
95
+ }
96
+ if (!rateLimiter.canPost()) {
97
+ return { action: type, success: false, error: "No credits remaining" };
98
+ }
99
+ const client = await getXClient();
100
+ const result = await client.replyToTweet(action.tweetId, action.content);
101
+ if (result.success) {
102
+ rateLimiter.consume(1);
103
+ logInteraction({
104
+ id: `int-${Date.now()}`,
105
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
106
+ type: "reply",
107
+ tweetId: result.tweetId,
108
+ inReplyTo: action.tweetId,
109
+ content: action.content,
110
+ creditsUsed: 1,
111
+ success: true
112
+ });
113
+ logger.info(`Replied to ${action.tweetId}: "${action.content.slice(0, 50)}..."`);
114
+ }
115
+ return { action: type, success: result.success, detail: result.tweetId, error: result.error };
116
+ }
117
+ case "like": {
118
+ if (!action.tweetId) return { action: type, success: false, error: "Missing tweetId" };
119
+ const client = await getXClient();
120
+ const result = await client.likeTweet(action.tweetId);
121
+ if (result.success) {
122
+ logInteraction({
123
+ id: `int-${Date.now()}`,
124
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
125
+ type: "like",
126
+ tweetId: action.tweetId,
127
+ creditsUsed: 0,
128
+ success: true
129
+ });
130
+ }
131
+ return { action: type, success: result.success, error: result.error };
132
+ }
133
+ case "retweet": {
134
+ if (!action.tweetId) return { action: type, success: false, error: "Missing tweetId" };
135
+ const client = await getXClient();
136
+ const result = await client.retweet(action.tweetId);
137
+ if (result.success) {
138
+ logInteraction({
139
+ id: `int-${Date.now()}`,
140
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
141
+ type: "retweet",
142
+ tweetId: action.tweetId,
143
+ creditsUsed: 0,
144
+ success: true
145
+ });
146
+ }
147
+ return { action: type, success: result.success, error: result.error };
148
+ }
149
+ case "follow": {
150
+ if (!action.handle) return { action: type, success: false, error: "Missing handle" };
151
+ const client = await getXClient();
152
+ const result = await client.followUser(action.handle);
153
+ if (result.success) {
154
+ logInteraction({
155
+ id: `int-${Date.now()}`,
156
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
157
+ type: "follow",
158
+ targetHandle: action.handle,
159
+ creditsUsed: 0,
160
+ success: true
161
+ });
162
+ }
163
+ return { action: type, success: result.success, error: result.error };
164
+ }
165
+ case "schedule": {
166
+ if (!action.content) return { action: type, success: false, error: "No content" };
167
+ const entry = addToQueue(action.content);
168
+ logger.info(`Scheduled: "${action.content.slice(0, 50)}..." for ${entry.scheduledFor}`);
169
+ return { action: type, success: true, detail: `Scheduled for ${entry.scheduledFor}` };
170
+ }
171
+ case "learn": {
172
+ if (!action.content) return { action: type, success: false, error: "No content" };
173
+ addLearning(action.content, "agent", action.tags ?? ["heartbeat"]);
174
+ logger.info(`Learned: "${action.content.slice(0, 50)}..."`);
175
+ return { action: type, success: true };
176
+ }
177
+ case "reflect": {
178
+ if (!action.content) return { action: type, success: false, error: "No content" };
179
+ const identity = loadIdentity();
180
+ identity.evolutionJournal.push({
181
+ date: (/* @__PURE__ */ new Date()).toISOString(),
182
+ reflection: action.content
183
+ });
184
+ saveIdentity(identity);
185
+ logger.info(`Reflected: "${action.content.slice(0, 50)}..."`);
186
+ return { action: type, success: true };
187
+ }
188
+ case "skip": {
189
+ logger.info(`Skipping: ${action.reason ?? action.reasoning ?? "no reason given"}`);
190
+ return { action: type, success: true, detail: action.reason ?? action.reasoning };
191
+ }
192
+ default:
193
+ logger.warn(`Unknown action: ${type}`);
194
+ return { action: type, success: false, error: `Unknown action: ${type}` };
195
+ }
196
+ } catch (error) {
197
+ const msg = error.message;
198
+ logger.error(`Action ${type} failed: ${msg}`);
199
+ return { action: type, success: false, error: msg };
200
+ }
201
+ }
202
+ async function executeActions(actions) {
203
+ const results = [];
204
+ for (const action of actions) {
205
+ const result = await executeAction(action);
206
+ results.push(result);
207
+ await new Promise((r) => setTimeout(r, 2e3 + Math.random() * 3e3));
208
+ }
209
+ return results;
210
+ }
211
+
212
+ // src/runtime/heartbeat.ts
213
+ var running = false;
214
+ function isRunning() {
215
+ return running;
216
+ }
217
+ function requestStop() {
218
+ writeFileSync(paths.stopSignal, "stop");
219
+ logger.info("Stop signal sent.");
220
+ }
221
+ function shouldStop() {
222
+ if (existsSync(paths.stopSignal)) {
223
+ unlinkSync(paths.stopSignal);
224
+ return true;
225
+ }
226
+ return false;
227
+ }
228
+ function writePid() {
229
+ writeFileSync(paths.runtimePid, String(process.pid));
230
+ }
231
+ function clearPid() {
232
+ if (existsSync(paths.runtimePid)) {
233
+ unlinkSync(paths.runtimePid);
234
+ }
235
+ }
236
+ function getRunningPid() {
237
+ if (!existsSync(paths.runtimePid)) return null;
238
+ const pid = parseInt(readFileSync(paths.runtimePid, "utf-8").trim(), 10);
239
+ if (isNaN(pid)) return null;
240
+ try {
241
+ process.kill(pid, 0);
242
+ return pid;
243
+ } catch {
244
+ clearPid();
245
+ return null;
246
+ }
247
+ }
248
+ async function startHeartbeatLoop() {
249
+ const existingPid = getRunningPid();
250
+ if (existingPid) {
251
+ throw new Error(`Spora is already running (PID ${existingPid}). Run \`spora stop\` first.`);
252
+ }
253
+ running = true;
254
+ writePid();
255
+ const config = loadConfig();
256
+ const intervalMs = config.runtime?.heartbeatIntervalMs ?? 3e5;
257
+ const maxActions = config.runtime?.actionsPerHeartbeat ?? 3;
258
+ logger.info(`Spora agent starting. Heartbeat interval: ${intervalMs / 1e3}s, max actions: ${maxActions}`);
259
+ console.log(`
260
+ Spora agent is running (PID ${process.pid})`);
261
+ console.log(`Heartbeat every ${Math.round(intervalMs / 6e4)} minutes`);
262
+ console.log(`Press Ctrl+C or run \`spora stop\` to stop.
263
+ `);
264
+ const shutdown = () => {
265
+ logger.info("Shutting down...");
266
+ running = false;
267
+ clearPid();
268
+ process.exit(0);
269
+ };
270
+ process.on("SIGINT", shutdown);
271
+ process.on("SIGTERM", shutdown);
272
+ if (existsSync(paths.stopSignal)) {
273
+ unlinkSync(paths.stopSignal);
274
+ }
275
+ let heartbeatCount = 0;
276
+ while (running) {
277
+ heartbeatCount++;
278
+ logger.info(`=== Heartbeat #${heartbeatCount} ===`);
279
+ try {
280
+ await runHeartbeat(maxActions);
281
+ } catch (error) {
282
+ logger.error("Heartbeat error", error);
283
+ console.error(`Heartbeat #${heartbeatCount} failed: ${error.message}`);
284
+ }
285
+ if (shouldStop()) {
286
+ logger.info("Stop signal received.");
287
+ break;
288
+ }
289
+ const jitter = Math.floor(Math.random() * intervalMs * 0.3);
290
+ const sleepMs = intervalMs + jitter;
291
+ logger.info(`Sleeping ${Math.round(sleepMs / 1e3)}s until next heartbeat...`);
292
+ const chunkMs = 1e4;
293
+ let slept = 0;
294
+ while (slept < sleepMs && running) {
295
+ await new Promise((r) => setTimeout(r, Math.min(chunkMs, sleepMs - slept)));
296
+ slept += chunkMs;
297
+ if (shouldStop()) {
298
+ running = false;
299
+ break;
300
+ }
301
+ }
302
+ }
303
+ clearPid();
304
+ logger.info("Spora agent stopped.");
305
+ console.log("\nSpora agent stopped.");
306
+ }
307
+ async function runHeartbeat(maxActions) {
308
+ logger.info("Checking queue...");
309
+ try {
310
+ const flushed = await flushQueue();
311
+ if (flushed.posted > 0) {
312
+ logger.info(`Flushed ${flushed.posted} queued posts.`);
313
+ }
314
+ } catch (error) {
315
+ logger.warn(`Queue flush failed: ${error.message}`);
316
+ }
317
+ logger.info("Reading timeline and mentions...");
318
+ const client = await getXClient();
319
+ let timeline = [];
320
+ let mentions = [];
321
+ try {
322
+ timeline = await client.getTimeline({ count: 20 });
323
+ } catch (error) {
324
+ logger.warn(`Timeline read failed: ${error.message}`);
325
+ }
326
+ try {
327
+ mentions = await client.getMentions({ count: 10 });
328
+ } catch (error) {
329
+ logger.warn(`Mentions read failed: ${error.message}`);
330
+ }
331
+ const systemPrompt = buildSystemPrompt();
332
+ const userMessage = buildHeartbeatUserMessage(timeline, mentions);
333
+ logger.info("Asking LLM for decisions...");
334
+ const response = await generateResponse(systemPrompt, userMessage);
335
+ const actions = parseActions(response.content);
336
+ if (actions.length === 0) {
337
+ logger.info("LLM returned no actions.");
338
+ return;
339
+ }
340
+ const limitedActions = actions.slice(0, maxActions);
341
+ logger.info(`Executing ${limitedActions.length} action(s)...`);
342
+ const results = await executeActions(limitedActions);
343
+ for (const result of results) {
344
+ if (result.success) {
345
+ logger.info(` [OK] ${result.action}${result.detail ? `: ${result.detail}` : ""}`);
346
+ } else {
347
+ logger.warn(` [FAIL] ${result.action}: ${result.error}`);
348
+ }
349
+ }
350
+ logger.info(`Heartbeat complete. ${results.filter((r) => r.success).length}/${results.length} actions succeeded.`);
351
+ }
352
+ export {
353
+ getRunningPid,
354
+ isRunning,
355
+ requestStop,
356
+ startHeartbeatLoop
357
+ };
358
+ //# sourceMappingURL=heartbeat-J4JLYH2B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/heartbeat.ts","../src/runtime/decision-engine.ts"],"sourcesContent":["import { existsSync, unlinkSync, writeFileSync, readFileSync } from \"node:fs\";\nimport { logger } from \"../utils/logger.js\";\nimport { loadConfig } from \"../utils/config.js\";\nimport { paths } from \"../utils/paths.js\";\nimport { getXClient } from \"../x-client/index.js\";\nimport { flushQueue } from \"../scheduler/queue.js\";\nimport { buildSystemPrompt, buildHeartbeatUserMessage } from \"./prompt-builder.js\";\nimport { generateResponse } from \"./llm.js\";\nimport { parseActions, executeActions, type ActionResult } from \"./decision-engine.js\";\n\nlet running = false;\n\nexport function isRunning(): boolean {\n return running;\n}\n\nexport function requestStop(): void {\n writeFileSync(paths.stopSignal, \"stop\");\n logger.info(\"Stop signal sent.\");\n}\n\nfunction shouldStop(): boolean {\n if (existsSync(paths.stopSignal)) {\n unlinkSync(paths.stopSignal);\n return true;\n }\n return false;\n}\n\nfunction writePid(): void {\n writeFileSync(paths.runtimePid, String(process.pid));\n}\n\nfunction clearPid(): void {\n if (existsSync(paths.runtimePid)) {\n unlinkSync(paths.runtimePid);\n }\n}\n\nexport function getRunningPid(): number | null {\n if (!existsSync(paths.runtimePid)) return null;\n const pid = parseInt(readFileSync(paths.runtimePid, \"utf-8\").trim(), 10);\n if (isNaN(pid)) return null;\n\n // Check if process is actually running\n try {\n process.kill(pid, 0);\n return pid;\n } catch {\n // Process not running, clean up stale PID\n clearPid();\n return null;\n }\n}\n\nexport async function startHeartbeatLoop(): Promise<void> {\n // Check if already running\n const existingPid = getRunningPid();\n if (existingPid) {\n throw new Error(`Spora is already running (PID ${existingPid}). Run \\`spora stop\\` first.`);\n }\n\n running = true;\n writePid();\n\n const config = loadConfig();\n const intervalMs = config.runtime?.heartbeatIntervalMs ?? 300_000;\n const maxActions = config.runtime?.actionsPerHeartbeat ?? 3;\n\n logger.info(`Spora agent starting. Heartbeat interval: ${intervalMs / 1000}s, max actions: ${maxActions}`);\n console.log(`\\nSpora agent is running (PID ${process.pid})`);\n console.log(`Heartbeat every ${Math.round(intervalMs / 60_000)} minutes`);\n console.log(`Press Ctrl+C or run \\`spora stop\\` to stop.\\n`);\n\n // Handle graceful shutdown\n const shutdown = () => {\n logger.info(\"Shutting down...\");\n running = false;\n clearPid();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Clean any stale stop signal\n if (existsSync(paths.stopSignal)) {\n unlinkSync(paths.stopSignal);\n }\n\n let heartbeatCount = 0;\n\n while (running) {\n heartbeatCount++;\n logger.info(`=== Heartbeat #${heartbeatCount} ===`);\n\n try {\n await runHeartbeat(maxActions);\n } catch (error) {\n logger.error(\"Heartbeat error\", error);\n console.error(`Heartbeat #${heartbeatCount} failed: ${(error as Error).message}`);\n }\n\n // Check for stop signal\n if (shouldStop()) {\n logger.info(\"Stop signal received.\");\n break;\n }\n\n // Sleep with jitter\n const jitter = Math.floor(Math.random() * intervalMs * 0.3);\n const sleepMs = intervalMs + jitter;\n logger.info(`Sleeping ${Math.round(sleepMs / 1000)}s until next heartbeat...`);\n\n // Sleep in chunks so we can check for stop signals\n const chunkMs = 10_000;\n let slept = 0;\n while (slept < sleepMs && running) {\n await new Promise((r) => setTimeout(r, Math.min(chunkMs, sleepMs - slept)));\n slept += chunkMs;\n if (shouldStop()) {\n running = false;\n break;\n }\n }\n }\n\n clearPid();\n logger.info(\"Spora agent stopped.\");\n console.log(\"\\nSpora agent stopped.\");\n}\n\nasync function runHeartbeat(maxActions: number): Promise<void> {\n // 1. Flush any queued posts\n logger.info(\"Checking queue...\");\n try {\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n logger.info(`Flushed ${flushed.posted} queued posts.`);\n }\n } catch (error) {\n logger.warn(`Queue flush failed: ${(error as Error).message}`);\n }\n\n // 2. Read timeline and mentions for context\n logger.info(\"Reading timeline and mentions...\");\n const client = await getXClient();\n\n let timeline: Awaited<ReturnType<typeof client.getTimeline>> = [];\n let mentions: Awaited<ReturnType<typeof client.getMentions>> = [];\n\n try {\n timeline = await client.getTimeline({ count: 20 });\n } catch (error) {\n logger.warn(`Timeline read failed: ${(error as Error).message}`);\n }\n\n try {\n mentions = await client.getMentions({ count: 10 });\n } catch (error) {\n logger.warn(`Mentions read failed: ${(error as Error).message}`);\n }\n\n // 3. Build prompts\n const systemPrompt = buildSystemPrompt();\n const userMessage = buildHeartbeatUserMessage(timeline, mentions);\n\n // 4. Ask LLM for decisions\n logger.info(\"Asking LLM for decisions...\");\n const response = await generateResponse(systemPrompt, userMessage);\n\n // 5. Parse and execute actions\n const actions = parseActions(response.content);\n if (actions.length === 0) {\n logger.info(\"LLM returned no actions.\");\n return;\n }\n\n // Limit to max actions per heartbeat\n const limitedActions = actions.slice(0, maxActions);\n logger.info(`Executing ${limitedActions.length} action(s)...`);\n\n const results = await executeActions(limitedActions);\n\n // 6. Log results\n for (const result of results) {\n if (result.success) {\n logger.info(` [OK] ${result.action}${result.detail ? `: ${result.detail}` : \"\"}`);\n } else {\n logger.warn(` [FAIL] ${result.action}: ${result.error}`);\n }\n }\n\n logger.info(`Heartbeat complete. ${results.filter((r) => r.success).length}/${results.length} actions succeeded.`);\n}\n","import { logger } from \"../utils/logger.js\";\nimport { getXClient } from \"../x-client/index.js\";\nimport { logInteraction, addLearning } from \"../memory/index.js\";\nimport { loadIdentity, saveIdentity } from \"../identity/index.js\";\nimport { addToQueue } from \"../scheduler/queue.js\";\nimport { rateLimiter } from \"../x-client/rate-limiter.js\";\n\nexport interface AgentAction {\n action: string;\n content?: string;\n tweetId?: string;\n handle?: string;\n tags?: string[];\n reason?: string;\n reasoning?: string;\n}\n\nexport interface ActionResult {\n action: string;\n success: boolean;\n detail?: string;\n error?: string;\n}\n\nexport function parseActions(llmResponse: string): AgentAction[] {\n // Extract JSON array from the response (may be wrapped in markdown code blocks)\n const jsonMatch = llmResponse.match(/\\[[\\s\\S]*?\\]/);\n if (!jsonMatch) {\n // Try to parse as a single action object\n const objMatch = llmResponse.match(/\\{[\\s\\S]*?\\}/);\n if (objMatch) {\n try {\n return [JSON.parse(objMatch[0]) as AgentAction];\n } catch {\n logger.warn(\"Could not parse LLM response as action object\");\n return [];\n }\n }\n logger.warn(\"No JSON found in LLM response\");\n return [];\n }\n\n try {\n const actions = JSON.parse(jsonMatch[0]) as AgentAction[];\n return Array.isArray(actions) ? actions : [actions];\n } catch {\n logger.warn(\"Failed to parse actions JSON from LLM response\");\n return [];\n }\n}\n\nexport async function executeAction(action: AgentAction): Promise<ActionResult> {\n const { action: type } = action;\n\n try {\n switch (type) {\n case \"post\": {\n if (!action.content) return { action: type, success: false, error: \"No content provided\" };\n if (action.content.length > 280) {\n return { action: type, success: false, error: `Tweet too long: ${action.content.length} chars (max 280)` };\n }\n if (!rateLimiter.canPost()) {\n return { action: type, success: false, error: \"No credits remaining this month\" };\n }\n\n const client = await getXClient();\n const result = await client.postTweet(action.content);\n if (result.success) {\n rateLimiter.consume(1);\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"post\",\n tweetId: result.tweetId,\n content: action.content,\n creditsUsed: 1,\n success: true,\n });\n logger.info(`Posted: \"${action.content.slice(0, 50)}...\"`);\n }\n return { action: type, success: result.success, detail: result.tweetId, error: result.error };\n }\n\n case \"reply\": {\n if (!action.tweetId || !action.content) {\n return { action: type, success: false, error: \"Missing tweetId or content\" };\n }\n if (!rateLimiter.canPost()) {\n return { action: type, success: false, error: \"No credits remaining\" };\n }\n\n const client = await getXClient();\n const result = await client.replyToTweet(action.tweetId, action.content);\n if (result.success) {\n rateLimiter.consume(1);\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"reply\",\n tweetId: result.tweetId,\n inReplyTo: action.tweetId,\n content: action.content,\n creditsUsed: 1,\n success: true,\n });\n logger.info(`Replied to ${action.tweetId}: \"${action.content.slice(0, 50)}...\"`);\n }\n return { action: type, success: result.success, detail: result.tweetId, error: result.error };\n }\n\n case \"like\": {\n if (!action.tweetId) return { action: type, success: false, error: \"Missing tweetId\" };\n const client = await getXClient();\n const result = await client.likeTweet(action.tweetId);\n if (result.success) {\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"like\",\n tweetId: action.tweetId,\n creditsUsed: 0,\n success: true,\n });\n }\n return { action: type, success: result.success, error: result.error };\n }\n\n case \"retweet\": {\n if (!action.tweetId) return { action: type, success: false, error: \"Missing tweetId\" };\n const client = await getXClient();\n const result = await client.retweet(action.tweetId);\n if (result.success) {\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"retweet\",\n tweetId: action.tweetId,\n creditsUsed: 0,\n success: true,\n });\n }\n return { action: type, success: result.success, error: result.error };\n }\n\n case \"follow\": {\n if (!action.handle) return { action: type, success: false, error: \"Missing handle\" };\n const client = await getXClient();\n const result = await client.followUser(action.handle);\n if (result.success) {\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"follow\",\n targetHandle: action.handle,\n creditsUsed: 0,\n success: true,\n });\n }\n return { action: type, success: result.success, error: result.error };\n }\n\n case \"schedule\": {\n if (!action.content) return { action: type, success: false, error: \"No content\" };\n const entry = addToQueue(action.content);\n logger.info(`Scheduled: \"${action.content.slice(0, 50)}...\" for ${entry.scheduledFor}`);\n return { action: type, success: true, detail: `Scheduled for ${entry.scheduledFor}` };\n }\n\n case \"learn\": {\n if (!action.content) return { action: type, success: false, error: \"No content\" };\n addLearning(action.content, \"agent\", action.tags ?? [\"heartbeat\"]);\n logger.info(`Learned: \"${action.content.slice(0, 50)}...\"`);\n return { action: type, success: true };\n }\n\n case \"reflect\": {\n if (!action.content) return { action: type, success: false, error: \"No content\" };\n const identity = loadIdentity();\n identity.evolutionJournal.push({\n date: new Date().toISOString(),\n reflection: action.content,\n });\n saveIdentity(identity);\n logger.info(`Reflected: \"${action.content.slice(0, 50)}...\"`);\n return { action: type, success: true };\n }\n\n case \"skip\": {\n logger.info(`Skipping: ${action.reason ?? action.reasoning ?? \"no reason given\"}`);\n return { action: type, success: true, detail: action.reason ?? action.reasoning };\n }\n\n default:\n logger.warn(`Unknown action: ${type}`);\n return { action: type, success: false, error: `Unknown action: ${type}` };\n }\n } catch (error) {\n const msg = (error as Error).message;\n logger.error(`Action ${type} failed: ${msg}`);\n return { action: type, success: false, error: msg };\n }\n}\n\nexport async function executeActions(actions: AgentAction[]): Promise<ActionResult[]> {\n const results: ActionResult[] = [];\n for (const action of actions) {\n const result = await executeAction(action);\n results.push(result);\n // Small delay between actions to be human-like\n await new Promise((r) => setTimeout(r, 2000 + Math.random() * 3000));\n }\n return results;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,205 @@
1
+ import {
2
+ createDefaultConfig,
3
+ saveConfig
4
+ } from "./chunk-YEKHNTQO.js";
5
+ import {
6
+ saveCredentials
7
+ } from "./chunk-ZJZKH7N7.js";
8
+ import {
9
+ ensureDirectories,
10
+ sporaExists
11
+ } from "./chunk-53YLFYJF.js";
12
+
13
+ // src/init.ts
14
+ import { input, select, confirm, password as passwordPrompt } from "@inquirer/prompts";
15
+ import chalk from "chalk";
16
+ async function runInit() {
17
+ if (sporaExists()) {
18
+ const overwrite = await confirm({
19
+ message: "A Spore already exists. Overwrite credentials?",
20
+ default: false
21
+ });
22
+ if (!overwrite) {
23
+ console.log(chalk.yellow("Init cancelled."));
24
+ return;
25
+ }
26
+ }
27
+ ensureDirectories();
28
+ console.log(chalk.bold("\n--- X Account Setup ---\n"));
29
+ console.log(
30
+ chalk.gray(
31
+ "This sets up the X account your Spore will use.\nYour agent will handle personality and identity creation later.\n"
32
+ )
33
+ );
34
+ const accountMethod = await select({
35
+ message: "How do you want to connect an X account?",
36
+ choices: [
37
+ {
38
+ value: "create",
39
+ name: "Create a new account automatically (browser automation)"
40
+ },
41
+ {
42
+ value: "browser-existing",
43
+ name: "I have an X account \u2014 use browser mode (username/password)"
44
+ },
45
+ {
46
+ value: "api-existing",
47
+ name: "I have an X account \u2014 use API mode (developer credentials)"
48
+ }
49
+ ]
50
+ });
51
+ let xCredentials;
52
+ let xMethod;
53
+ let xApiTier;
54
+ if (accountMethod === "create") {
55
+ xMethod = "browser";
56
+ const sporeName = await input({
57
+ message: "What should the account be named?",
58
+ validate: (val) => val.length > 0 ? true : "Name is required"
59
+ });
60
+ console.log(chalk.yellow("\nAttempting automated X account creation..."));
61
+ console.log(chalk.yellow("A browser window will open. You may need to help with CAPTCHAs.\n"));
62
+ const { provisionAccount } = await import("./account-creator-PZW5JLHS.js");
63
+ const result = await provisionAccount({
64
+ name: sporeName
65
+ });
66
+ if (result.success) {
67
+ console.log(chalk.green(`
68
+ \u2713 X account created!`));
69
+ console.log(chalk.gray(` Username: @${result.username}`));
70
+ console.log(chalk.gray(` Email: ${result.email}`));
71
+ xCredentials = {
72
+ method: "browser",
73
+ username: result.username,
74
+ password: result.password,
75
+ email: result.email
76
+ };
77
+ } else {
78
+ console.log(chalk.red(`
79
+ \u2717 Automated creation failed: ${result.error}`));
80
+ console.log(chalk.yellow("Falling back to manual setup.\n"));
81
+ const username = await input({ message: "Enter your X username:" });
82
+ const xPassword = await passwordPrompt({ message: "Enter your X password:", mask: "*" });
83
+ const email = await input({ message: "Enter the email for this X account:" });
84
+ xCredentials = {
85
+ method: "browser",
86
+ username,
87
+ password: xPassword,
88
+ email
89
+ };
90
+ }
91
+ } else if (accountMethod === "browser-existing") {
92
+ xMethod = "browser";
93
+ const username = await input({ message: "X username (without @):" });
94
+ const xPassword = await passwordPrompt({ message: "X password:", mask: "*" });
95
+ const email = await input({ message: "Email associated with this X account:" });
96
+ xCredentials = {
97
+ method: "browser",
98
+ username,
99
+ password: xPassword,
100
+ email
101
+ };
102
+ } else {
103
+ xMethod = "api";
104
+ console.log(chalk.gray("\nYou'll need X API developer credentials."));
105
+ console.log(chalk.gray("Get them at https://developer.x.com\n"));
106
+ const apiKey = await passwordPrompt({ message: "API Key:", mask: "*" });
107
+ const apiSecret = await passwordPrompt({ message: "API Secret:", mask: "*" });
108
+ const accessToken = await passwordPrompt({ message: "Access Token:", mask: "*" });
109
+ const accessTokenSecret = await passwordPrompt({
110
+ message: "Access Token Secret:",
111
+ mask: "*"
112
+ });
113
+ const bearerToken = await passwordPrompt({ message: "Bearer Token:", mask: "*" });
114
+ xApiTier = await select({
115
+ message: "Which X API tier do you have?",
116
+ choices: [
117
+ {
118
+ value: "free",
119
+ name: "Free (post/reply only, 500 posts/month)"
120
+ },
121
+ {
122
+ value: "basic",
123
+ name: "Basic ($200/mo \u2014 full access)"
124
+ }
125
+ ]
126
+ });
127
+ xCredentials = {
128
+ method: "api",
129
+ apiKey,
130
+ apiSecret,
131
+ accessToken,
132
+ accessTokenSecret,
133
+ bearerToken
134
+ };
135
+ }
136
+ saveCredentials(xCredentials);
137
+ console.log(chalk.green("\u2713 X credentials saved (encrypted)\n"));
138
+ const config = createDefaultConfig({
139
+ xMethod,
140
+ xApiTier
141
+ });
142
+ saveConfig(config);
143
+ console.log(chalk.green("\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
144
+ console.log(chalk.green.bold(" X account connected."));
145
+ console.log(chalk.green("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n"));
146
+ console.log(chalk.gray("--- Next Steps ---\n"));
147
+ console.log(chalk.bold("Choose how to continue:\n"));
148
+ const continueMethod = await select({
149
+ message: "How do you want to create your Spore's identity?",
150
+ choices: [
151
+ {
152
+ value: "ui",
153
+ name: "Open web UI (recommended \u2014 visual setup wizard)"
154
+ },
155
+ {
156
+ value: "terminal",
157
+ name: "Stay in terminal (advanced \u2014 command-line setup)"
158
+ }
159
+ ]
160
+ });
161
+ if (continueMethod === "ui") {
162
+ console.log(chalk.cyan("\nLaunching Spora UI...\n"));
163
+ console.log(chalk.gray("The web interface will open in your browser."));
164
+ console.log(chalk.gray("Complete the setup wizard to create your Spore's identity.\n"));
165
+ const { spawn } = await import("child_process");
166
+ const { resolve } = await import("path");
167
+ const webDir = resolve(import.meta.dirname, "../packages/web");
168
+ try {
169
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
170
+ const { execSync } = await import("child_process");
171
+ setTimeout(() => {
172
+ try {
173
+ execSync(`${openCmd} http://localhost:3000`, { stdio: "ignore" });
174
+ } catch {
175
+ }
176
+ }, 2e3);
177
+ spawn("npx", ["next", "dev", "-p", "3000"], {
178
+ cwd: webDir,
179
+ stdio: "inherit"
180
+ });
181
+ } catch (error) {
182
+ console.log(chalk.red("Failed to launch UI. Run `spora ui` manually."));
183
+ }
184
+ } else {
185
+ console.log(chalk.bold("\n1. Set your Anthropic API key:\n"));
186
+ console.log(chalk.cyan(" spora set-llm-key"));
187
+ console.log(chalk.bold("\n2. Create your Spore identity:\n"));
188
+ console.log(chalk.cyan(" spora frameworks ") + chalk.gray("# List personality frameworks"));
189
+ console.log(chalk.cyan(" spora create \\"));
190
+ console.log(chalk.gray(" --framework truthseeker \\"));
191
+ console.log(chalk.gray(' --name "TruthBot" \\'));
192
+ console.log(chalk.gray(" --handle truthbot_ai \\"));
193
+ console.log(chalk.gray(' --goals "seek truth" "grow followers"'));
194
+ console.log(chalk.bold("\n3. Start your autonomous agent:\n"));
195
+ console.log(chalk.cyan(" spora start"));
196
+ console.log(chalk.bold("\n4. Monitor and chat with your Spore:\n"));
197
+ console.log(chalk.cyan(" spora ui ") + chalk.gray("# Open dashboard"));
198
+ console.log(chalk.cyan(" spora chat ") + chalk.gray("# Chat in terminal"));
199
+ console.log(chalk.cyan(" spora agent-status ") + chalk.gray("# Check if running\n"));
200
+ }
201
+ }
202
+ export {
203
+ runInit
204
+ };
205
+ //# sourceMappingURL=init-BG4Z4XQU.js.map