sam-coder-cli 1.0.12 → 1.0.14

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/bin/agi-cli.js CHANGED
@@ -750,6 +750,15 @@ async function start() {
750
750
  output: process.stdout
751
751
  });
752
752
 
753
+ // Check for server mode
754
+ if (process.argv.includes('--server')) {
755
+ const MultiplayerServer = require('./multiplayer-server');
756
+ const server = new MultiplayerServer(8080);
757
+ server.start();
758
+ console.log('Multiplayer server started on ws://localhost:8080');
759
+ return;
760
+ }
761
+
753
762
  try {
754
763
  let config = await readConfig();
755
764
  if (!config || !config.OPENROUTER_API_KEY) {
@@ -1,8 +1,210 @@
1
1
  const WebSocket = require('ws');
2
+ const http = require('http');
2
3
  const EventEmitter = require('events');
3
4
  const readline = require('readline');
4
5
  const chalk = require('chalk');
5
6
  const { v4: uuidv4 } = require('uuid');
7
+ const { spawn, execSync } = require('child_process');
8
+
9
+ async function isPortInUse(port) {
10
+ return new Promise((resolve) => {
11
+ const net = require('net');
12
+ const server = net.createServer()
13
+ .once('error', () => resolve(true))
14
+ .once('listening', () => {
15
+ server.close();
16
+ resolve(false);
17
+ })
18
+ .listen(port);
19
+ });
20
+ }
21
+
22
+ async function ensureServerRunning(port = 8080) {
23
+ const portInUse = await isPortInUse(port);
24
+ if (!portInUse) {
25
+ console.log('Starting local multiplayer server...');
26
+ // Start server in a detached process
27
+ const serverProcess = spawn('node', [__filename, '--server', '--port', port.toString()], {
28
+ detached: true,
29
+ stdio: 'ignore',
30
+ windowsHide: true
31
+ });
32
+
33
+ serverProcess.unref();
34
+
35
+ // Wait for server to start
36
+ await new Promise(resolve => setTimeout(resolve, 1000));
37
+ console.log('Local multiplayer server started');
38
+ }
39
+ return `ws://localhost:${port}`;
40
+ }
41
+
42
+ class MultiplayerServer {
43
+ constructor(port = 8080, isChildProcess = false) {
44
+ this.port = port;
45
+ this.sessions = new Map();
46
+ this.server = http.createServer();
47
+ this.wss = new WebSocket.Server({ server: this.server });
48
+ this.setupEventHandlers();
49
+ }
50
+
51
+ setupEventHandlers() {
52
+ this.wss.on('connection', (ws) => {
53
+ let clientId = uuidv4();
54
+ let sessionId = null;
55
+ let clientInfo = {};
56
+
57
+ ws.on('message', (message) => {
58
+ try {
59
+ const data = JSON.parse(message);
60
+ this.handleMessage(ws, clientId, data);
61
+ } catch (error) {
62
+ console.error('Error parsing message:', error);
63
+ }
64
+ });
65
+
66
+ ws.on('close', () => {
67
+ if (sessionId && this.sessions.has(sessionId)) {
68
+ const session = this.sessions.get(sessionId);
69
+ delete session.clients[clientId];
70
+ this.broadcastToSession(sessionId, {
71
+ type: 'agent_left',
72
+ clientId,
73
+ clientInfo
74
+ });
75
+
76
+ if (Object.keys(session.clients).length === 0) {
77
+ this.sessions.delete(sessionId);
78
+ }
79
+ }
80
+ });
81
+ });
82
+ }
83
+
84
+ handleMessage(ws, clientId, data) {
85
+ switch (data.type) {
86
+ case 'create_session':
87
+ this.handleCreateSession(ws, clientId, data);
88
+ break;
89
+ case 'join_session':
90
+ this.handleJoinSession(ws, clientId, data);
91
+ break;
92
+ case 'update_work':
93
+ case 'chat':
94
+ case 'task_update':
95
+ this.broadcastToSession(data.sessionId, {
96
+ ...data,
97
+ clientId,
98
+ timestamp: new Date().toISOString()
99
+ });
100
+ break;
101
+ default:
102
+ console.log('Unknown message type:', data.type);
103
+ }
104
+ }
105
+
106
+ handleCreateSession(ws, clientId, data) {
107
+ const sessionId = uuidv4();
108
+ this.sessions.set(sessionId, {
109
+ id: sessionId,
110
+ clients: {},
111
+ workHistory: [],
112
+ createdAt: new Date().toISOString()
113
+ });
114
+
115
+ this.handleJoinSession(ws, clientId, {
116
+ ...data,
117
+ sessionId,
118
+ isHost: true
119
+ });
120
+ }
121
+
122
+ handleJoinSession(ws, clientId, data) {
123
+ const { sessionId, clientInfo } = data;
124
+
125
+ if (!this.sessions.has(sessionId)) {
126
+ ws.send(JSON.stringify({
127
+ type: 'error',
128
+ message: 'Session not found'
129
+ }));
130
+ return;
131
+ }
132
+
133
+ const session = this.sessions.get(sessionId);
134
+
135
+ if (Object.keys(session.clients).length >= 4) {
136
+ ws.send(JSON.stringify({
137
+ type: 'error',
138
+ message: 'Session is full (max 4 agents)'
139
+ }));
140
+ return;
141
+ }
142
+
143
+ // Add client to session
144
+ session.clients[clientId] = {
145
+ ws,
146
+ clientId,
147
+ clientInfo: {
148
+ ...clientInfo,
149
+ joinedAt: new Date().toISOString(),
150
+ isHost: data.isHost || false
151
+ }
152
+ };
153
+
154
+ // Send welcome message with session info
155
+ ws.send(JSON.stringify({
156
+ type: 'session_joined',
157
+ sessionId,
158
+ clientId,
159
+ clients: Object.values(session.clients).map(c => c.clientInfo),
160
+ workHistory: session.workHistory
161
+ }));
162
+
163
+ // Notify other clients
164
+ this.broadcastToSession(sessionId, {
165
+ type: 'agent_joined',
166
+ clientId,
167
+ clientInfo: session.clients[clientId].clientInfo
168
+ }, clientId);
169
+ }
170
+
171
+ broadcastToSession(sessionId, message, excludeClientId = null) {
172
+ if (!this.sessions.has(sessionId)) return;
173
+
174
+ const session = this.sessions.get(sessionId);
175
+ const messageStr = JSON.stringify(message);
176
+
177
+ Object.entries(session.clients).forEach(([id, client]) => {
178
+ if (id !== excludeClientId && client.ws.readyState === WebSocket.OPEN) {
179
+ client.ws.send(messageStr);
180
+ }
181
+ });
182
+ }
183
+
184
+ start() {
185
+ return new Promise((resolve) => {
186
+ this.server.listen(this.port, () => {
187
+ if (!this.isChildProcess) {
188
+ console.log(`Multiplayer server running on ws://localhost:${this.port}`);
189
+ }
190
+ resolve();
191
+ });
192
+ });
193
+ }
194
+
195
+ static async startAsChildProcess(port = 8080) {
196
+ const server = new MultiplayerServer(port, true);
197
+ await server.start();
198
+ // Keep process alive
199
+ process.on('SIGINT', () => process.exit());
200
+ }
201
+ }
202
+
203
+ // Start server if this file is run directly
204
+ if (require.main === module && process.argv.includes('--server')) {
205
+ const port = parseInt(process.argv[process.argv.indexOf('--port') + 1]) || 8080;
206
+ MultiplayerServer.startAsChildProcess(port);
207
+ }
6
208
 
7
209
  class MultiplayerClient extends EventEmitter {
8
210
  constructor(options = {}) {
@@ -22,29 +224,44 @@ class MultiplayerClient extends EventEmitter {
22
224
  });
23
225
  }
24
226
 
25
- connect() {
26
- this.ws = new WebSocket(this.serverUrl);
27
-
28
- this.ws.on('open', () => {
29
- this.emit('connected');
30
- });
31
-
32
- this.ws.on('message', (data) => {
33
- try {
34
- const message = JSON.parse(data);
35
- this.handleMessage(message);
36
- } catch (error) {
37
- console.error('Error parsing message:', error);
38
- }
39
- });
40
-
41
- this.ws.on('close', () => {
42
- this.emit('disconnected');
43
- });
44
-
45
- this.ws.on('error', (error) => {
46
- this.emit('error', error);
47
- });
227
+ async connect() {
228
+ try {
229
+ // Ensure server is running and get the correct URL
230
+ const serverPort = new URL(this.serverUrl).port || 8080;
231
+ this.serverUrl = await ensureServerRunning(serverPort);
232
+
233
+ return new Promise((resolve, reject) => {
234
+ this.ws = new WebSocket(this.serverUrl);
235
+
236
+ this.ws.on('open', () => {
237
+ this.connected = true;
238
+ this.emit('connected');
239
+ resolve();
240
+ });
241
+
242
+ this.ws.on('message', (data) => {
243
+ try {
244
+ const message = JSON.parse(data);
245
+ this.handleMessage(message);
246
+ } catch (error) {
247
+ console.error('Error parsing message:', error);
248
+ }
249
+ });
250
+
251
+ this.ws.on('close', () => {
252
+ this.connected = false;
253
+ this.emit('disconnected');
254
+ });
255
+
256
+ this.ws.on('error', (error) => {
257
+ this.connected = false;
258
+ reject(error);
259
+ });
260
+ });
261
+ } catch (error) {
262
+ console.error('Failed to start multiplayer server:', error);
263
+ throw error;
264
+ }
48
265
  }
49
266
 
50
267
  handleMessage(message) {
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "sam-coder-cli",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {
7
- "sam-coder": "bin/agi-cli.js"
7
+ "sam-coder": "bin/agi-cli.js",
8
+ "sam-coder-server": "bin/agi-cli.js --server"
8
9
  },
9
10
  "scripts": {
10
11
  "start": "node ./bin/agi-cli.js"