nep-cli 0.2.5 → 0.2.6

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const { program } = require('commander');
4
4
  const { version } = require('../package.json');
@@ -9,6 +9,7 @@ var nep_configuration = { current_port: 50200, IP: "0.0.0.0", brokers: {} }
9
9
  const { exec } = require('child_process');
10
10
  const { AutoComplete } = require('enquirer');
11
11
 
12
+
12
13
  const open = require('open'); // Import the 'open' package
13
14
 
14
15
  const express = require('express');
@@ -22,6 +23,39 @@ const PORT_MASTER_INFO = 50001; // Default port for master info
22
23
  const PORT_SERVER = 50050;
23
24
 
24
25
 
26
+ const JSON5 = require('json5');
27
+
28
+ // --- helpers local to this command (do NOT change selectTopic) ---
29
+ function normalizeResults(results) {
30
+ if (!Array.isArray(results?.input)) return results;
31
+ const normalized = results.input.map(item => {
32
+ if (typeof item === 'string') return item;
33
+ if (item && typeof item === 'object') {
34
+ // prefer common fields; last fallback prints the whole object as JSON
35
+ return item.topic || item.name || item.path || item.id || JSON.stringify(item);
36
+ }
37
+ return String(item);
38
+ });
39
+ return { ...results, input: normalized };
40
+ }
41
+
42
+ function inferTypeFromSuffix(topic, allowed) {
43
+ const last = String(topic).split('/').pop().toLowerCase();
44
+ return allowed.includes(last) ? last : null; // infer only; do NOT strip from topic
45
+ }
46
+
47
+ function parseFlexibleJSON(src) { try { return JSON.parse(src); } catch { return JSON5.parse(src); } }
48
+
49
+
50
+ async function readAllFromStdin() {
51
+ return await new Promise((resolve, reject) => {
52
+ let data = '';
53
+ process.stdin.setEncoding('utf8');
54
+ process.stdin.on('data', chunk => data += chunk);
55
+ process.stdin.on('end', () => resolve(data.trim()));
56
+ process.stdin.on('error', reject);
57
+ });
58
+ }
25
59
 
26
60
  program
27
61
  .version(version)
@@ -33,37 +67,76 @@ program
33
67
  .description('Display current Wi-Fi and Ethernet IP addresses of this computer')
34
68
  .action(() => {
35
69
  try {
36
- // subsitude by interfaces = nep.getNetworkInterfaces --> {"wifi":wifiInterfaces, "eth":ethernetInterface}
37
70
  const platform = os.platform();
38
- let wifiInterfaces, ethernetInterface;
39
-
40
- if (platform === 'linux') {
41
- wifiInterfaces = Object.keys(os.networkInterfaces()).filter(ifname => /^(wlan|wlp|wl|en)\d*$/i.test(ifname));
42
- ethernetInterface = Object.keys(os.networkInterfaces()).find(ifname => /^(eth|enp|eno)\d*$/i.test(ifname));
43
- } else if (platform === 'win32') {
44
- wifiInterfaces = Object.keys(os.networkInterfaces()).filter(ifname => /^(Wireless|Wi-Fi)/i.test(ifname));
45
- ethernetInterface = Object.keys(os.networkInterfaces()).find(ifname => /^Ethernet/i.test(ifname));
46
- } else if (platform === 'darwin') {
47
- wifiInterfaces = Object.keys(os.networkInterfaces()).filter(ifname => /^en\d*$/i.test(ifname));
48
- ethernetInterface = Object.keys(os.networkInterfaces()).find(ifname => /^en\d*$/i.test(ifname));
49
- } else {
50
- console.log('Unsupported operating system.');
51
- return;
71
+ const names = Object.keys(os.networkInterfaces());
72
+
73
+ // Name patterns (broad & locale-friendly)
74
+ const PATS = {
75
+ wifi: [
76
+ /wi-?fi/i, /wireless/i, /wlan/i,
77
+ /ワイヤレス/i, /無線/i
78
+ ],
79
+ eth: [
80
+ /ethernet/i, /イーサネット/i, /有線/i, /lan/i,
81
+ /^eth\d+/i, /^enp\d+/i, /^eno\d+/i, /^en\d+$/i // Linux/mac
82
+ ],
83
+ };
84
+
85
+ // Helper: get IPv4 addresses for a given interface name
86
+ const ipv4s = (ifname) =>
87
+ (os.networkInterfaces()[ifname] || [])
88
+ .filter(a => a.family === 'IPv4' && !a.internal)
89
+ .map(a => a.address);
90
+
91
+ // Matchers
92
+ const matchAny = (str, regexes) => regexes.some(r => r.test(str));
93
+
94
+ // Candidate sets
95
+ let wifiIfs = names.filter(n => matchAny(n, PATS.wifi));
96
+ let ethIfs = names.filter(n => matchAny(n, PATS.eth));
97
+
98
+ // Windows special: accept numbered “Ethernet 2 / イーサネット 2”
99
+ if (platform === 'win32') {
100
+ const winEthWide = /^(Ethernet|イーサネット)(\s*\d+)?$/i;
101
+ const winWiFiWide = /^(Wi-?Fi|Wireless|WLAN|ワイヤレス|無線)(\s*\d+)?$/i;
102
+ ethIfs = [...new Set([...ethIfs, ...names.filter(n => winEthWide.test(n))])];
103
+ wifiIfs = [...new Set([...wifiIfs, ...names.filter(n => winWiFiWide.test(n))])];
52
104
  }
53
105
 
54
- wifiInterfaces.forEach(wifiInterface => {
55
- const ipAddress = nep.getIPAddress(wifiInterface);
56
- nep.printIPAddress(`Wi-Fi (${wifiInterface})`, ipAddress);
106
+ // Print Wi-Fi(s)
107
+ let printed = false;
108
+ wifiIfs.forEach(ifn => {
109
+ const addrs = ipv4s(ifn);
110
+ addrs.forEach(ip => {
111
+ printed = true;
112
+ nep.printIPAddress(`Wi-Fi (${ifn})`, ip);
113
+ });
57
114
  });
58
115
 
59
- nep.printIPAddress('Ethernet', nep.getIPAddress(ethernetInterface));
60
- } catch (error) {
61
- console.error('An error occurred:', error.message);
116
+ // Print Ethernet(s)
117
+ ethIfs.forEach(ifn => {
118
+ const addrs = ipv4s(ifn);
119
+ addrs.forEach(ip => {
120
+ printed = true;
121
+ nep.printIPAddress(`Ethernet (${ifn})`, ip);
122
+ });
123
+ });
124
+
125
+ // Fallback: if nothing matched, dump all non-internal IPv4s
126
+ if (!printed) {
127
+ names.forEach(ifn => {
128
+ const addrs = ipv4s(ifn);
129
+ addrs.forEach(ip => nep.printIPAddress(`${ifn}`, ip));
130
+ });
131
+ }
132
+ } catch (err) {
133
+ console.error('An error occurred:', err.message);
62
134
  }
63
135
  });
64
136
 
65
137
 
66
138
 
139
+
67
140
  // Node saved here
68
141
  var nodes_register = {};
69
142
  // ----------- Main variables ---------------
@@ -784,52 +857,167 @@ program
784
857
  openSub(master_ip, topic, msg_type, callback);
785
858
  });
786
859
 
787
-
788
860
  program
789
- .command('publish <topic> <message>')
790
- .description('Publish a message to a NEP+ topic (only JSON messages are supported)')
791
- .action((topic, message) => {
861
+ .command('pub <topic>')
862
+ .description(
863
+ 'Publish JSON/JSON5 to a NEP+ topic.\n\n' +
864
+ 'Input can come from --json, --file, or piped stdin.\n\n' +
865
+ 'Examples:\n' +
866
+ ' nep pub test/json --json "{value:1, txt:\'hello\'}"\n' +
867
+ ' nep pub test/json --file payload.json5\n' +
868
+ ' echo {value:1, txt:"hello"} | nep pub test/json\n'
869
+ )
870
+ .option('-j, --json <text>', 'JSON/JSON5 string (allows single quotes, unquoted keys)')
871
+ .option('-f, --file <path>', 'Read payload from a file')
872
+ .option('--pretty', 'Pretty-print parsed JSON before publishing')
873
+ .action(async (topic, opts) => {
874
+ try {
875
+ let text = null;
792
876
 
793
- var openpub = function (master_ip = "127.0.0.1") {
877
+ if (opts.json && opts.file) {
878
+ console.error('Please provide only one of --json or --file (or use stdin).');
879
+ process.exit(2);
880
+ }
794
881
 
795
- var pubFunction = function () {
882
+ if (opts.json) {
883
+ text = opts.json;
884
+ } else if (opts.file) {
885
+ text = fs.readFileSync(opts.file, 'utf8');
886
+ } else if (!process.stdin.isTTY) {
887
+ text = await readAllFromStdin(); // supports: echo {...} | nep pubj test
888
+ } else {
889
+ console.error('No input provided. Use --json, --file, or pipe data via stdin.');
890
+ process.exit(2);
891
+ }
796
892
 
797
- var msg = message.replace(/'/g, '"')
798
- console.log(JSON.parse(msg))
799
- pub.publish(JSON.parse(msg))
800
- console.log("Message published")
893
+ let payload;
894
+ try {
895
+ payload = parseFlexibleJSON(text);
896
+ } catch (err) {
897
+ console.error('Failed to parse input as JSON/JSON5:');
898
+ console.error(err.message);
899
+ process.exit(2);
900
+ }
801
901
 
802
- process.exit(1)
902
+ if (opts.pretty) {
903
+ console.log('Parsed payload:');
904
+ console.log(JSON.stringify(payload, null, 2));
803
905
  }
804
906
 
907
+ const node = new nep.Node('nep-cli-pubj');
908
+ const pub = node.new_pub(topic, 'json');
805
909
 
806
- var node = new nep.Node("nep-cli-pub")
807
- var pub = node.new_pub(topic, "json")
808
- setTimeout(pubFunction, 1000)
910
+ // give a moment for transport to connect
911
+ setTimeout(() => {
912
+ pub.publish(payload);
913
+ console.log('Message published to', topic);
914
+ process.exit(0);
915
+ }, 500);
916
+ } catch (e) {
917
+ console.error('Error:', e.message);
918
+ process.exit(1);
809
919
  }
920
+ });
810
921
 
811
- async function run() {
812
- var requester = new zmq.Request;
813
- var master_ip = "127.0.0.1"
922
+
923
+ program
924
+ .command('publish [topic] [message...]')
925
+ .description(
926
+ 'Publish to a registered NEP+ topic.\n\n' +
927
+ 'Examples:\n' +
928
+ ' nep publish # select topic & type, then enter payload\n' +
929
+ ' nep publish test/json "{number:1, txt:\'hello\'}" # publishes TO topic "test/json"\n'
930
+ )
931
+ .action(async (topicArg, messageParts) => {
932
+ const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
933
+ const master_ip = "127.0.0.1";
814
934
 
815
- requester.connect("tcp://" + master_ip + ":" + PORT_MASTER_INFO);
935
+ // 1) Fetch topics from master
936
+ const requester = createRequester(master_ip, PORT_MASTER_INFO);
937
+ const resultsRaw = await sendRequest(requester, { "input": "topics" });
816
938
 
817
- let msg = { "input": "topics" }
818
- var message = JSON.stringify(msg);
819
- await requester.send(message)
820
- const [result] = await requester.receive()
821
- var results = JSON.parse(result.toString())
822
- //console.log(results);
823
- if (results["input"].includes(topic)) {
824
- console.log("")
825
- openpub(master_ip)
826
- }
827
- else {
828
- console.log("Topic is not registered");
939
+ // 2) Normalize ONLY for this command (leave selectTopic untouched elsewhere)
940
+ const results = normalizeResults(resultsRaw);
941
+ console.log(results);
942
+
943
+ // 3) Let the user select a registered topic (reuses your existing selectTopic)
944
+ const selectedTopic = await selectTopic(results, topicArg);
945
+
946
+
947
+
948
+ // 5) Determine message type
949
+ let msg_type = inferTypeFromSuffix(selectedTopic, allowedFormats);
950
+ if (!msg_type) {
951
+ msg_type = await selectMsgType(allowedFormats, null, selectedTopic);
952
+ }
953
+
954
+ // 6) Acquire payload (CLI arg or prompt)
955
+ let messageText = Array.isArray(messageParts) ? messageParts.join(' ') : (messageParts || '');
956
+ if (!messageText || messageText.trim() === '') {
957
+ const prompt = new Input({
958
+ name: 'payload',
959
+ message:
960
+ msg_type === 'string' ? 'Enter STRING payload:'
961
+ : msg_type === 'bytes' ? 'Enter BYTES payload (base64-encoded):'
962
+ : `Enter ${msg_type.toUpperCase()} payload (JSON/JSON5 allowed):`
963
+ });
964
+ messageText = await prompt.run();
965
+ }
966
+ const src = messageText.trim();
967
+
968
+ // 7) Build payload by type
969
+ let payload;
970
+ try {
971
+ if (msg_type === 'string') {
972
+ payload = src;
973
+ } else if (msg_type === 'bytes') {
974
+ payload = Buffer.from(src, 'base64');
975
+ } else {
976
+ // json / dictionary / msgpack / images → parse as JSON/JSON5
977
+ payload = parseFlexibleJSON(src);
829
978
  }
979
+ } catch (e) {
980
+ console.error('Failed to parse payload. Tip: use `nep pubj` for stdin/files.\n', e.message);
981
+ process.exit(2);
830
982
  }
831
- run()
983
+
984
+ // 8) Publish to the EXACT selected topic (no stripping of suffix)
985
+ const node = new nep.Node('nep-cli-pub');
986
+ const conf = node.hybrid(master_ip);
987
+ const pub = node.new_pub(selectedTopic, msg_type, conf);
988
+
989
+ setTimeout(() => {
990
+ pub.publish(payload);
991
+ console.log(`Published to "${selectedTopic}" as ${msg_type}`);
992
+ process.exit(0);
993
+ }, 500);
832
994
  });
995
+
996
+ program
997
+ .command('test [master_ip]')
998
+ .description('Publish "hello world" to the specified IP. If no IP is provided, the default is 127.0.0.1')
999
+ .action((master_ip = "127.0.0.1") => {
1000
+
1001
+ var openpub = function (master_ip) {
1002
+
1003
+ var pubFunction = function () {
1004
+
1005
+ var msg = { "message": "hello world" }
1006
+ console.log("Messgae published: " + JSON.stringify(msg) + " to " + master_ip)
1007
+ pub.publish(msg)
1008
+
1009
+ process.exit(1)
1010
+ }
1011
+
1012
+ var node = new nep.Node("nep-cli-test")
1013
+ const conf = node.hybrid(master_ip);
1014
+ var pub = node.new_pub("test", "json", conf)
1015
+ setTimeout(pubFunction, 1000)
1016
+ }
1017
+
1018
+ openpub(master_ip)
1019
+
1020
+ });
833
1021
 
834
1022
 
835
1023
  const { Input } = require('enquirer');
Binary file
package/package.json CHANGED
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "name": "nep-cli",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "main": "./lib/nep.js",
5
5
  "bin": {
6
- "nep": "./bin/index.js"
6
+ "nep": "bin/index.js"
7
7
  },
8
8
  "keywords": [],
9
9
  "author": "Enrique Coronado <enriquecoronadozu@gmail.com> (https://enriquecoronadozu.github.io/NEP/)",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
- "commander": "^11.0.0",
12
+ "commander": "^14.0.3",
13
13
  "enquirer": "^2.4.1",
14
14
  "express": "^4.18.2",
15
+ "json5": "^2.2.3",
15
16
  "nep-js": "^0.4.0",
16
17
  "open": "7.4.2",
17
- "socket.io": "^4.7.2",
18
- "zeromq": "6.0.0-beta.19"
18
+ "socket.io": "4.8.3",
19
+ "zeromq": "6.5.0"
19
20
  }
20
21
  }