@robinpath/cli 1.73.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/tls.js ADDED
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Native TLS module for RobinPath.
3
+ * TLS/SSL secure connections — required by databases, email, websockets.
4
+ */
5
+ import { connect as _tlsConnect, createServer as _tlsCreateServer, TLSSocket } from 'node:tls';
6
+ import { readFileSync } from 'node:fs';
7
+ import { resolve } from 'node:path';
8
+ import { toStr, toNum, requireArgs } from './_helpers.js';
9
+
10
+ const _sockets = new Map();
11
+ const _servers = new Map();
12
+ let _nextId = 1;
13
+
14
+ export const TlsFunctions = {
15
+
16
+ connect: (args) => {
17
+ requireArgs('tls.connect', args, 2);
18
+ const host = toStr(args[0]);
19
+ const port = toNum(args[1]);
20
+ const opts = args[2] && typeof args[2] === 'object' ? args[2] : {};
21
+ const id = `tls_${_nextId++}`;
22
+
23
+ const tlsOpts = {
24
+ host,
25
+ port,
26
+ rejectUnauthorized: opts.rejectUnauthorized !== false,
27
+ };
28
+
29
+ // Optional client certificates
30
+ if (opts.cert) tlsOpts.cert = readFileSync(resolve(toStr(opts.cert)));
31
+ if (opts.key) tlsOpts.key = readFileSync(resolve(toStr(opts.key)));
32
+ if (opts.ca) tlsOpts.ca = readFileSync(resolve(toStr(opts.ca)));
33
+ if (opts.servername) tlsOpts.servername = toStr(opts.servername);
34
+ if (opts.minVersion) tlsOpts.minVersion = toStr(opts.minVersion);
35
+ if (opts.maxVersion) tlsOpts.maxVersion = toStr(opts.maxVersion);
36
+
37
+ return new Promise((resolve, reject) => {
38
+ const socket = _tlsConnect(tlsOpts, () => {
39
+ _sockets.set(id, { socket, data: '' });
40
+ socket.on('data', (chunk) => {
41
+ const entry = _sockets.get(id);
42
+ if (entry) entry.data += chunk.toString();
43
+ });
44
+ socket.on('end', () => { _sockets.delete(id); });
45
+ socket.on('error', () => { _sockets.delete(id); });
46
+ resolve(id);
47
+ });
48
+ socket.on('error', (err) => reject(new Error(`tls.connect: ${err.message}`)));
49
+ });
50
+ },
51
+
52
+ send: (args) => {
53
+ requireArgs('tls.send', args, 2);
54
+ const id = toStr(args[0]);
55
+ const data = toStr(args[1]);
56
+ const entry = _sockets.get(id);
57
+ if (!entry) throw new Error(`tls.send: socket ${id} not found`);
58
+ return new Promise((resolve, reject) => {
59
+ entry.socket.write(data, (err) => {
60
+ if (err) reject(err);
61
+ else resolve(true);
62
+ });
63
+ });
64
+ },
65
+
66
+ read: (args) => {
67
+ requireArgs('tls.read', args, 1);
68
+ const id = toStr(args[0]);
69
+ const entry = _sockets.get(id);
70
+ if (!entry) throw new Error(`tls.read: socket ${id} not found`);
71
+ const data = entry.data;
72
+ entry.data = '';
73
+ return data;
74
+ },
75
+
76
+ close: (args) => {
77
+ requireArgs('tls.close', args, 1);
78
+ const id = toStr(args[0]);
79
+ const entry = _sockets.get(id);
80
+ if (entry) {
81
+ entry.socket.destroy();
82
+ _sockets.delete(id);
83
+ return true;
84
+ }
85
+ const server = _servers.get(id);
86
+ if (server) {
87
+ server.close();
88
+ _servers.delete(id);
89
+ return true;
90
+ }
91
+ return false;
92
+ },
93
+
94
+ createServer: (args, callback) => {
95
+ requireArgs('tls.createServer', args, 1);
96
+ const opts = args[0];
97
+ if (typeof opts !== 'object' || opts === null) {
98
+ throw new Error('tls.createServer requires options: {port, cert, key}');
99
+ }
100
+ const port = toNum(opts.port, 443);
101
+ const host = toStr(opts.host || '0.0.0.0');
102
+ const id = `tlss_${_nextId++}`;
103
+
104
+ const serverOpts = {};
105
+ if (opts.cert) serverOpts.cert = readFileSync(resolve(toStr(opts.cert)));
106
+ if (opts.key) serverOpts.key = readFileSync(resolve(toStr(opts.key)));
107
+ if (opts.ca) serverOpts.ca = readFileSync(resolve(toStr(opts.ca)));
108
+ if (opts.requestCert) serverOpts.requestCert = true;
109
+
110
+ const server = _tlsCreateServer(serverOpts, (socket) => {
111
+ const connId = `tlsc_${_nextId++}`;
112
+ _sockets.set(connId, { socket, data: '' });
113
+ socket.on('data', (chunk) => {
114
+ const entry = _sockets.get(connId);
115
+ if (entry) entry.data += chunk.toString();
116
+ if (callback) callback([connId, chunk.toString()]);
117
+ });
118
+ socket.on('end', () => { _sockets.delete(connId); });
119
+ socket.on('error', () => { _sockets.delete(connId); });
120
+ });
121
+
122
+ server.listen(port, host);
123
+ _servers.set(id, server);
124
+ return id;
125
+ },
126
+
127
+ getCertificate: (args) => {
128
+ requireArgs('tls.getCertificate', args, 1);
129
+ const id = toStr(args[0]);
130
+ const entry = _sockets.get(id);
131
+ if (!entry) throw new Error(`tls.getCertificate: socket ${id} not found`);
132
+ const cert = entry.socket.getPeerCertificate();
133
+ return {
134
+ subject: cert.subject,
135
+ issuer: cert.issuer,
136
+ validFrom: cert.valid_from,
137
+ validTo: cert.valid_to,
138
+ fingerprint: cert.fingerprint,
139
+ fingerprint256: cert.fingerprint256,
140
+ serialNumber: cert.serialNumber
141
+ };
142
+ },
143
+
144
+ getPeerCertificate: (args) => {
145
+ requireArgs('tls.getPeerCertificate', args, 1);
146
+ const id = toStr(args[0]);
147
+ const entry = _sockets.get(id);
148
+ if (!entry) throw new Error(`tls.getPeerCertificate: socket ${id} not found`);
149
+ const cert = entry.socket.getPeerCertificate(true);
150
+ return {
151
+ subject: cert.subject,
152
+ issuer: cert.issuer,
153
+ validFrom: cert.valid_from,
154
+ validTo: cert.valid_to,
155
+ fingerprint: cert.fingerprint,
156
+ fingerprint256: cert.fingerprint256,
157
+ serialNumber: cert.serialNumber,
158
+ raw: cert.raw ? cert.raw.toString('base64') : null
159
+ };
160
+ },
161
+
162
+ isEncrypted: (args) => {
163
+ requireArgs('tls.isEncrypted', args, 1);
164
+ const id = toStr(args[0]);
165
+ const entry = _sockets.get(id);
166
+ if (!entry) return false;
167
+ return entry.socket.encrypted === true;
168
+ },
169
+
170
+ getProtocol: (args) => {
171
+ requireArgs('tls.getProtocol', args, 1);
172
+ const id = toStr(args[0]);
173
+ const entry = _sockets.get(id);
174
+ if (!entry) throw new Error(`tls.getProtocol: socket ${id} not found`);
175
+ return entry.socket.getProtocol();
176
+ },
177
+
178
+ getCipher: (args) => {
179
+ requireArgs('tls.getCipher', args, 1);
180
+ const id = toStr(args[0]);
181
+ const entry = _sockets.get(id);
182
+ if (!entry) throw new Error(`tls.getCipher: socket ${id} not found`);
183
+ return entry.socket.getCipher();
184
+ },
185
+
186
+ active: () => ({
187
+ sockets: Array.from(_sockets.keys()),
188
+ servers: Array.from(_servers.keys())
189
+ })
190
+ };
191
+
192
+ export const TlsFunctionMetadata = {
193
+ connect: {
194
+ description: 'Create a TLS/SSL connection',
195
+ parameters: [
196
+ { name: 'host', dataType: 'string', description: 'Hostname', formInputType: 'text', required: true },
197
+ { name: 'port', dataType: 'number', description: 'Port number', formInputType: 'number', required: true },
198
+ { name: 'options', dataType: 'object', description: 'TLS options: cert, key, ca, rejectUnauthorized, servername', formInputType: 'json', required: false }
199
+ ],
200
+ returnType: 'string', returnDescription: 'TLS socket handle ID', example: 'tls.connect "smtp.gmail.com" 465'
201
+ },
202
+ send: {
203
+ description: 'Send data over TLS socket',
204
+ parameters: [
205
+ { name: 'socketId', dataType: 'string', description: 'Socket handle', formInputType: 'text', required: true },
206
+ { name: 'data', dataType: 'string', description: 'Data to send', formInputType: 'text', required: true }
207
+ ],
208
+ returnType: 'boolean', returnDescription: 'true on success', example: 'tls.send $sock "EHLO example.com"'
209
+ },
210
+ read: {
211
+ description: 'Read buffered TLS data',
212
+ parameters: [{ name: 'socketId', dataType: 'string', description: 'Socket handle', formInputType: 'text', required: true }],
213
+ returnType: 'string', returnDescription: 'Buffered data', example: 'tls.read $sock'
214
+ },
215
+ close: {
216
+ description: 'Close TLS socket or server',
217
+ parameters: [{ name: 'id', dataType: 'string', description: 'Socket or server handle', formInputType: 'text', required: true }],
218
+ returnType: 'boolean', returnDescription: 'true if closed', example: 'tls.close $sock'
219
+ },
220
+ createServer: {
221
+ description: 'Create a TLS server',
222
+ parameters: [{ name: 'options', dataType: 'object', description: 'Server options: port, cert, key, ca', formInputType: 'json', required: true }],
223
+ returnType: 'string', returnDescription: 'Server handle ID', example: 'tls.createServer {"port": 443, "cert": "cert.pem", "key": "key.pem"}'
224
+ },
225
+ getCertificate: {
226
+ description: 'Get peer TLS certificate info',
227
+ parameters: [{ name: 'socketId', dataType: 'string', description: 'Socket handle', formInputType: 'text', required: true }],
228
+ returnType: 'object', returnDescription: 'Certificate details', example: 'tls.getCertificate $sock'
229
+ },
230
+ getPeerCertificate: {
231
+ description: 'Get full peer certificate with raw data',
232
+ parameters: [{ name: 'socketId', dataType: 'string', description: 'Socket handle', formInputType: 'text', required: true }],
233
+ returnType: 'object', returnDescription: 'Full certificate object', example: 'tls.getPeerCertificate $sock'
234
+ },
235
+ isEncrypted: {
236
+ description: 'Check if socket is TLS encrypted',
237
+ parameters: [{ name: 'socketId', dataType: 'string', description: 'Socket handle', formInputType: 'text', required: true }],
238
+ returnType: 'boolean', returnDescription: 'true if encrypted', example: 'tls.isEncrypted $sock'
239
+ },
240
+ getProtocol: {
241
+ description: 'Get TLS protocol version (e.g. TLSv1.3)',
242
+ parameters: [{ name: 'socketId', dataType: 'string', description: 'Socket handle', formInputType: 'text', required: true }],
243
+ returnType: 'string', returnDescription: 'Protocol version string', example: 'tls.getProtocol $sock'
244
+ },
245
+ getCipher: {
246
+ description: 'Get current cipher info',
247
+ parameters: [{ name: 'socketId', dataType: 'string', description: 'Socket handle', formInputType: 'text', required: true }],
248
+ returnType: 'object', returnDescription: 'Cipher name and version', example: 'tls.getCipher $sock'
249
+ },
250
+ active: { description: 'List active TLS sockets and servers', parameters: [], returnType: 'object', returnDescription: '{sockets, servers}', example: 'tls.active' }
251
+ };
252
+
253
+ export const TlsModuleMetadata = {
254
+ description: 'TLS/SSL: secure connections, certificates, encrypted client/server sockets',
255
+ methods: Object.keys(TlsFunctions)
256
+ };
257
+
258
+ export default {
259
+ name: 'tls',
260
+ functions: TlsFunctions,
261
+ functionMetadata: TlsFunctionMetadata,
262
+ moduleMetadata: TlsModuleMetadata,
263
+ global: false
264
+ };
package/modules/tty.js ADDED
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Native tty module for RobinPath.
3
+ * Terminal/TTY detection and capabilities — needed by supports-color, chalk, etc.
4
+ */
5
+ import { isatty } from 'node:tty';
6
+ import { toNum, requireArgs } from './_helpers.js';
7
+
8
+ export const TtyFunctions = {
9
+
10
+ isatty: (args) => {
11
+ requireArgs('tty.isatty', args, 1);
12
+ const fd = toNum(args[0], 1);
13
+ return isatty(fd);
14
+ },
15
+
16
+ isStdinTTY: () => process.stdin?.isTTY === true,
17
+
18
+ isStdoutTTY: () => process.stdout?.isTTY === true,
19
+
20
+ isStderrTTY: () => process.stderr?.isTTY === true,
21
+
22
+ columns: () => process.stdout?.columns || 80,
23
+
24
+ rows: () => process.stdout?.rows || 24,
25
+
26
+ size: () => ({
27
+ columns: process.stdout?.columns || 80,
28
+ rows: process.stdout?.rows || 24
29
+ }),
30
+
31
+ hasColors: (args) => {
32
+ const count = args[0] ? toNum(args[0], 16) : 16;
33
+ if (process.stdout?.hasColors) {
34
+ return process.stdout.hasColors(count);
35
+ }
36
+ // Fallback: detect from env
37
+ const env = process.env;
38
+ if (env.NO_COLOR) return false;
39
+ if (env.FORCE_COLOR) return true;
40
+ if (env.TERM === 'dumb') return false;
41
+ if (process.platform === 'win32') return true;
42
+ if (env.CI) return true;
43
+ if (env.COLORTERM === 'truecolor' || env.COLORTERM === '24bit') return count <= 16777216;
44
+ if (env.TERM_PROGRAM === 'iTerm.app') return count <= 256;
45
+ if (/256color/i.test(env.TERM || '')) return count <= 256;
46
+ return count <= 16;
47
+ },
48
+
49
+ colorDepth: () => {
50
+ if (process.stdout?.getColorDepth) {
51
+ return process.stdout.getColorDepth();
52
+ }
53
+ const env = process.env;
54
+ if (env.NO_COLOR) return 1;
55
+ if (env.COLORTERM === 'truecolor' || env.COLORTERM === '24bit') return 24;
56
+ if (process.platform === 'win32') return 4;
57
+ if (/256color/i.test(env.TERM || '')) return 8;
58
+ return 4;
59
+ },
60
+
61
+ supportsColor: () => {
62
+ const env = process.env;
63
+ if (env.NO_COLOR) return false;
64
+ if (env.FORCE_COLOR) return true;
65
+ if (env.TERM === 'dumb') return false;
66
+ if (process.platform === 'win32') return true;
67
+ if (process.stdout?.isTTY) return true;
68
+ if (env.CI) return true;
69
+ return false;
70
+ },
71
+
72
+ getWindowSize: () => {
73
+ if (process.stdout?.getWindowSize) {
74
+ const [cols, rows] = process.stdout.getWindowSize();
75
+ return { columns: cols, rows };
76
+ }
77
+ return {
78
+ columns: process.stdout?.columns || 80,
79
+ rows: process.stdout?.rows || 24
80
+ };
81
+ },
82
+
83
+ clearLine: (args) => {
84
+ const dir = args[0] ? toNum(args[0], 0) : 0;
85
+ if (process.stdout?.clearLine) {
86
+ process.stdout.clearLine(dir);
87
+ return true;
88
+ }
89
+ return false;
90
+ },
91
+
92
+ cursorTo: (args) => {
93
+ requireArgs('tty.cursorTo', args, 1);
94
+ const x = toNum(args[0], 0);
95
+ const y = args[1] != null ? toNum(args[1]) : undefined;
96
+ if (process.stdout?.cursorTo) {
97
+ process.stdout.cursorTo(x, y);
98
+ return true;
99
+ }
100
+ return false;
101
+ },
102
+
103
+ moveCursor: (args) => {
104
+ requireArgs('tty.moveCursor', args, 2);
105
+ const dx = toNum(args[0], 0);
106
+ const dy = toNum(args[1], 0);
107
+ if (process.stdout?.moveCursor) {
108
+ process.stdout.moveCursor(dx, dy);
109
+ return true;
110
+ }
111
+ return false;
112
+ }
113
+ };
114
+
115
+ export const TtyFunctionMetadata = {
116
+ isatty: {
117
+ description: 'Check if a file descriptor is a TTY',
118
+ parameters: [{ name: 'fd', dataType: 'number', description: 'File descriptor (0=stdin, 1=stdout, 2=stderr)', formInputType: 'number', required: true }],
119
+ returnType: 'boolean', returnDescription: 'true if TTY', example: 'tty.isatty 1'
120
+ },
121
+ isStdinTTY: { description: 'Check if stdin is a TTY', parameters: [], returnType: 'boolean', returnDescription: 'true if TTY', example: 'tty.isStdinTTY' },
122
+ isStdoutTTY: { description: 'Check if stdout is a TTY', parameters: [], returnType: 'boolean', returnDescription: 'true if TTY', example: 'tty.isStdoutTTY' },
123
+ isStderrTTY: { description: 'Check if stderr is a TTY', parameters: [], returnType: 'boolean', returnDescription: 'true if TTY', example: 'tty.isStderrTTY' },
124
+ columns: { description: 'Get terminal width in columns', parameters: [], returnType: 'number', returnDescription: 'Column count', example: 'tty.columns' },
125
+ rows: { description: 'Get terminal height in rows', parameters: [], returnType: 'number', returnDescription: 'Row count', example: 'tty.rows' },
126
+ size: { description: 'Get terminal size {columns, rows}', parameters: [], returnType: 'object', returnDescription: '{columns, rows}', example: 'tty.size' },
127
+ hasColors: {
128
+ description: 'Check if terminal supports N colors',
129
+ parameters: [{ name: 'count', dataType: 'number', description: 'Number of colors to check (default: 16)', formInputType: 'number', required: false, defaultValue: '16' }],
130
+ returnType: 'boolean', returnDescription: 'true if supported', example: 'tty.hasColors 256'
131
+ },
132
+ colorDepth: { description: 'Get terminal color depth in bits', parameters: [], returnType: 'number', returnDescription: 'Color depth (1, 4, 8, or 24)', example: 'tty.colorDepth' },
133
+ supportsColor: { description: 'Check if terminal supports color output', parameters: [], returnType: 'boolean', returnDescription: 'true if color supported', example: 'tty.supportsColor' },
134
+ getWindowSize: { description: 'Get terminal window size', parameters: [], returnType: 'object', returnDescription: '{columns, rows}', example: 'tty.getWindowSize' },
135
+ clearLine: {
136
+ description: 'Clear the current terminal line',
137
+ parameters: [{ name: 'direction', dataType: 'number', description: '-1=left, 0=entire, 1=right', formInputType: 'number', required: false, defaultValue: '0' }],
138
+ returnType: 'boolean', returnDescription: 'true if cleared', example: 'tty.clearLine 0'
139
+ },
140
+ cursorTo: {
141
+ description: 'Move cursor to position',
142
+ parameters: [
143
+ { name: 'x', dataType: 'number', description: 'Column position', formInputType: 'number', required: true },
144
+ { name: 'y', dataType: 'number', description: 'Row position', formInputType: 'number', required: false }
145
+ ],
146
+ returnType: 'boolean', returnDescription: 'true if moved', example: 'tty.cursorTo 0 5'
147
+ },
148
+ moveCursor: {
149
+ description: 'Move cursor relative to current position',
150
+ parameters: [
151
+ { name: 'dx', dataType: 'number', description: 'Horizontal offset', formInputType: 'number', required: true },
152
+ { name: 'dy', dataType: 'number', description: 'Vertical offset', formInputType: 'number', required: true }
153
+ ],
154
+ returnType: 'boolean', returnDescription: 'true if moved', example: 'tty.moveCursor 1 -1'
155
+ }
156
+ };
157
+
158
+ export const TtyModuleMetadata = {
159
+ description: 'TTY: terminal detection, color support, cursor control, window size',
160
+ methods: Object.keys(TtyFunctions)
161
+ };
162
+
163
+ export default {
164
+ name: 'tty',
165
+ functions: TtyFunctions,
166
+ functionMetadata: TtyFunctionMetadata,
167
+ moduleMetadata: TtyModuleMetadata,
168
+ global: false
169
+ };
package/modules/url.js ADDED
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Native URL module for RobinPath.
3
+ * URL parsing, formatting, and encoding utilities.
4
+ */
5
+ import { toStr, requireArgs } from './_helpers.js';
6
+
7
+ export const UrlFunctions = {
8
+
9
+ parse: (args) => {
10
+ requireArgs('url.parse', args, 1);
11
+ const urlStr = toStr(args[0]);
12
+ const u = new URL(urlStr);
13
+ return {
14
+ href: u.href,
15
+ protocol: u.protocol,
16
+ hostname: u.hostname,
17
+ host: u.host,
18
+ port: u.port || null,
19
+ pathname: u.pathname,
20
+ search: u.search,
21
+ hash: u.hash,
22
+ origin: u.origin,
23
+ username: u.username,
24
+ password: u.password
25
+ };
26
+ },
27
+
28
+ format: (args) => {
29
+ requireArgs('url.format', args, 1);
30
+ const obj = args[0];
31
+ if (typeof obj === 'string') return obj;
32
+ if (typeof obj !== 'object' || obj === null) {
33
+ throw new Error('url.format requires a URL object or string');
34
+ }
35
+ const u = new URL(obj.href || `${obj.protocol || 'https:'}//${obj.hostname || 'localhost'}`);
36
+ if (obj.port) u.port = String(obj.port);
37
+ if (obj.pathname) u.pathname = obj.pathname;
38
+ if (obj.search) u.search = obj.search;
39
+ if (obj.hash) u.hash = obj.hash;
40
+ if (obj.username) u.username = obj.username;
41
+ if (obj.password) u.password = obj.password;
42
+ return u.href;
43
+ },
44
+
45
+ resolve: (args) => {
46
+ requireArgs('url.resolve', args, 2);
47
+ const base = toStr(args[0]);
48
+ const relative = toStr(args[1]);
49
+ return new URL(relative, base).href;
50
+ },
51
+
52
+ searchParams: (args) => {
53
+ requireArgs('url.searchParams', args, 1);
54
+ const urlStr = toStr(args[0]);
55
+ const u = new URL(urlStr);
56
+ const result = {};
57
+ for (const [key, value] of u.searchParams) {
58
+ result[key] = value;
59
+ }
60
+ return result;
61
+ },
62
+
63
+ buildQuery: (args) => {
64
+ requireArgs('url.buildQuery', args, 1);
65
+ const obj = args[0];
66
+ if (typeof obj !== 'object' || obj === null) {
67
+ throw new Error('url.buildQuery requires an object');
68
+ }
69
+ const params = new URLSearchParams();
70
+ for (const [key, value] of Object.entries(obj)) {
71
+ params.append(key, String(value));
72
+ }
73
+ return params.toString();
74
+ },
75
+
76
+ encode: (args) => {
77
+ requireArgs('url.encode', args, 1);
78
+ return encodeURIComponent(toStr(args[0]));
79
+ },
80
+
81
+ decode: (args) => {
82
+ requireArgs('url.decode', args, 1);
83
+ return decodeURIComponent(toStr(args[0]));
84
+ },
85
+
86
+ encodeFull: (args) => {
87
+ requireArgs('url.encodeFull', args, 1);
88
+ return encodeURI(toStr(args[0]));
89
+ },
90
+
91
+ decodeFull: (args) => {
92
+ requireArgs('url.decodeFull', args, 1);
93
+ return decodeURI(toStr(args[0]));
94
+ },
95
+
96
+ isValid: (args) => {
97
+ requireArgs('url.isValid', args, 1);
98
+ try {
99
+ new URL(toStr(args[0]));
100
+ return true;
101
+ } catch {
102
+ return false;
103
+ }
104
+ },
105
+
106
+ join: (args) => {
107
+ requireArgs('url.join', args, 2);
108
+ const base = toStr(args[0]).replace(/\/+$/, '');
109
+ const parts = args.slice(1).map(a => toStr(a).replace(/^\/+|\/+$/g, ''));
110
+ return base + '/' + parts.join('/');
111
+ }
112
+ };
113
+
114
+ export const UrlFunctionMetadata = {
115
+ parse: {
116
+ description: 'Parse a URL into components',
117
+ parameters: [{ name: 'url', dataType: 'string', description: 'URL string', formInputType: 'text', required: true }],
118
+ returnType: 'object', returnDescription: 'Object with protocol, hostname, port, pathname, search, hash', example: 'url.parse "https://example.com/path?q=1"'
119
+ },
120
+ format: {
121
+ description: 'Format a URL object into a string',
122
+ parameters: [{ name: 'urlObject', dataType: 'object', description: 'URL components object', formInputType: 'json', required: true }],
123
+ returnType: 'string', returnDescription: 'Formatted URL string', example: 'url.format $urlObj'
124
+ },
125
+ resolve: {
126
+ description: 'Resolve a relative URL against a base',
127
+ parameters: [
128
+ { name: 'base', dataType: 'string', description: 'Base URL', formInputType: 'text', required: true },
129
+ { name: 'relative', dataType: 'string', description: 'Relative URL', formInputType: 'text', required: true }
130
+ ],
131
+ returnType: 'string', returnDescription: 'Resolved URL', example: 'url.resolve "https://example.com" "/path"'
132
+ },
133
+ searchParams: {
134
+ description: 'Extract query parameters as an object',
135
+ parameters: [{ name: 'url', dataType: 'string', description: 'URL string', formInputType: 'text', required: true }],
136
+ returnType: 'object', returnDescription: 'Key-value object of query parameters', example: 'url.searchParams "https://example.com?a=1&b=2"'
137
+ },
138
+ buildQuery: {
139
+ description: 'Build a query string from an object',
140
+ parameters: [{ name: 'params', dataType: 'object', description: 'Key-value pairs', formInputType: 'json', required: true }],
141
+ returnType: 'string', returnDescription: 'Query string (without ?)', example: 'url.buildQuery {"a": 1, "b": 2}'
142
+ },
143
+ encode: {
144
+ description: 'URL-encode a string component',
145
+ parameters: [{ name: 'value', dataType: 'string', description: 'String to encode', formInputType: 'text', required: true }],
146
+ returnType: 'string', returnDescription: 'Encoded string', example: 'url.encode "hello world"'
147
+ },
148
+ decode: {
149
+ description: 'URL-decode a string component',
150
+ parameters: [{ name: 'value', dataType: 'string', description: 'String to decode', formInputType: 'text', required: true }],
151
+ returnType: 'string', returnDescription: 'Decoded string', example: 'url.decode "hello%20world"'
152
+ },
153
+ encodeFull: {
154
+ description: 'Encode a full URI',
155
+ parameters: [{ name: 'uri', dataType: 'string', description: 'URI to encode', formInputType: 'text', required: true }],
156
+ returnType: 'string', returnDescription: 'Encoded URI', example: 'url.encodeFull "https://example.com/path with spaces"'
157
+ },
158
+ decodeFull: {
159
+ description: 'Decode a full URI',
160
+ parameters: [{ name: 'uri', dataType: 'string', description: 'URI to decode', formInputType: 'text', required: true }],
161
+ returnType: 'string', returnDescription: 'Decoded URI', example: 'url.decodeFull "https://example.com/path%20with%20spaces"'
162
+ },
163
+ isValid: {
164
+ description: 'Check if a string is a valid URL',
165
+ parameters: [{ name: 'url', dataType: 'string', description: 'String to check', formInputType: 'text', required: true }],
166
+ returnType: 'boolean', returnDescription: 'true if valid URL', example: 'url.isValid "https://example.com"'
167
+ },
168
+ join: {
169
+ description: 'Join URL path segments',
170
+ parameters: [
171
+ { name: 'base', dataType: 'string', description: 'Base URL', formInputType: 'text', required: true },
172
+ { name: 'segments', dataType: 'string', description: 'Path segments', formInputType: 'text', required: true }
173
+ ],
174
+ returnType: 'string', returnDescription: 'Joined URL', example: 'url.join "https://api.example.com" "v1" "users"'
175
+ }
176
+ };
177
+
178
+ export const UrlModuleMetadata = {
179
+ description: 'URL parsing, formatting, encoding, and query string utilities',
180
+ methods: Object.keys(UrlFunctions)
181
+ };
182
+
183
+ export default {
184
+ name: 'url',
185
+ functions: UrlFunctions,
186
+ functionMetadata: UrlFunctionMetadata,
187
+ moduleMetadata: UrlModuleMetadata,
188
+ global: false
189
+ };