nep-cli 0.2.4 → 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
@@ -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');
@@ -17,10 +18,44 @@ const socketIo = require('socket.io');
17
18
  const fs = require ('fs');
18
19
  const zmqc = require("zeromq/v5-compat");
19
20
 
21
+
20
22
  const PORT_MASTER_INFO = 50001; // Default port for master info
21
23
  const PORT_SERVER = 50050;
22
24
 
23
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
+ }
24
59
 
25
60
  program
26
61
  .version(version)
@@ -32,37 +67,76 @@ program
32
67
  .description('Display current Wi-Fi and Ethernet IP addresses of this computer')
33
68
  .action(() => {
34
69
  try {
35
- // subsitude by interfaces = nep.getNetworkInterfaces --> {"wifi":wifiInterfaces, "eth":ethernetInterface}
36
70
  const platform = os.platform();
37
- let wifiInterfaces, ethernetInterface;
38
-
39
- if (platform === 'linux') {
40
- wifiInterfaces = Object.keys(os.networkInterfaces()).filter(ifname => /^(wlan|wlp|wl|en)\d*$/i.test(ifname));
41
- ethernetInterface = Object.keys(os.networkInterfaces()).find(ifname => /^(eth|enp|eno)\d*$/i.test(ifname));
42
- } else if (platform === 'win32') {
43
- wifiInterfaces = Object.keys(os.networkInterfaces()).filter(ifname => /^(Wireless|Wi-Fi)/i.test(ifname));
44
- ethernetInterface = Object.keys(os.networkInterfaces()).find(ifname => /^Ethernet/i.test(ifname));
45
- } else if (platform === 'darwin') {
46
- wifiInterfaces = Object.keys(os.networkInterfaces()).filter(ifname => /^en\d*$/i.test(ifname));
47
- ethernetInterface = Object.keys(os.networkInterfaces()).find(ifname => /^en\d*$/i.test(ifname));
48
- } else {
49
- console.log('Unsupported operating system.');
50
- 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))])];
51
104
  }
52
105
 
53
- wifiInterfaces.forEach(wifiInterface => {
54
- const ipAddress = nep.getIPAddress(wifiInterface);
55
- 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
+ });
56
114
  });
57
115
 
58
- nep.printIPAddress('Ethernet', nep.getIPAddress(ethernetInterface));
59
- } catch (error) {
60
- 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);
61
134
  }
62
135
  });
63
136
 
64
137
 
65
138
 
139
+
66
140
  // Node saved here
67
141
  var nodes_register = {};
68
142
  // ----------- Main variables ---------------
@@ -578,7 +652,7 @@ program
578
652
  .action(async (topic, index) => {
579
653
  const ip = "127.0.0.1";
580
654
 
581
- const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary", "string"];
655
+ const allowedFormats = ["json", "msgpack", "bytes", "images", "dictionary"];
582
656
  const master_ip = "127.0.0.1";
583
657
 
584
658
  const requester = createRequester(master_ip, PORT_MASTER_INFO);
@@ -599,7 +673,7 @@ program
599
673
  open(`http://localhost:${port}/?port=${port}&topic=${encodeURIComponent(topicid)}`);
600
674
  startShowServer(port, topic, ip);
601
675
  }
602
- else if (msg_type === "json" || msg_type === "dictionary") {
676
+ else if (msg_type === "json" || msg_type === "dictionary" || msg_type === "msgpack") {
603
677
  open(`http://localhost:${port}/?port=${port}&topic=${encodeURIComponent(topicid)}`);
604
678
  startJsonServer(port, topic, ip, msg_type);
605
679
  }
@@ -783,52 +857,167 @@ program
783
857
  openSub(master_ip, topic, msg_type, callback);
784
858
  });
785
859
 
786
-
787
860
  program
788
- .command('publish <topic> <message>')
789
- .description('Publish a message to a NEP+ topic (only JSON messages are supported)')
790
- .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;
791
876
 
792
- 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
+ }
793
881
 
794
- 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
+ }
795
892
 
796
- var msg = message.replace(/'/g, '"')
797
- console.log(JSON.parse(msg))
798
- pub.publish(JSON.parse(msg))
799
- 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
+ }
800
901
 
801
- process.exit(1)
902
+ if (opts.pretty) {
903
+ console.log('Parsed payload:');
904
+ console.log(JSON.stringify(payload, null, 2));
802
905
  }
803
906
 
907
+ const node = new nep.Node('nep-cli-pubj');
908
+ const pub = node.new_pub(topic, 'json');
804
909
 
805
- var node = new nep.Node("nep-cli-pub")
806
- var pub = node.new_pub(topic, "json")
807
- 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);
808
919
  }
920
+ });
809
921
 
810
- async function run() {
811
- var requester = new zmq.Request;
812
- 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";
813
934
 
814
- 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" });
815
938
 
816
- let msg = { "input": "topics" }
817
- var message = JSON.stringify(msg);
818
- await requester.send(message)
819
- const [result] = await requester.receive()
820
- var results = JSON.parse(result.toString())
821
- //console.log(results);
822
- if (results["input"].includes(topic)) {
823
- console.log("")
824
- openpub(master_ip)
825
- }
826
- else {
827
- 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);
828
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);
829
982
  }
830
- 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);
831
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
+ });
832
1021
 
833
1022
 
834
1023
  const { Input } = require('enquirer');
Binary file
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "nep-cli",
3
- "version": "0.2.4",
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
- "nep-js": "^0.3.8",
15
+ "json5": "^2.2.3",
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
  }
21
-