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 +9 -0
- package/bin/multiplayer-client.js +240 -23
- package/package.json +3 -2
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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.
|
|
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"
|