@robinpath/cli 1.73.0 → 1.75.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.
package/modules/http.js CHANGED
@@ -1,268 +1,268 @@
1
- /**
2
- * Native HTTP module for RobinPath.
3
- * HTTP client (fetch-based) and server (Node.js http).
4
- */
5
- import { createServer } from 'node:http';
6
- import { toStr, toNum, requireArgs } from './_helpers.js';
7
-
8
- const _servers = new Map();
9
- let _nextId = 1;
10
-
11
- async function doFetch(url, method, bodyArg, headersArg) {
12
- const opts = { method };
13
-
14
- // Headers
15
- const headers = {};
16
- if (headersArg && typeof headersArg === 'object') {
17
- for (const [k, v] of Object.entries(headersArg)) headers[k] = toStr(v);
18
- }
19
-
20
- // Body
21
- if (bodyArg != null && method !== 'GET' && method !== 'HEAD') {
22
- if (typeof bodyArg === 'object') {
23
- headers['Content-Type'] = headers['Content-Type'] || 'application/json';
24
- opts.body = JSON.stringify(bodyArg);
25
- } else {
26
- opts.body = toStr(bodyArg);
27
- }
28
- }
29
-
30
- opts.headers = headers;
31
-
32
- const res = await fetch(url, opts);
33
- const contentType = res.headers.get('content-type') || '';
34
- let data;
35
- if (contentType.includes('application/json')) {
36
- try { data = await res.json(); } catch { data = await res.text(); }
37
- } else {
38
- data = await res.text();
39
- }
40
-
41
- return {
42
- status: res.status,
43
- statusText: res.statusText,
44
- headers: Object.fromEntries(res.headers.entries()),
45
- data,
46
- ok: res.ok,
47
- url: res.url
48
- };
49
- }
50
-
51
- export const HttpFunctions = {
52
-
53
- get: async (args) => {
54
- requireArgs('http.get', args, 1);
55
- return doFetch(toStr(args[0]), 'GET', null, args[1]);
56
- },
57
-
58
- post: async (args) => {
59
- requireArgs('http.post', args, 1);
60
- return doFetch(toStr(args[0]), 'POST', args[1], args[2]);
61
- },
62
-
63
- put: async (args) => {
64
- requireArgs('http.put', args, 1);
65
- return doFetch(toStr(args[0]), 'PUT', args[1], args[2]);
66
- },
67
-
68
- patch: async (args) => {
69
- requireArgs('http.patch', args, 1);
70
- return doFetch(toStr(args[0]), 'PATCH', args[1], args[2]);
71
- },
72
-
73
- delete: async (args) => {
74
- requireArgs('http.delete', args, 1);
75
- return doFetch(toStr(args[0]), 'DELETE', args[1], args[2]);
76
- },
77
-
78
- head: async (args) => {
79
- requireArgs('http.head', args, 1);
80
- const res = await fetch(toStr(args[0]), { method: 'HEAD' });
81
- return {
82
- status: res.status,
83
- statusText: res.statusText,
84
- headers: Object.fromEntries(res.headers.entries()),
85
- ok: res.ok
86
- };
87
- },
88
-
89
- request: async (args) => {
90
- requireArgs('http.request', args, 1);
91
- const opts = args[0];
92
- if (typeof opts !== 'object' || opts === null) {
93
- throw new Error('http.request requires an options object: {url, method, body?, headers?}');
94
- }
95
- const url = toStr(opts.url || opts.href);
96
- const method = toStr(opts.method || 'GET').toUpperCase();
97
- return doFetch(url, method, opts.body || opts.data, opts.headers);
98
- },
99
-
100
- serve: (args, callback) => {
101
- requireArgs('http.serve', args, 1);
102
- const port = toNum(args[0], 3000);
103
- const host = toStr(args[1], '0.0.0.0');
104
- const id = `http_${_nextId++}`;
105
-
106
- const server = createServer(async (req, res) => {
107
- const body = await new Promise((resolve) => {
108
- let data = '';
109
- req.on('data', (chunk) => { data += chunk; });
110
- req.on('end', () => resolve(data));
111
- });
112
-
113
- let parsedBody = body;
114
- try { parsedBody = JSON.parse(body); } catch { /* raw string */ }
115
-
116
- const request = {
117
- method: req.method,
118
- url: req.url,
119
- headers: req.headers,
120
- body: parsedBody
121
- };
122
-
123
- if (callback) {
124
- try {
125
- const result = await callback([request]);
126
- const statusCode = (result && result.status) ? result.status : 200;
127
- const respHeaders = (result && result.headers) ? result.headers : { 'Content-Type': 'application/json' };
128
- const respBody = (result && result.body != null) ? result.body : result;
129
- res.writeHead(statusCode, respHeaders);
130
- res.end(typeof respBody === 'object' ? JSON.stringify(respBody) : toStr(respBody));
131
- } catch (err) {
132
- res.writeHead(500, { 'Content-Type': 'application/json' });
133
- res.end(JSON.stringify({ error: err.message }));
134
- }
135
- } else {
136
- res.writeHead(200, { 'Content-Type': 'application/json' });
137
- res.end(JSON.stringify(request));
138
- }
139
- });
140
-
141
- server.listen(port, host);
142
- _servers.set(id, server);
143
- return id;
144
- },
145
-
146
- close: (args) => {
147
- requireArgs('http.close', args, 1);
148
- const id = toStr(args[0]);
149
- const server = _servers.get(id);
150
- if (server) {
151
- server.close();
152
- _servers.delete(id);
153
- return true;
154
- }
155
- return false;
156
- },
157
-
158
- servers: () => Array.from(_servers.keys()),
159
-
160
- download: async (args) => {
161
- requireArgs('http.download', args, 2);
162
- const url = toStr(args[0]);
163
- const filePath = toStr(args[1]);
164
- const res = await fetch(url);
165
- if (!res.ok) throw new Error(`http.download: ${res.status} ${res.statusText}`);
166
- const { writeFile } = await import('node:fs/promises');
167
- const { resolve } = await import('node:path');
168
- const buffer = Buffer.from(await res.arrayBuffer());
169
- await writeFile(resolve(filePath), buffer);
170
- return { size: buffer.length, path: filePath, status: res.status };
171
- }
172
- };
173
-
174
- export const HttpFunctionMetadata = {
175
- get: {
176
- description: 'HTTP GET request',
177
- parameters: [
178
- { name: 'url', dataType: 'string', description: 'URL to request', formInputType: 'text', required: true },
179
- { name: 'headers', dataType: 'object', description: 'Request headers', formInputType: 'json', required: false }
180
- ],
181
- returnType: 'object', returnDescription: 'Response with status, headers, data', example: 'http.get "https://api.example.com/data"'
182
- },
183
- post: {
184
- description: 'HTTP POST request',
185
- parameters: [
186
- { name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true },
187
- { name: 'body', dataType: 'any', description: 'Request body (object auto-serialized to JSON)', formInputType: 'json', required: false },
188
- { name: 'headers', dataType: 'object', description: 'Request headers', formInputType: 'json', required: false }
189
- ],
190
- returnType: 'object', returnDescription: 'Response with status, headers, data', example: 'http.post "https://api.example.com/data" {"key": "value"}'
191
- },
192
- put: {
193
- description: 'HTTP PUT request',
194
- parameters: [
195
- { name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true },
196
- { name: 'body', dataType: 'any', description: 'Request body', formInputType: 'json', required: false },
197
- { name: 'headers', dataType: 'object', description: 'Headers', formInputType: 'json', required: false }
198
- ],
199
- returnType: 'object', returnDescription: 'Response', example: 'http.put "https://api.example.com/data/1" {"key": "new"}'
200
- },
201
- patch: {
202
- description: 'HTTP PATCH request',
203
- parameters: [
204
- { name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true },
205
- { name: 'body', dataType: 'any', description: 'Request body', formInputType: 'json', required: false },
206
- { name: 'headers', dataType: 'object', description: 'Headers', formInputType: 'json', required: false }
207
- ],
208
- returnType: 'object', returnDescription: 'Response', example: 'http.patch "https://api.example.com/data/1" {"key": "updated"}'
209
- },
210
- delete: {
211
- description: 'HTTP DELETE request',
212
- parameters: [
213
- { name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true },
214
- { name: 'body', dataType: 'any', description: 'Request body', formInputType: 'json', required: false },
215
- { name: 'headers', dataType: 'object', description: 'Headers', formInputType: 'json', required: false }
216
- ],
217
- returnType: 'object', returnDescription: 'Response', example: 'http.delete "https://api.example.com/data/1"'
218
- },
219
- head: {
220
- description: 'HTTP HEAD request (headers only)',
221
- parameters: [{ name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true }],
222
- returnType: 'object', returnDescription: 'Response with status and headers', example: 'http.head "https://example.com"'
223
- },
224
- request: {
225
- description: 'Custom HTTP request with full options',
226
- parameters: [{ name: 'options', dataType: 'object', description: 'Object with url, method, body, headers', formInputType: 'json', required: true }],
227
- returnType: 'object', returnDescription: 'Response', example: 'http.request {"url": "https://api.example.com", "method": "POST", "body": {"key": 1}}'
228
- },
229
- serve: {
230
- description: 'Start an HTTP server',
231
- parameters: [
232
- { name: 'port', dataType: 'number', description: 'Port to listen on (default: 3000)', formInputType: 'number', required: true },
233
- { name: 'host', dataType: 'string', description: 'Host (default: 0.0.0.0)', formInputType: 'text', required: false, defaultValue: '0.0.0.0' }
234
- ],
235
- returnType: 'string', returnDescription: 'Server handle ID', example: 'http.serve 8080'
236
- },
237
- close: {
238
- description: 'Close an HTTP server',
239
- parameters: [{ name: 'serverId', dataType: 'string', description: 'Server handle ID', formInputType: 'text', required: true }],
240
- returnType: 'boolean', returnDescription: 'true if closed', example: 'http.close $serverId'
241
- },
242
- servers: {
243
- description: 'List all running HTTP servers',
244
- parameters: [],
245
- returnType: 'array', returnDescription: 'Array of server IDs', example: 'http.servers'
246
- },
247
- download: {
248
- description: 'Download a file from a URL',
249
- parameters: [
250
- { name: 'url', dataType: 'string', description: 'URL to download', formInputType: 'text', required: true },
251
- { name: 'path', dataType: 'string', description: 'Local file path to save', formInputType: 'text', required: true }
252
- ],
253
- returnType: 'object', returnDescription: 'Object with size, path, status', example: 'http.download "https://example.com/file.zip" "file.zip"'
254
- }
255
- };
256
-
257
- export const HttpModuleMetadata = {
258
- description: 'HTTP client and server: GET, POST, PUT, DELETE, serve, download',
259
- methods: Object.keys(HttpFunctions)
260
- };
261
-
262
- export default {
263
- name: 'http',
264
- functions: HttpFunctions,
265
- functionMetadata: HttpFunctionMetadata,
266
- moduleMetadata: HttpModuleMetadata,
267
- global: false
268
- };
1
+ /**
2
+ * Native HTTP module for RobinPath.
3
+ * HTTP client (fetch-based) and server (Node.js http).
4
+ */
5
+ import { createServer } from 'node:http';
6
+ import { toStr, toNum, requireArgs } from './_helpers.js';
7
+
8
+ const _servers = new Map();
9
+ let _nextId = 1;
10
+
11
+ async function doFetch(url, method, bodyArg, headersArg) {
12
+ const opts = { method };
13
+
14
+ // Headers
15
+ const headers = {};
16
+ if (headersArg && typeof headersArg === 'object') {
17
+ for (const [k, v] of Object.entries(headersArg)) headers[k] = toStr(v);
18
+ }
19
+
20
+ // Body
21
+ if (bodyArg != null && method !== 'GET' && method !== 'HEAD') {
22
+ if (typeof bodyArg === 'object') {
23
+ headers['Content-Type'] = headers['Content-Type'] || 'application/json';
24
+ opts.body = JSON.stringify(bodyArg);
25
+ } else {
26
+ opts.body = toStr(bodyArg);
27
+ }
28
+ }
29
+
30
+ opts.headers = headers;
31
+
32
+ const res = await fetch(url, opts);
33
+ const contentType = res.headers.get('content-type') || '';
34
+ let data;
35
+ if (contentType.includes('application/json')) {
36
+ try { data = await res.json(); } catch { data = await res.text(); }
37
+ } else {
38
+ data = await res.text();
39
+ }
40
+
41
+ return {
42
+ status: res.status,
43
+ statusText: res.statusText,
44
+ headers: Object.fromEntries(res.headers.entries()),
45
+ data,
46
+ ok: res.ok,
47
+ url: res.url
48
+ };
49
+ }
50
+
51
+ export const HttpFunctions = {
52
+
53
+ get: async (args) => {
54
+ requireArgs('http.get', args, 1);
55
+ return doFetch(toStr(args[0]), 'GET', null, args[1]);
56
+ },
57
+
58
+ post: async (args) => {
59
+ requireArgs('http.post', args, 1);
60
+ return doFetch(toStr(args[0]), 'POST', args[1], args[2]);
61
+ },
62
+
63
+ put: async (args) => {
64
+ requireArgs('http.put', args, 1);
65
+ return doFetch(toStr(args[0]), 'PUT', args[1], args[2]);
66
+ },
67
+
68
+ patch: async (args) => {
69
+ requireArgs('http.patch', args, 1);
70
+ return doFetch(toStr(args[0]), 'PATCH', args[1], args[2]);
71
+ },
72
+
73
+ delete: async (args) => {
74
+ requireArgs('http.delete', args, 1);
75
+ return doFetch(toStr(args[0]), 'DELETE', args[1], args[2]);
76
+ },
77
+
78
+ head: async (args) => {
79
+ requireArgs('http.head', args, 1);
80
+ const res = await fetch(toStr(args[0]), { method: 'HEAD' });
81
+ return {
82
+ status: res.status,
83
+ statusText: res.statusText,
84
+ headers: Object.fromEntries(res.headers.entries()),
85
+ ok: res.ok
86
+ };
87
+ },
88
+
89
+ request: async (args) => {
90
+ requireArgs('http.request', args, 1);
91
+ const opts = args[0];
92
+ if (typeof opts !== 'object' || opts === null) {
93
+ throw new Error('http.request requires an options object: {url, method, body?, headers?}');
94
+ }
95
+ const url = toStr(opts.url || opts.href);
96
+ const method = toStr(opts.method || 'GET').toUpperCase();
97
+ return doFetch(url, method, opts.body || opts.data, opts.headers);
98
+ },
99
+
100
+ serve: (args, callback) => {
101
+ requireArgs('http.serve', args, 1);
102
+ const port = toNum(args[0], 3000);
103
+ const host = toStr(args[1], '0.0.0.0');
104
+ const id = `http_${_nextId++}`;
105
+
106
+ const server = createServer(async (req, res) => {
107
+ const body = await new Promise((resolve) => {
108
+ let data = '';
109
+ req.on('data', (chunk) => { data += chunk; });
110
+ req.on('end', () => resolve(data));
111
+ });
112
+
113
+ let parsedBody = body;
114
+ try { parsedBody = JSON.parse(body); } catch { /* raw string */ }
115
+
116
+ const request = {
117
+ method: req.method,
118
+ url: req.url,
119
+ headers: req.headers,
120
+ body: parsedBody
121
+ };
122
+
123
+ if (callback) {
124
+ try {
125
+ const result = await callback([request]);
126
+ const statusCode = (result && result.status) ? result.status : 200;
127
+ const respHeaders = (result && result.headers) ? result.headers : { 'Content-Type': 'application/json' };
128
+ const respBody = (result && result.body != null) ? result.body : result;
129
+ res.writeHead(statusCode, respHeaders);
130
+ res.end(typeof respBody === 'object' ? JSON.stringify(respBody) : toStr(respBody));
131
+ } catch (err) {
132
+ res.writeHead(500, { 'Content-Type': 'application/json' });
133
+ res.end(JSON.stringify({ error: err.message }));
134
+ }
135
+ } else {
136
+ res.writeHead(200, { 'Content-Type': 'application/json' });
137
+ res.end(JSON.stringify(request));
138
+ }
139
+ });
140
+
141
+ server.listen(port, host);
142
+ _servers.set(id, server);
143
+ return id;
144
+ },
145
+
146
+ close: (args) => {
147
+ requireArgs('http.close', args, 1);
148
+ const id = toStr(args[0]);
149
+ const server = _servers.get(id);
150
+ if (server) {
151
+ server.close();
152
+ _servers.delete(id);
153
+ return true;
154
+ }
155
+ return false;
156
+ },
157
+
158
+ servers: () => Array.from(_servers.keys()),
159
+
160
+ download: async (args) => {
161
+ requireArgs('http.download', args, 2);
162
+ const url = toStr(args[0]);
163
+ const filePath = toStr(args[1]);
164
+ const res = await fetch(url);
165
+ if (!res.ok) throw new Error(`http.download: ${res.status} ${res.statusText}`);
166
+ const { writeFile } = await import('node:fs/promises');
167
+ const { resolve } = await import('node:path');
168
+ const buffer = Buffer.from(await res.arrayBuffer());
169
+ await writeFile(resolve(filePath), buffer);
170
+ return { size: buffer.length, path: filePath, status: res.status };
171
+ }
172
+ };
173
+
174
+ export const HttpFunctionMetadata = {
175
+ get: {
176
+ description: 'HTTP GET request',
177
+ parameters: [
178
+ { name: 'url', dataType: 'string', description: 'URL to request', formInputType: 'text', required: true },
179
+ { name: 'headers', dataType: 'object', description: 'Request headers', formInputType: 'json', required: false }
180
+ ],
181
+ returnType: 'object', returnDescription: 'Response with status, headers, data', example: 'http.get "https://api.example.com/data"'
182
+ },
183
+ post: {
184
+ description: 'HTTP POST request',
185
+ parameters: [
186
+ { name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true },
187
+ { name: 'body', dataType: 'any', description: 'Request body (object auto-serialized to JSON)', formInputType: 'json', required: false },
188
+ { name: 'headers', dataType: 'object', description: 'Request headers', formInputType: 'json', required: false }
189
+ ],
190
+ returnType: 'object', returnDescription: 'Response with status, headers, data', example: 'http.post "https://api.example.com/data" {"key": "value"}'
191
+ },
192
+ put: {
193
+ description: 'HTTP PUT request',
194
+ parameters: [
195
+ { name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true },
196
+ { name: 'body', dataType: 'any', description: 'Request body', formInputType: 'json', required: false },
197
+ { name: 'headers', dataType: 'object', description: 'Headers', formInputType: 'json', required: false }
198
+ ],
199
+ returnType: 'object', returnDescription: 'Response', example: 'http.put "https://api.example.com/data/1" {"key": "new"}'
200
+ },
201
+ patch: {
202
+ description: 'HTTP PATCH request',
203
+ parameters: [
204
+ { name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true },
205
+ { name: 'body', dataType: 'any', description: 'Request body', formInputType: 'json', required: false },
206
+ { name: 'headers', dataType: 'object', description: 'Headers', formInputType: 'json', required: false }
207
+ ],
208
+ returnType: 'object', returnDescription: 'Response', example: 'http.patch "https://api.example.com/data/1" {"key": "updated"}'
209
+ },
210
+ delete: {
211
+ description: 'HTTP DELETE request',
212
+ parameters: [
213
+ { name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true },
214
+ { name: 'body', dataType: 'any', description: 'Request body', formInputType: 'json', required: false },
215
+ { name: 'headers', dataType: 'object', description: 'Headers', formInputType: 'json', required: false }
216
+ ],
217
+ returnType: 'object', returnDescription: 'Response', example: 'http.delete "https://api.example.com/data/1"'
218
+ },
219
+ head: {
220
+ description: 'HTTP HEAD request (headers only)',
221
+ parameters: [{ name: 'url', dataType: 'string', description: 'URL', formInputType: 'text', required: true }],
222
+ returnType: 'object', returnDescription: 'Response with status and headers', example: 'http.head "https://example.com"'
223
+ },
224
+ request: {
225
+ description: 'Custom HTTP request with full options',
226
+ parameters: [{ name: 'options', dataType: 'object', description: 'Object with url, method, body, headers', formInputType: 'json', required: true }],
227
+ returnType: 'object', returnDescription: 'Response', example: 'http.request {"url": "https://api.example.com", "method": "POST", "body": {"key": 1}}'
228
+ },
229
+ serve: {
230
+ description: 'Start an HTTP server',
231
+ parameters: [
232
+ { name: 'port', dataType: 'number', description: 'Port to listen on (default: 3000)', formInputType: 'number', required: true },
233
+ { name: 'host', dataType: 'string', description: 'Host (default: 0.0.0.0)', formInputType: 'text', required: false, defaultValue: '0.0.0.0' }
234
+ ],
235
+ returnType: 'string', returnDescription: 'Server handle ID', example: 'http.serve 8080'
236
+ },
237
+ close: {
238
+ description: 'Close an HTTP server',
239
+ parameters: [{ name: 'serverId', dataType: 'string', description: 'Server handle ID', formInputType: 'text', required: true }],
240
+ returnType: 'boolean', returnDescription: 'true if closed', example: 'http.close $serverId'
241
+ },
242
+ servers: {
243
+ description: 'List all running HTTP servers',
244
+ parameters: [],
245
+ returnType: 'array', returnDescription: 'Array of server IDs', example: 'http.servers'
246
+ },
247
+ download: {
248
+ description: 'Download a file from a URL',
249
+ parameters: [
250
+ { name: 'url', dataType: 'string', description: 'URL to download', formInputType: 'text', required: true },
251
+ { name: 'path', dataType: 'string', description: 'Local file path to save', formInputType: 'text', required: true }
252
+ ],
253
+ returnType: 'object', returnDescription: 'Object with size, path, status', example: 'http.download "https://example.com/file.zip" "file.zip"'
254
+ }
255
+ };
256
+
257
+ export const HttpModuleMetadata = {
258
+ description: 'HTTP client and server: GET, POST, PUT, DELETE, serve, download',
259
+ methods: Object.keys(HttpFunctions)
260
+ };
261
+
262
+ export default {
263
+ name: 'http',
264
+ functions: HttpFunctions,
265
+ functionMetadata: HttpFunctionMetadata,
266
+ moduleMetadata: HttpModuleMetadata,
267
+ global: false
268
+ };