@fnet/cli 0.2.8 → 0.2.10

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 (27) hide show
  1. package/dist/fservice/index.js +1 -1
  2. package/package.json +1 -1
  3. package/template/fnet/node/src/cli/index.js.njk +5 -315
  4. package/template/fnet/node/src/cli/index.js.v1.njk +319 -0
  5. package/template/fnet/node/src/cli/v2/core/args-parser.njk +10 -0
  6. package/template/fnet/node/src/cli/v2/core/imports.njk +33 -0
  7. package/template/fnet/node/src/cli/v2/core/run-wrapper.njk +25 -0
  8. package/template/fnet/node/src/cli/v2/index.js.njk +184 -0
  9. package/template/fnet/node/src/cli/v2/modes/default/extend.njk +11 -0
  10. package/template/fnet/node/src/cli/v2/modes/default/standard.njk +20 -0
  11. package/template/fnet/node/src/cli/v2/modes/http/builtin.njk +66 -0
  12. package/template/fnet/node/src/cli/v2/modes/http/imports.njk +9 -0
  13. package/template/fnet/node/src/cli/v2/modes/http/index.njk +12 -0
  14. package/template/fnet/node/src/cli/v2/modes/mcp/imports.njk +33 -0
  15. package/template/fnet/node/src/cli/v2/modes/mcp/index.njk +30 -0
  16. package/template/fnet/node/src/cli/v2/modes/mcp/server-setup.njk +15 -0
  17. package/template/fnet/node/src/cli/v2/modes/mcp/tool-handlers.njk +46 -0
  18. package/template/fnet/node/src/cli/v2/modes/mcp/transport-http.njk +222 -0
  19. package/template/fnet/node/src/cli/v2/modes/mcp/transport-stdio.njk +9 -0
  20. package/template/fnet/node/src/cli/v2/modes/pipeline/imports.njk +9 -0
  21. package/template/fnet/node/src/cli/v2/modes/pipeline/index.njk +113 -0
  22. package/template/fnet/node/src/cli/v2/modes/webhook/imports.njk +9 -0
  23. package/template/fnet/node/src/cli/v2/modes/webhook/index.njk +127 -0
  24. package/template/fnet/node/src/cli/v2/modes/websocket/imports.njk +15 -0
  25. package/template/fnet/node/src/cli/v2/modes/websocket/index.njk +104 -0
  26. package/template/fnode/node/src/cli/v2/modes/mcp/imports.njk +6 -0
  27. package/template/fnode/node/src/cli/v2/modes/mcp/transport-http.njk +68 -2
@@ -0,0 +1,104 @@
1
+ {# ============================================================================
2
+ WEBSOCKET MODE - MAIN
3
+ ============================================================================
4
+ WebSocket server for real-time bidirectional communication
5
+ Supports connection handling, message processing, and heartbeat
6
+ ============================================================================ #}
7
+
8
+ if (cliMode === 'websocket') {
9
+ // WebSocket mode code
10
+ const port = args['cli-port'] || args.cli_port || 3000;
11
+ const heartbeat = args['ws-heartbeat'] || args.ws_heartbeat || 30000; // 30 seconds default
12
+
13
+ const wss = new WebSocketServer({ port });
14
+
15
+ // Store active connections
16
+ const clients = new Set();
17
+
18
+ wss.on('connection', (ws) => {
19
+ console.log('WebSocket client connected');
20
+ clients.add(ws);
21
+
22
+ // Set up heartbeat
23
+ ws.isAlive = true;
24
+ ws.on('pong', () => {
25
+ ws.isAlive = true;
26
+ });
27
+
28
+ // Handle incoming messages
29
+ ws.on('message', async (data) => {
30
+ try {
31
+ // Parse message
32
+ const message = data.toString();
33
+ let input;
34
+
35
+ try {
36
+ input = JSON.parse(message);
37
+ } catch (e) {
38
+ // If not JSON, wrap in object
39
+ input = { data: message };
40
+ }
41
+
42
+ // Validate input
43
+ // TODO: Add schema validation if needed
44
+
45
+ // Process with Engine
46
+ {% if atom.doc.features.cli.extend===true %}
47
+ const result = await runExtended(input, { Engine });
48
+ {% else %}
49
+ const engine = new Engine();
50
+ const result = await engine.run(input);
51
+ {% endif %}
52
+
53
+ // Send response back to client
54
+ ws.send(JSON.stringify(result));
55
+
56
+ } catch (error) {
57
+ // Send error to client
58
+ ws.send(JSON.stringify({
59
+ error: error.message,
60
+ type: 'processing_error'
61
+ }));
62
+ }
63
+ });
64
+
65
+ // Handle client disconnect
66
+ ws.on('close', () => {
67
+ console.log('WebSocket client disconnected');
68
+ clients.delete(ws);
69
+ });
70
+
71
+ // Handle errors
72
+ ws.on('error', (error) => {
73
+ console.error('WebSocket error:', error.message);
74
+ clients.delete(ws);
75
+ });
76
+ });
77
+
78
+ // Heartbeat interval
79
+ if (heartbeat > 0) {
80
+ const interval = setInterval(() => {
81
+ wss.clients.forEach((ws) => {
82
+ if (ws.isAlive === false) {
83
+ console.log('Terminating inactive client');
84
+ return ws.terminate();
85
+ }
86
+
87
+ ws.isAlive = false;
88
+ ws.ping();
89
+ });
90
+ }, heartbeat);
91
+
92
+ wss.on('close', () => {
93
+ clearInterval(interval);
94
+ });
95
+ }
96
+
97
+ console.log(`WebSocket server started on port ${port}`);
98
+ if (heartbeat > 0) {
99
+ console.log(`Heartbeat interval: ${heartbeat}ms`);
100
+ }
101
+
102
+ return;
103
+ }
104
+
@@ -12,6 +12,9 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprot
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
13
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
14
14
  import express from "express";
15
+ {% if atom.doc.features.cli.mcp.ws===true %}
16
+ import expressWs from "express-ws";
17
+ {% endif %}
15
18
  {# crypto imported in shared section #}
16
19
 
17
20
  {% elif atom.doc.features.project.format==='cjs' %}
@@ -21,6 +24,9 @@ const { ListToolsRequestSchema, CallToolRequestSchema } = require("@modelcontext
21
24
  const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
22
25
  const { StreamableHTTPServerTransport } = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
23
26
  const express = require("express");
27
+ {% if atom.doc.features.cli.mcp.ws===true %}
28
+ const expressWs = require("express-ws");
29
+ {% endif %}
24
30
  {# crypto imported in shared section #}
25
31
 
26
32
  {% endif %}
@@ -7,15 +7,24 @@
7
7
 
8
8
  // Use Streamable HTTP transport (official transport as of MCP 2025-03-26)
9
9
  const app = express();
10
+ {% if atom.doc.features.cli.mcp.ws===true %}
11
+ // Enable WebSocket support
12
+ expressWs(app);
13
+ {% endif %}
10
14
  app.use(express.json());
11
15
 
12
16
  const port = args['cli-port'] || args.cli_port || 3000;
13
17
  const host = args['cli-host'] || args.cli_host || '0.0.0.0';
14
18
  const basePath = '{{atom.doc.features.cli.mcp.path or 'mcp'}}';
15
19
  const mcpEndpoint = `/${basePath}`; // MCP protocol endpoint
16
- const httpEndpoint = `/${basePath}/input`; // HTTP input endpoint
17
20
  const verbose = args['cli-verbose'] || args.cli_verbose || false;
18
21
 
22
+ // Optional endpoints (enabled via config)
23
+ const httpEnabled = {{atom.doc.features.cli.mcp.http or false}};
24
+ const wsEnabled = {{atom.doc.features.cli.mcp.ws or false}};
25
+ const httpEndpoint = `/${basePath}/input`; // HTTP input endpoint
26
+ const wsEndpoint = `/${basePath}/ws`; // WebSocket endpoint
27
+
19
28
  // Verbose logging helper
20
29
  const log = (...args) => {
21
30
  if (verbose) {
@@ -107,7 +116,8 @@ app.get(mcpEndpoint, handleSessionRequest);
107
116
  // Handle DELETE requests for session termination
108
117
  app.delete(mcpEndpoint, handleSessionRequest);
109
118
 
110
- // Handle HTTP POST requests for normal input processing (non-MCP)
119
+ {% if atom.doc.features.cli.mcp.http===true %}
120
+ // HTTP input endpoint (enabled via config: cli.mcp.http: true)
111
121
  app.post(httpEndpoint, async (req, res) => {
112
122
  log('HTTP input request received');
113
123
  log('Request body:', JSON.stringify(req.body, null, 2));
@@ -131,11 +141,67 @@ app.post(httpEndpoint, async (req, res) => {
131
141
  });
132
142
  }
133
143
  });
144
+ {% endif %}
145
+
146
+ {% if atom.doc.features.cli.mcp.ws===true %}
147
+ // WebSocket endpoint (enabled via config: cli.mcp.ws: true)
148
+ // Map to store WebSocket clients
149
+ const wsClients = new Set();
150
+
151
+ app.ws(wsEndpoint, (ws, req) => {
152
+ log('WebSocket connection established');
153
+ wsClients.add(ws);
154
+
155
+ // Handle incoming messages
156
+ ws.on('message', async (message) => {
157
+ log('WebSocket message received:', message);
158
+
159
+ try {
160
+ // Parse JSON message
161
+ const data = JSON.parse(message);
162
+
163
+ // Call the Node function
164
+ const result = await Node(data);
165
+
166
+ log('WebSocket message processed successfully');
167
+ log('Result:', JSON.stringify(result, null, 2));
168
+
169
+ // Send result back to client
170
+ ws.send(JSON.stringify(result));
171
+ } catch (error) {
172
+ log('WebSocket message processing error:', error.message);
173
+
174
+ // Send error response
175
+ ws.send(JSON.stringify({
176
+ error: error.message,
177
+ stack: verbose ? error.stack : undefined
178
+ }));
179
+ }
180
+ });
181
+
182
+ // Handle connection close
183
+ ws.on('close', () => {
184
+ log('WebSocket connection closed');
185
+ wsClients.delete(ws);
186
+ });
187
+
188
+ // Handle errors
189
+ ws.on('error', (error) => {
190
+ log('WebSocket error:', error.message);
191
+ wsClients.delete(ws);
192
+ });
193
+ });
194
+ {% endif %}
134
195
 
135
196
  app.listen(port, host, () => {
136
197
  console.log(`MCP server started with Streamable HTTP transport`);
137
198
  console.log(`MCP endpoint: http://${host}:${port}${mcpEndpoint}`);
199
+ {% if atom.doc.features.cli.mcp.http===true %}
138
200
  console.log(`HTTP endpoint: http://${host}:${port}${httpEndpoint}`);
201
+ {% endif %}
202
+ {% if atom.doc.features.cli.mcp.ws===true %}
203
+ console.log(`WebSocket endpoint: ws://${host}:${port}${wsEndpoint}`);
204
+ {% endif %}
139
205
  console.log(`Listening on ${host}:${port}`);
140
206
  if (verbose) {
141
207
  console.log(`Verbose logging enabled`);