spora 0.2.2 → 0.2.3

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 (30) hide show
  1. package/dist/chunk-5ELTXYDV.js +162 -0
  2. package/dist/chunk-5ELTXYDV.js.map +1 -0
  3. package/dist/{chunk-3WPIVG27.js → chunk-IIQAE7OP.js} +3 -3
  4. package/dist/{chunk-CMD2AGVW.js → chunk-T3JHWIKX.js} +2 -2
  5. package/dist/cli.js +24 -24
  6. package/dist/{client-RII4ST5D.js → client-2CURS7J6.js} +7 -7
  7. package/dist/{client-FULMJH3S.js → client-2ZSXZE3D.js} +7 -7
  8. package/dist/{colony-CBUE2M3P.js → colony-EFGAMLOX.js} +7 -7
  9. package/dist/{heartbeat-4EMODFIB.js → heartbeat-IYMR7BNA.js} +16 -138
  10. package/dist/heartbeat-IYMR7BNA.js.map +1 -0
  11. package/dist/{init-ZEILF6Y5.js → init-CH72XB55.js} +5 -5
  12. package/dist/mcp-server.js +31 -31
  13. package/dist/prompt-builder-XVMRRGY3.js +17 -0
  14. package/dist/{queue-5SDR3MF2.js → queue-N64QLRAB.js} +2 -2
  15. package/dist/web-chat/chat.html +140 -113
  16. package/dist/{web-chat-BNAFTFTH.js → web-chat-XJQ42L3R.js} +35 -11
  17. package/dist/web-chat-XJQ42L3R.js.map +1 -0
  18. package/dist/{x-client-T762KJFE.js → x-client-SLAC2ON5.js} +2 -2
  19. package/dist/x-client-SLAC2ON5.js.map +1 -0
  20. package/package.json +1 -1
  21. package/dist/heartbeat-4EMODFIB.js.map +0 -1
  22. package/dist/web-chat-BNAFTFTH.js.map +0 -1
  23. /package/dist/{chunk-3WPIVG27.js.map → chunk-IIQAE7OP.js.map} +0 -0
  24. /package/dist/{chunk-CMD2AGVW.js.map → chunk-T3JHWIKX.js.map} +0 -0
  25. /package/dist/{client-RII4ST5D.js.map → client-2CURS7J6.js.map} +0 -0
  26. /package/dist/{client-FULMJH3S.js.map → client-2ZSXZE3D.js.map} +0 -0
  27. /package/dist/{colony-CBUE2M3P.js.map → colony-EFGAMLOX.js.map} +0 -0
  28. /package/dist/{init-ZEILF6Y5.js.map → init-CH72XB55.js.map} +0 -0
  29. /package/dist/{queue-5SDR3MF2.js.map → prompt-builder-XVMRRGY3.js.map} +0 -0
  30. /package/dist/{x-client-T762KJFE.js.map → queue-N64QLRAB.js.map} +0 -0
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Spora Chat</title>
6
+ <title>Spora</title>
7
7
  <style>
8
8
  * {
9
9
  margin: 0;
@@ -21,39 +21,46 @@
21
21
  }
22
22
 
23
23
  .header {
24
- padding: 1rem 2rem;
25
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
24
+ padding: 0.875rem 1.25rem;
25
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
26
26
  display: flex;
27
27
  align-items: center;
28
- gap: 1rem;
28
+ gap: 0.75rem;
29
+ background: #0a0a0a;
30
+ }
31
+
32
+ .logo-img {
33
+ height: 28px;
34
+ width: auto;
29
35
  }
30
36
 
31
- .logo {
32
- font-size: 1.5rem;
33
- font-weight: bold;
34
- background: linear-gradient(135deg, #f97316, #ec4899);
35
- -webkit-background-clip: text;
36
- -webkit-text-fill-color: transparent;
37
- background-clip: text;
37
+ .logo-text {
38
+ font-size: 1.125rem;
39
+ font-weight: 700;
40
+ letter-spacing: -0.02em;
41
+ color: #fff;
38
42
  }
39
43
 
40
- .status {
44
+ .header-right {
41
45
  margin-left: auto;
42
46
  display: flex;
43
47
  align-items: center;
44
48
  gap: 0.5rem;
45
- font-size: 0.875rem;
46
- color: rgba(255, 255, 255, 0.6);
47
49
  }
48
50
 
49
51
  .status-dot {
50
- width: 8px;
51
- height: 8px;
52
+ width: 7px;
53
+ height: 7px;
52
54
  border-radius: 50%;
53
- background: #10b981;
55
+ background: #389e77;
54
56
  animation: pulse 2s infinite;
55
57
  }
56
58
 
59
+ .status-text {
60
+ font-size: 0.75rem;
61
+ color: rgba(255, 255, 255, 0.4);
62
+ }
63
+
57
64
  @keyframes pulse {
58
65
  0%, 100% { opacity: 1; }
59
66
  50% { opacity: 0.5; }
@@ -62,20 +69,33 @@
62
69
  .chat-container {
63
70
  flex: 1;
64
71
  overflow-y: auto;
65
- padding: 2rem;
72
+ padding: 1.25rem;
66
73
  display: flex;
67
74
  flex-direction: column;
68
- gap: 1rem;
75
+ gap: 0.875rem;
76
+ }
77
+
78
+ .chat-container::-webkit-scrollbar {
79
+ width: 4px;
80
+ }
81
+
82
+ .chat-container::-webkit-scrollbar-track {
83
+ background: transparent;
84
+ }
85
+
86
+ .chat-container::-webkit-scrollbar-thumb {
87
+ background: rgba(255, 255, 255, 0.1);
88
+ border-radius: 4px;
69
89
  }
70
90
 
71
91
  .message {
72
92
  display: flex;
73
- gap: 0.75rem;
93
+ gap: 0.625rem;
74
94
  animation: fadeIn 0.3s ease-in;
75
95
  }
76
96
 
77
97
  @keyframes fadeIn {
78
- from { opacity: 0; transform: translateY(10px); }
98
+ from { opacity: 0; transform: translateY(8px); }
79
99
  to { opacity: 1; transform: translateY(0); }
80
100
  }
81
101
 
@@ -88,15 +108,16 @@
88
108
  }
89
109
 
90
110
  .message-avatar {
91
- width: 40px;
92
- height: 40px;
111
+ width: 32px;
112
+ height: 32px;
93
113
  border-radius: 50%;
94
- background: #000;
114
+ background: #1a1a1a;
95
115
  display: flex;
96
116
  align-items: center;
97
117
  justify-content: center;
98
118
  flex-shrink: 0;
99
119
  overflow: hidden;
120
+ border: 1px solid rgba(255, 255, 255, 0.08);
100
121
  }
101
122
 
102
123
  .message-avatar img {
@@ -106,87 +127,93 @@
106
127
  }
107
128
 
108
129
  .message.user .message-avatar {
109
- order: 2;
130
+ display: none;
110
131
  }
111
132
 
112
133
  .message-content {
113
- max-width: 70%;
114
- padding: 0.75rem 1.25rem;
115
- border-radius: 1rem;
116
- font-size: 0.875rem;
117
- line-height: 1.5;
134
+ max-width: 80%;
135
+ padding: 0.625rem 0.875rem;
136
+ border-radius: 0.875rem;
137
+ font-size: 0.8125rem;
138
+ line-height: 1.55;
118
139
  white-space: pre-wrap;
119
140
  }
120
141
 
121
142
  .message.user .message-content {
122
- background: linear-gradient(135deg, #f97316, #ec4899);
143
+ background: #389e77;
123
144
  color: #fff;
124
145
  border-bottom-right-radius: 0.25rem;
125
146
  }
126
147
 
127
148
  .message.assistant .message-content {
128
- background: #1a1a1a;
129
- border: 1px solid rgba(255, 255, 255, 0.1);
149
+ background: #141414;
150
+ border: 1px solid rgba(255, 255, 255, 0.06);
130
151
  border-bottom-left-radius: 0.25rem;
152
+ color: rgba(255, 255, 255, 0.9);
131
153
  }
132
154
 
133
155
  .input-container {
134
- padding: 1.5rem 2rem;
135
- border-top: 1px solid rgba(255, 255, 255, 0.1);
156
+ padding: 1rem 1.25rem;
157
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
136
158
  display: flex;
137
- gap: 0.75rem;
159
+ gap: 0.5rem;
160
+ background: #0a0a0a;
138
161
  }
139
162
 
140
163
  .input-box {
141
164
  flex: 1;
142
- background: #1a1a1a;
143
- border: 1px solid rgba(255, 255, 255, 0.1);
144
- border-radius: 0.75rem;
145
- padding: 0.75rem 1rem;
165
+ background: #141414;
166
+ border: 1px solid rgba(255, 255, 255, 0.08);
167
+ border-radius: 0.625rem;
168
+ padding: 0.625rem 0.875rem;
146
169
  color: #fff;
147
- font-size: 0.875rem;
170
+ font-size: 0.8125rem;
148
171
  font-family: inherit;
149
172
  outline: none;
150
173
  transition: border-color 0.2s;
151
174
  }
152
175
 
153
176
  .input-box:focus {
154
- border-color: #f97316;
177
+ border-color: #389e77;
178
+ }
179
+
180
+ .input-box::placeholder {
181
+ color: rgba(255, 255, 255, 0.25);
155
182
  }
156
183
 
157
184
  .send-button {
158
- background: linear-gradient(135deg, #f97316, #ec4899);
185
+ background: #389e77;
159
186
  color: #fff;
160
187
  border: none;
161
- padding: 0.75rem 1.5rem;
162
- border-radius: 0.75rem;
188
+ padding: 0.625rem 1.125rem;
189
+ border-radius: 0.625rem;
163
190
  font-weight: 600;
164
- font-size: 0.875rem;
191
+ font-size: 0.8125rem;
165
192
  cursor: pointer;
166
- transition: transform 0.2s, box-shadow 0.2s;
193
+ transition: background 0.2s, transform 0.15s;
167
194
  }
168
195
 
169
196
  .send-button:hover:not(:disabled) {
170
- transform: scale(1.05);
171
- box-shadow: 0 10px 25px rgba(249, 115, 22, 0.3);
197
+ background: #4ab88a;
198
+ transform: scale(1.02);
172
199
  }
173
200
 
174
201
  .send-button:disabled {
175
- opacity: 0.5;
202
+ opacity: 0.4;
176
203
  cursor: not-allowed;
177
204
  }
178
205
 
179
206
  .loading {
180
207
  display: flex;
181
208
  gap: 0.25rem;
182
- padding: 0.75rem 1.25rem;
209
+ padding: 0.5rem 0;
183
210
  }
184
211
 
185
212
  .loading-dot {
186
- width: 8px;
187
- height: 8px;
213
+ width: 6px;
214
+ height: 6px;
188
215
  border-radius: 50%;
189
- background: rgba(255, 255, 255, 0.4);
216
+ background: rgba(255, 255, 255, 0.3);
190
217
  animation: bounce 1.4s infinite ease-in-out both;
191
218
  }
192
219
 
@@ -197,47 +224,20 @@
197
224
  0%, 80%, 100% { transform: scale(0); }
198
225
  40% { transform: scale(1); }
199
226
  }
200
-
201
- .empty-state {
202
- flex: 1;
203
- display: flex;
204
- flex-direction: column;
205
- align-items: center;
206
- justify-content: center;
207
- text-align: center;
208
- gap: 1rem;
209
- color: rgba(255, 255, 255, 0.4);
210
- }
211
-
212
- .empty-state-icon {
213
- font-size: 4rem;
214
- }
215
-
216
- .empty-state-text {
217
- font-size: 1.125rem;
218
- font-weight: 600;
219
- }
220
-
221
- .empty-state-subtext {
222
- font-size: 0.875rem;
223
- }
224
227
  </style>
225
228
  </head>
226
229
  <body>
227
230
  <div class="header">
228
- <div class="logo">SPORA</div>
229
- <div class="status">
231
+ <svg class="logo-img" viewBox="0 0 120 28" fill="none" xmlns="http://www.w3.org/2000/svg">
232
+ <text x="0" y="22" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="22" font-weight="800" letter-spacing="-0.5" fill="#fff">spora</text>
233
+ </svg>
234
+ <div class="header-right">
230
235
  <span class="status-dot"></span>
231
- <span>Connected</span>
236
+ <span class="status-text">Online</span>
232
237
  </div>
233
238
  </div>
234
239
 
235
240
  <div class="chat-container" id="chatContainer">
236
- <div class="empty-state">
237
- <div class="empty-state-icon">💬</div>
238
- <div class="empty-state-text">Chat with your Spore</div>
239
- <div class="empty-state-subtext">Start a conversation below</div>
240
- </div>
241
241
  </div>
242
242
 
243
243
  <div class="input-container">
@@ -245,7 +245,7 @@
245
245
  type="text"
246
246
  class="input-box"
247
247
  id="messageInput"
248
- placeholder="Type your message..."
248
+ placeholder="Message your agent..."
249
249
  autocomplete="off"
250
250
  />
251
251
  <button class="send-button" id="sendButton">Send</button>
@@ -257,13 +257,38 @@
257
257
  const sendButton = document.getElementById('sendButton');
258
258
 
259
259
  let isLoading = false;
260
+ let agentName = 'Your Spore';
261
+ let agentPfp = null;
262
+
263
+ // Fetch agent identity and show welcome message
264
+ async function init() {
265
+ try {
266
+ const response = await fetch('/api/identity');
267
+ const data = await response.json();
268
+ if (data.identity) {
269
+ agentName = data.identity.name || 'Your Spore';
270
+ agentPfp = data.identity.profileImage || null;
271
+
272
+ // Update page title
273
+ document.title = `${agentName} - Spora`;
274
+ }
275
+ } catch (error) {
276
+ console.error('Failed to load identity:', error);
277
+ }
278
+
279
+ // Show welcome message from the agent
280
+ addMessage('assistant', `Hey! I'm ${agentName}. I've just been set up and I'm ready to start posting on X. What would you like me to do? You can tell me what to post, how to behave, or just chat with me.`);
281
+
282
+ // Load any existing messages
283
+ await loadMessages();
284
+ }
260
285
 
261
- // Load existing messages on startup
262
286
  async function loadMessages() {
263
287
  try {
264
288
  const response = await fetch('/api/messages');
265
289
  const data = await response.json();
266
290
  if (data.messages && data.messages.length > 0) {
291
+ // Clear welcome message if there are existing messages
267
292
  chatContainer.innerHTML = '';
268
293
  data.messages.forEach(msg => {
269
294
  addMessage(msg.role, msg.content, false);
@@ -275,28 +300,29 @@
275
300
  }
276
301
 
277
302
  function addMessage(role, content, animate = true) {
278
- // Remove empty state if it exists
279
- const emptyState = chatContainer.querySelector('.empty-state');
280
- if (emptyState) {
281
- emptyState.remove();
282
- }
283
-
284
303
  const messageDiv = document.createElement('div');
285
304
  messageDiv.className = `message ${role}`;
305
+ if (!animate) messageDiv.style.animation = 'none';
286
306
 
287
- const avatar = document.createElement('div');
288
- avatar.className = 'message-avatar';
289
307
  if (role === 'assistant') {
290
- avatar.innerHTML = '<img src="/spore.png" alt="Spore" onerror="this.style.display=\'none\'" />';
291
- } else {
292
- avatar.textContent = '👤';
308
+ const avatar = document.createElement('div');
309
+ avatar.className = 'message-avatar';
310
+ if (agentPfp) {
311
+ avatar.innerHTML = `<img src="${agentPfp}" alt="${agentName}" onerror="this.parentElement.textContent='${agentName.charAt(0).toUpperCase()}'" />`;
312
+ } else {
313
+ avatar.textContent = agentName.charAt(0).toUpperCase();
314
+ avatar.style.background = '#389e77';
315
+ avatar.style.color = '#fff';
316
+ avatar.style.fontSize = '0.75rem';
317
+ avatar.style.fontWeight = '700';
318
+ }
319
+ messageDiv.appendChild(avatar);
293
320
  }
294
321
 
295
322
  const contentDiv = document.createElement('div');
296
323
  contentDiv.className = 'message-content';
297
324
  contentDiv.textContent = content;
298
325
 
299
- messageDiv.appendChild(avatar);
300
326
  messageDiv.appendChild(contentDiv);
301
327
 
302
328
  chatContainer.appendChild(messageDiv);
@@ -310,7 +336,15 @@
310
336
 
311
337
  const avatar = document.createElement('div');
312
338
  avatar.className = 'message-avatar';
313
- avatar.innerHTML = '<img src="/spore.png" alt="Spore" onerror="this.style.display=\'none\'" />';
339
+ if (agentPfp) {
340
+ avatar.innerHTML = `<img src="${agentPfp}" alt="${agentName}" />`;
341
+ } else {
342
+ avatar.textContent = agentName.charAt(0).toUpperCase();
343
+ avatar.style.background = '#389e77';
344
+ avatar.style.color = '#fff';
345
+ avatar.style.fontSize = '0.75rem';
346
+ avatar.style.fontWeight = '700';
347
+ }
314
348
 
315
349
  const loadingContent = document.createElement('div');
316
350
  loadingContent.className = 'message-content';
@@ -331,9 +365,7 @@
331
365
 
332
366
  function hideLoading() {
333
367
  const loadingDiv = document.getElementById('loadingMessage');
334
- if (loadingDiv) {
335
- loadingDiv.remove();
336
- }
368
+ if (loadingDiv) loadingDiv.remove();
337
369
  }
338
370
 
339
371
  async function sendMessage() {
@@ -344,11 +376,9 @@
344
376
  sendButton.disabled = true;
345
377
  messageInput.disabled = true;
346
378
 
347
- // Add user message
348
379
  addMessage('user', message);
349
380
  messageInput.value = '';
350
381
 
351
- // Show loading
352
382
  showLoading();
353
383
 
354
384
  try {
@@ -359,17 +389,16 @@
359
389
  });
360
390
 
361
391
  const data = await response.json();
362
-
363
392
  hideLoading();
364
393
 
365
394
  if (data.response) {
366
395
  addMessage('assistant', data.response);
367
396
  } else if (data.error) {
368
- addMessage('assistant', 'Sorry, I encountered an error. Please try again.');
397
+ addMessage('assistant', 'Something went wrong. Try again?');
369
398
  }
370
399
  } catch (error) {
371
400
  hideLoading();
372
- addMessage('assistant', 'Sorry, I couldn\'t connect to the server. Please try again.');
401
+ addMessage('assistant', 'Couldn\'t connect to the server. Try again?');
373
402
  } finally {
374
403
  isLoading = false;
375
404
  sendButton.disabled = false;
@@ -386,10 +415,8 @@
386
415
  }
387
416
  });
388
417
 
389
- // Load messages on startup
390
- loadMessages();
391
-
392
- // Focus input
418
+ // Initialize
419
+ init();
393
420
  messageInput.focus();
394
421
  </script>
395
422
  </body>
@@ -16,9 +16,13 @@ var WebChatServer = class {
16
16
  port;
17
17
  messages = [];
18
18
  onUserMessage;
19
+ identity;
19
20
  constructor(port = 3737) {
20
21
  this.port = port;
21
22
  }
23
+ setIdentity(identity) {
24
+ this.identity = identity;
25
+ }
22
26
  setMessageHandler(handler) {
23
27
  this.onUserMessage = handler;
24
28
  }
@@ -65,6 +69,11 @@ var WebChatServer = class {
65
69
  }
66
70
  return;
67
71
  }
72
+ if (url.pathname === "/api/identity" && req.method === "GET") {
73
+ res.writeHead(200, { "Content-Type": "application/json" });
74
+ res.end(JSON.stringify({ identity: this.identity || null }));
75
+ return;
76
+ }
68
77
  if (url.pathname === "/api/messages" && req.method === "GET") {
69
78
  res.writeHead(200, { "Content-Type": "application/json" });
70
79
  res.end(JSON.stringify({ messages: this.messages }));
@@ -135,17 +144,32 @@ import chalk from "chalk";
135
144
  async function startWebChat() {
136
145
  const identity = loadIdentity();
137
146
  const server = new WebChatServer();
147
+ server.setIdentity({
148
+ name: identity.name,
149
+ handle: identity.handle,
150
+ bio: identity.bio,
151
+ profileImage: identity.profileImage
152
+ });
153
+ const chatHistory = [];
154
+ let systemPrompt = null;
138
155
  server.setMessageHandler(async (message) => {
139
- const responses = [
140
- `That's interesting! As ${identity.name}, I'm always curious to learn more.`,
141
- `I hear you. Let me think about that...`,
142
- `${message} - that reminds me of something from my training.`,
143
- `Good question! Based on my understanding...`,
144
- `Thanks for sharing that with me!`
145
- ];
146
- const randomResponse = responses[Math.floor(Math.random() * responses.length)];
147
- await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
148
- return randomResponse;
156
+ try {
157
+ if (!systemPrompt) {
158
+ const { buildChatPrompt } = await import("./prompt-builder-XVMRRGY3.js");
159
+ systemPrompt = buildChatPrompt();
160
+ }
161
+ const { hasLLMKey, chat: chatLLM } = await import("./llm-OH2Z4PSN.js");
162
+ if (!hasLLMKey()) {
163
+ return "I can't respond right now - no API key configured. Run `spora set-llm-key` to set one up.";
164
+ }
165
+ chatHistory.push({ role: "user", content: message });
166
+ const response = await chatLLM(systemPrompt, chatHistory);
167
+ chatHistory.push({ role: "assistant", content: response.content });
168
+ return response.content;
169
+ } catch (error) {
170
+ console.error("Chat error:", error);
171
+ return `Sorry, I ran into an issue: ${error.message}`;
172
+ }
149
173
  });
150
174
  const url = await server.start();
151
175
  console.log(chalk.green(`
@@ -195,4 +219,4 @@ export {
195
219
  openBrowser,
196
220
  startWebChat
197
221
  };
198
- //# sourceMappingURL=web-chat-BNAFTFTH.js.map
222
+ //# sourceMappingURL=web-chat-XJQ42L3R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web-chat/server.ts","../src/web-chat/index.ts"],"sourcesContent":["/**\n * Local web chat server\n * Serves a simple chat interface for interacting with your Spore\n */\n\nimport http from \"node:http\";\nimport { URL } from \"node:url\";\nimport { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\ninterface ChatMessage {\n role: \"user\" | \"assistant\";\n content: string;\n timestamp: number;\n}\n\ninterface AgentIdentity {\n name: string;\n handle: string;\n bio?: string;\n profileImage?: string;\n}\n\nexport class WebChatServer {\n private server: http.Server | null = null;\n private port: number;\n private messages: ChatMessage[] = [];\n private onUserMessage?: (message: string) => Promise<string>;\n private identity?: AgentIdentity;\n\n constructor(port = 3737) {\n this.port = port;\n }\n\n setIdentity(identity: AgentIdentity) {\n this.identity = identity;\n }\n\n setMessageHandler(handler: (message: string) => Promise<string>) {\n this.onUserMessage = handler;\n }\n\n async start(): Promise<string> {\n return new Promise((resolve, reject) => {\n this.server = http.createServer(async (req, res) => {\n const url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n\n // CORS headers\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(200);\n res.end();\n return;\n }\n\n // Serve HTML\n if (url.pathname === \"/\" || url.pathname === \"/index.html\") {\n try {\n // Try multiple paths since __dirname changes based on build output\n const possiblePaths = [\n join(__dirname, \"web-chat\", \"chat.html\"),\n join(__dirname, \"chat.html\"),\n join(__dirname, \"..\", \"web-chat\", \"chat.html\"),\n join(__dirname, \"..\", \"src\", \"web-chat\", \"chat.html\"),\n ];\n\n let html: string | null = null;\n for (const p of possiblePaths) {\n try {\n html = readFileSync(p, \"utf-8\");\n break;\n } catch {\n continue;\n }\n }\n\n if (html) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(html);\n } else {\n console.error(\"Could not find chat.html in any of:\", possiblePaths);\n res.writeHead(500);\n res.end(\"Error: Could not find chat.html\");\n }\n } catch (error) {\n res.writeHead(500);\n res.end(\"Error loading chat interface\");\n }\n return;\n }\n\n // API: Get agent identity\n if (url.pathname === \"/api/identity\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ identity: this.identity || null }));\n return;\n }\n\n // API: Get messages\n if (url.pathname === \"/api/messages\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ messages: this.messages }));\n return;\n }\n\n // API: Send message\n if (url.pathname === \"/api/message\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => {\n body += chunk.toString();\n });\n\n req.on(\"end\", async () => {\n try {\n const { message } = JSON.parse(body);\n\n // Add user message\n this.messages.push({\n role: \"user\",\n content: message,\n timestamp: Date.now(),\n });\n\n // Get response\n if (this.onUserMessage) {\n const response = await this.onUserMessage(message);\n this.messages.push({\n role: \"assistant\",\n content: response,\n timestamp: Date.now(),\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ success: true, response }));\n } else {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No message handler configured\" }));\n }\n } catch (error) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid request\" }));\n }\n });\n return;\n }\n\n // 404\n res.writeHead(404);\n res.end(\"Not found\");\n });\n\n this.server.listen(this.port, () => {\n const url = `http://localhost:${this.port}`;\n resolve(url);\n });\n\n this.server.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"EADDRINUSE\") {\n // Port in use, try next port\n this.port++;\n this.server?.close();\n this.start().then(resolve).catch(reject);\n } else {\n reject(error);\n }\n });\n });\n }\n\n stop() {\n if (this.server) {\n this.server.close();\n this.server = null;\n }\n }\n}\n","/**\n * Web chat integration\n */\n\nimport { WebChatServer } from \"./server.js\";\nimport { loadIdentity } from \"../identity/index.js\";\nimport { execSync } from \"node:child_process\";\nimport chalk from \"chalk\";\n\nexport async function startWebChat() {\n const identity = loadIdentity();\n\n const server = new WebChatServer();\n\n // Pass identity to server so the chat UI can display it\n server.setIdentity({\n name: identity.name,\n handle: identity.handle,\n bio: identity.bio,\n profileImage: identity.profileImage,\n });\n\n // Set up message handler - connected to actual LLM\n const chatHistory: Array<{ role: \"user\" | \"assistant\"; content: string }> = [];\n let systemPrompt: string | null = null;\n\n server.setMessageHandler(async (message: string) => {\n try {\n // Build system prompt on first message\n if (!systemPrompt) {\n const { buildChatPrompt } = await import(\"../runtime/prompt-builder.js\");\n systemPrompt = buildChatPrompt();\n }\n\n // Check for LLM key\n const { hasLLMKey, chat: chatLLM } = await import(\"../runtime/llm.js\");\n if (!hasLLMKey()) {\n return \"I can't respond right now - no API key configured. Run `spora set-llm-key` to set one up.\";\n }\n\n // Add user message to history\n chatHistory.push({ role: \"user\", content: message });\n\n // Call LLM with full conversation history\n const response = await chatLLM(systemPrompt, chatHistory);\n\n // Add assistant response to history\n chatHistory.push({ role: \"assistant\", content: response.content });\n\n return response.content;\n } catch (error) {\n console.error(\"Chat error:\", error);\n return `Sorry, I ran into an issue: ${(error as Error).message}`;\n }\n });\n\n const url = await server.start();\n\n console.log(chalk.green(`\\n✓ Chat interface started at ${chalk.bold(url)}\\n`));\n console.log(chalk.dim(`Press Ctrl+C to stop the server\\n`));\n\n // Open browser\n openBrowser(url);\n\n // Keep process alive\n process.on(\"SIGINT\", () => {\n console.log(chalk.yellow(\"\\n\\nStopping chat server...\"));\n server.stop();\n process.exit(0);\n });\n\n // Return the server instance for external control\n return server;\n}\n\n/**\n * Open URL in a separate browser window (app-like)\n */\nfunction openBrowser(url: string) {\n const platform = process.platform;\n\n try {\n if (platform === \"darwin\") {\n // Open as a standalone Chrome app window (smaller, separate from existing tabs)\n try {\n execSync(`open -na \"Google Chrome\" --args --app=\"${url}\" --window-size=500,700`, { stdio: \"ignore\" });\n } catch {\n // Fallback: try Chromium-based browsers, then default\n try {\n execSync(`open -na \"Brave Browser\" --args --app=\"${url}\" --window-size=500,700`, { stdio: \"ignore\" });\n } catch {\n execSync(`open \"${url}\"`, { stdio: \"ignore\" });\n }\n }\n } else if (platform === \"win32\") {\n try {\n execSync(`start chrome --app=\"${url}\" --window-size=500,700`, { stdio: \"ignore\" });\n } catch {\n execSync(`start \"\" \"${url}\"`, { stdio: \"ignore\" });\n }\n } else {\n // Linux\n try {\n execSync(`google-chrome --app=\"${url}\" --window-size=500,700`, { stdio: \"ignore\" });\n } catch {\n execSync(`xdg-open \"${url}\"`, { stdio: \"ignore\" });\n }\n }\n } catch (error) {\n // Browser couldn't be opened, that's okay\n console.log(chalk.dim(`(Couldn't open browser automatically - please visit ${url} manually)`));\n }\n}\n\nexport { openBrowser };\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getXClient,
3
3
  resetXClient
4
- } from "./chunk-3WPIVG27.js";
4
+ } from "./chunk-IIQAE7OP.js";
5
5
  import "./chunk-YEKHNTQO.js";
6
6
  import "./chunk-KELPENM3.js";
7
7
  import "./chunk-53YLFYJF.js";
@@ -9,4 +9,4 @@ export {
9
9
  getXClient,
10
10
  resetXClient
11
11
  };
12
- //# sourceMappingURL=x-client-T762KJFE.js.map
12
+ //# sourceMappingURL=x-client-SLAC2ON5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spora",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "AI agents (Spores) that autonomously manage X/Twitter accounts",
5
5
  "type": "module",
6
6
  "author": "Spora",