@n42/cli 0.2.2 → 0.2.31

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/README.md CHANGED
@@ -5,19 +5,17 @@ validation**, with support for the Peppol network.
5
5
 
6
6
  The Node42 CLI is designed for **system integrators, service providers,
7
7
  and operators** who need fast, repeatable insight into eDelivery
8
- routing, SMP resolution, and Access Point behavior.
8
+ routing, SML/SMK, SMP resolution, and Access Point behavior.
9
9
 
10
10
  ------------------------------------------------------------------------
11
11
 
12
12
  ## Features
13
13
 
14
14
  - Peppol eDelivery path discovery
15
- - SMP and Access Point resolution diagnostics
15
+ - SML/SMK, SMP and AP resolution diagnostics
16
16
  - Supported document type detection
17
- - PlantUML and SVG visualizations
18
- - Authenticated API access
19
- - Deterministic, script-friendly output
20
- - No browser automation or UI side effects
17
+ - Discovery trace + diagram (SVG/PlantUML/MD)
18
+ - Interactive diagrams with clickable links
21
19
  - Local artefact history inspection
22
20
 
23
21
  ## Installation
@@ -56,7 +54,28 @@ Check authentication status:
56
54
  n42 me
57
55
  ```
58
56
 
59
- ------------------------------------------------------------------------
57
+ ## Configuration
58
+
59
+ Configuration and cached data are stored under `~/.node42`
60
+
61
+ ``` bash
62
+ ~/.node42/
63
+ ├── artefacts
64
+ │   ├── discovery
65
+ │   ├── transactions
66
+ │   └── validations
67
+ ├── assets
68
+ ├── config.json
69
+ ├── db.json
70
+ └── tokens.json
71
+ ```
72
+
73
+ ## Help
74
+
75
+ ```bash
76
+ n42 --help
77
+ n42 discover --help
78
+ ```
60
79
 
61
80
  ## Peppol Discovery
62
81
 
@@ -116,7 +135,6 @@ Errors are printed with a clickable reference link:
116
135
  ## Security
117
136
 
118
137
  - TLS verification enabled by default
119
- - Explicit `--insecure` flag for testing only
120
138
  - Tokens stored locally, never logged
121
139
 
122
140
  ## License
package/eslint.config.js CHANGED
@@ -3,6 +3,7 @@ const globals = require("globals");
3
3
  module.exports = [
4
4
  {
5
5
  files: ["src/**/*.js"],
6
+ ignores: ["src/assets/**"],
6
7
  languageOptions: {
7
8
  ecmaVersion: "latest",
8
9
  sourceType: "commonjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@n42/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.31",
4
4
  "description": "Node42 CLI – Command-line interface for Peppol eDelivery path discovery, diagnostics, and tooling",
5
5
  "keywords": [
6
6
  "node42"
@@ -0,0 +1,22 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ version="1.1"
6
+ id="svg2"
7
+ width="512"
8
+ height="512"
9
+ viewBox="0 0 512 512"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ xmlns:svg="http://www.w3.org/2000/svg">
12
+ <defs
13
+ id="defs6" />
14
+ <g
15
+ id="g8"
16
+ style="fill:#800000">
17
+ <path
18
+ style="fill:#1f2933;stroke-width:0.912989;fill-opacity:1"
19
+ d="m 250.68278,504.43224 c -6.75028,-0.86235 -10.40179,-2.51239 -26.3886,-11.92466 -7.18686,-4.23127 -22.51642,-13.16255 -34.06573,-19.84729 -17.61184,-10.1937 -90.544282,-52.7204 -115.949676,-67.60997 -4.017154,-2.35436 -11.743692,-6.86653 -17.170137,-10.02703 -16.284811,-9.4848 -23.240148,-18.04125 -25.20828,-31.01095 -1.118138,-7.36878 -1.234179,-204.26795 -0.126175,-214.14802 1.591615,-14.19281 8.942732,-23.64927 26.074433,-33.542 5.521577,-3.18841 16.612757,-9.63032 24.647065,-14.31534 8.034307,-4.685016 16.867478,-9.829712 19.62928,-11.432657 17.1769,-9.969498 73.75066,-42.991708 84.45153,-49.294531 47.47308,-27.961746 51.8264,-30.43697 56.37802,-32.055582 2.13356,-0.7587307 7.16605,-1.6087879 11.18321,-1.8890209 8.43903,-0.5887139 15.57532,1.3129793 24.19422,6.4473399 11.55324,6.882426 111.15984,65.007391 126.44904,73.788753 6.52788,3.749301 15.97732,9.214337 20.99877,12.144522 5.02144,2.930186 15.69117,9.125136 23.71051,13.766556 8.52175,4.9322 16.48887,10.2859 19.17278,12.88365 5.33504,5.16375 9.89879,13.28156 11.34032,20.17152 0.70738,3.3811 1.00748,36.23792 0.99634,109.10225 -0.0173,113.56626 0.0429,112.1513 -5.14415,121.02986 -4.93927,8.45458 -10.50629,12.50751 -43.35365,31.56272 -17.25514,10.00989 -39.58978,22.99615 -49.63267,28.85833 -10.04288,5.86218 -22.16282,12.91081 -26.93319,15.66363 -4.77036,2.75282 -24.69636,14.37995 -44.27999,25.83807 -19.58362,11.45813 -37.80671,21.84456 -40.49573,23.08097 -5.83374,2.6824 -13.25232,3.68189 -20.47754,2.75888 z m 14.85899,-29.89157 c 2.26422,-1.42476 15.20959,-9.0542 28.76748,-16.95432 13.5579,-7.90012 28.1429,-16.42086 32.41113,-18.93499 4.26823,-2.51413 10.4309,-6.11089 13.69484,-7.99281 3.26394,-1.88191 11.89169,-6.91401 19.17279,-11.18242 7.28109,-4.26842 21.24982,-12.40258 31.04164,-18.0759 53.01967,-30.71936 58.89622,-34.32123 60.94205,-37.3528 l 2.05422,-3.04409 -0.0155,-104.63052 -0.0146,-104.63054 -2.03926,-3.52865 c -1.67451,-2.89764 -4.56978,-4.99685 -16.1905,-11.73906 -7.78324,-4.51573 -20.31402,-11.7971 -27.84618,-16.1808 -7.53217,-4.38371 -23.14428,-13.44247 -34.6936,-20.13058 C 361.27694,93.475073 335.39369,78.371241 315.30792,66.599109 295.22215,54.826977 274.54248,42.706959 269.35314,39.665745 257.47369,32.703707 254.7286,32.500567 245.65467,37.91202 c -3.36875,2.008997 -17.01228,9.996331 -30.3191,17.749611 -13.30683,7.75329 -30.3569,17.719374 -37.88907,22.146844 -7.53216,4.427469 -19.44667,11.391863 -26.47669,15.476414 -88.881545,51.641631 -89.2094,51.842921 -90.999955,55.875931 -1.619188,3.6468 -1.681179,8.27351 -1.451745,108.35075 0.23966,104.52557 0.240025,104.54166 2.153377,107.11876 1.073676,1.44611 8.888227,6.62152 17.803296,11.7907 8.739318,5.06728 45.675957,26.59849 82.081407,47.84713 73.96101,43.1686 89.06095,51.89982 91.7382,53.04588 2.93773,1.25756 8.78762,0.0329 13.24729,-2.77337 z m -28.29436,-75.06372 c -7.2811,-3.52372 -17.34681,-8.2419 -22.36825,-10.48486 -10.24337,-4.57547 -13.7466,-7.39987 -15.06505,-12.14599 -0.53337,-1.91984 -0.90422,-20.32266 -0.90724,-45.01664 l -0.009,-41.76926 h -55.28124 c -60.895308,0 -59.490583,0.12764 -61.482452,-5.58644 -1.409747,-4.0439 -1.3861,-60.58499 0.02739,-64.80577 1.678074,-5.01204 4.780869,-8.45119 12.83736,-14.22883 37.757152,-27.07736 65.494042,-46.8543 111.203142,-79.28991 5.77465,-4.09776 16.25122,-11.54944 23.28122,-16.55931 31.1481,-22.197349 31.90278,-22.648895 37.6947,-22.554255 4.14971,0.06783 7.6418,1.108571 17.08468,5.091788 6.52787,2.753613 17.10713,7.203213 23.50947,9.887997 6.40234,2.68479 11.64062,5.17413 11.64062,5.53185 0,0.35772 -2.15695,2.08594 -4.7932,3.84051 -5.48095,3.64782 -50.20301,34.30436 -61.85504,42.40107 -4.26823,2.96588 -18.76276,12.96271 -32.2099,22.2152 -13.44724,9.25249 -35.63289,24.59365 -49.30143,34.09147 -13.66865,9.49782 -26.50218,18.39808 -28.51907,19.77836 -6.05585,4.14437 -6.98975,5.6617 -7.51536,12.21021 l -0.47995,5.97854 h 32.07962 32.07971 l 0.0676,-18.48804 c 0.0375,-10.16842 0.34529,-20.28648 0.68474,-22.48456 l 0.61709,-3.99652 25.44192,-17.23049 c 13.9931,-9.47677 25.59949,-17.23048 25.79195,-17.23048 0.19254,0 0.35003,17.8625 0.35003,39.69445 v 39.69444 l 16.35493,0.24884 16.35502,0.24885 -12.92501,10.04288 c -12.04625,9.36017 -13.14256,10.471 -16.12668,16.34057 l -3.20175,6.29768 -0.25848,62.40478 c -0.14214,34.32263 -0.55298,62.37639 -0.91298,62.3417 -0.35999,-0.0347 -6.61177,-2.94611 -13.89286,-6.46983 z m 33.78061,-83.68734 c 0,-27.3488 0.63005,-31.48088 5.67669,-37.22868 3.31416,-3.77461 1.63736,-2.67278 50.92866,-33.46574 20.83898,-13.01842 39.11767,-24.87028 40.61945,-26.33749 2.45247,-2.39614 2.73121,-3.24747 2.73897,-8.36483 l 0.009,-5.6972 -48.63422,-0.23626 -48.63412,-0.23626 0.24568,-20.306 0.24578,-20.30601 h 71.21318 c 80.74068,0 75.84852,-0.4478 80.42525,7.36184 l 2.30237,3.92877 -0.27928,26.76582 c -0.26541,25.42998 -0.37798,26.94809 -2.25618,30.41778 -2.83922,5.24508 -7.70764,9.47403 -18.10888,15.73043 -5.02143,3.02042 -15.29257,9.35994 -22.82473,14.08782 -7.53216,4.7279 -23.8556,14.94877 -36.27426,22.71308 -12.41867,7.7643 -22.58709,14.42505 -22.59649,14.80165 -0.009,0.37661 23.19568,0.68474 51.56683,0.68474 28.37115,0 51.59541,0.30814 51.60956,0.68475 0.0137,0.3766 -1.29535,9.4152 -2.91006,20.08577 l -2.93571,19.40102 h -76.0636 -76.06352 z"
20
+ id="path170" />
21
+ </g>
22
+ </svg>
@@ -0,0 +1,196 @@
1
+ :root {
2
+ --bg: transparent;
3
+ --card: #ffffff;
4
+ --border: #e5e7eb;
5
+ --border-soft: #f1f5f9;
6
+ --border-strong: #d1d5db;
7
+ --text-light: #9ca3af;
8
+ --text: #111827;
9
+ --muted: #6b7280;
10
+ --brand-soft: #eaf3ff; /* Soft, light blue */
11
+ --brand: #3f9cff; /* Main brand color */
12
+ --brand-strong: #0b3a82; /* Strong blue for contrast */
13
+ --brand-hover: #1e6fd0; /* Slightly darker hover state */
14
+ --brand-active: #1666c1; /* Active state */
15
+ --brand-button: #1d72d2; /* Darker shade of the brand color */
16
+ --shadow-soft: 0 2px 4px rgba(0, 0, 0, 0.04);
17
+ --shadow-form: 0 3px 8px rgba(0, 0, 0, 0.05);
18
+ }
19
+
20
+ body {
21
+ margin: 0;
22
+ height: 100vh;
23
+ background: #fff;
24
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
25
+ overflow-y: auto;
26
+ }
27
+
28
+ #page {
29
+ min-height: calc(100vh - 56px);
30
+ display: flex;
31
+ justify-content: center;
32
+ padding-top: 32px;
33
+ }
34
+
35
+ #app-shell {
36
+ width: 100%;
37
+ max-width: 1450px;
38
+ height: 100%;
39
+ display: flex;
40
+ flex-direction: column;
41
+ }
42
+
43
+ #app-header {
44
+ height: 62px;
45
+ display: flex;
46
+ justify-content: space-between;
47
+ align-items: center;
48
+ padding: 0 16px;
49
+ position: fixed;
50
+ top: 0;
51
+ left: 0;
52
+ right: 0;
53
+ z-index: 100;
54
+ }
55
+
56
+ .header-left {
57
+ gap: 16px;
58
+ }
59
+
60
+ .header-right {
61
+ gap: 16px;
62
+ }
63
+
64
+ #appBtn {
65
+ display: flex;
66
+ width: 34px;
67
+ height: 34px;
68
+ cursor: pointer;
69
+ }
70
+
71
+ #timeline {
72
+ flex: 1;
73
+ overflow-y: auto;
74
+ padding: 24px 16px 24px 16px;
75
+ background: var(--bg);
76
+ min-height: 0; /* for flex scroll containers */
77
+ }
78
+
79
+ .bubble {
80
+ position: relative;
81
+ background: #ffffff;
82
+ border-radius: 12px;
83
+ padding: 16px;
84
+ margin-bottom: 64px;
85
+ border: 1px solid var(--border);
86
+ }
87
+ .bubble.info {
88
+ width: 50%;
89
+ }
90
+
91
+ .bubble svg {
92
+ width: 100%;
93
+ height: auto;
94
+ max-width: 100%;
95
+ display: block;
96
+ }
97
+
98
+ .bubble-time {
99
+ position: absolute;
100
+ bottom: -24px;
101
+ left: 10px;
102
+ font-size: 0.65rem;
103
+ color: var(--text-light);
104
+ cursor: pointer;
105
+ }
106
+
107
+ .bubble-time:hover {
108
+ color: var(--text);
109
+ }
110
+
111
+ .bubble-link {
112
+ position: absolute;
113
+ bottom: 14px;
114
+ right: 21px;
115
+ display: inline-flex;
116
+ align-items: center;
117
+ gap: 2px;
118
+ font-size: 0.7rem;
119
+ text-decoration: none;
120
+ color: var(--text-light);
121
+ cursor: pointer;
122
+ }
123
+
124
+ .bubble-link svg {
125
+ width: 24px;
126
+ }
127
+
128
+ .bubble-link:hover {
129
+ color: var(--text);
130
+ }
131
+
132
+ .bubble-close {
133
+ position: absolute;
134
+ top: 10px;
135
+ right: 10px;
136
+ border: none;
137
+ background: #f6f6f6;
138
+ border-radius: 999px;
139
+ padding: 4px;
140
+ cursor: pointer;
141
+ line-height: 1;
142
+ }
143
+
144
+ .material-icons.bubble-close {
145
+ font-size: 14px;
146
+ }
147
+
148
+ .bubble-close:hover {
149
+ background: #fee2e2;;
150
+ color: #991b1b;
151
+ }
152
+
153
+ .bubble-info {
154
+ display: flex;
155
+ gap: 8px;
156
+ align-items: flex-start;
157
+ }
158
+
159
+ .bubble-info-icon {
160
+ font-size: 20px;
161
+ line-height: 1;
162
+ }
163
+
164
+ .bubble-info-icon.error {
165
+ color: #ef4444;
166
+ }
167
+
168
+ .bubble-info-icon.warning {
169
+ color: #f59e0b;
170
+ }
171
+
172
+ .bubble-info-icon.info {
173
+ color: #1f7fe5;
174
+ }
175
+
176
+ .bubble-info-header {
177
+ font-weight: 600;
178
+ margin-bottom: 2px;
179
+ }
180
+
181
+ .bubble-info-body {
182
+ font-size: 0.9rem;
183
+ line-height: 1.3;
184
+ }
185
+
186
+ .bubble-info-body a {
187
+ color: var(--text);
188
+ font-weight: 600;
189
+ text-decoration: underline;
190
+ }
191
+
192
+ .bubble-info-body a:hover {
193
+ color: var(--brand-active);
194
+ font-weight: 600;
195
+ text-decoration: none;
196
+ }
@@ -0,0 +1,33 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Node42</title>
6
+ <link rel="stylesheet" href="../../assets/wrapper-light.css">
7
+ </head>
8
+ <body>
9
+ <div id="app-header">
10
+ <div class="header-left">
11
+ <img id="appBtn" src="../../assets/node42-logo.svg" alt="Node42" onclick="window.open('https://www.node42.dev', '_blank');">
12
+ </div>
13
+ <div class="header-right"></div>
14
+ </div>
15
+ <div id="page">
16
+ <div id="app-shell">
17
+ <div id="timeline"></div>
18
+ <div id="bubble" class="bubble" data-uuid="/--UUID--/">
19
+ <!-- SVG -->
20
+ <div class="bubble-time"><!-- TIME --></div>
21
+ <a id="link" class="bubble-link" title="Download discovery PlantUML diagram (SVG)">
22
+ <svg width="24px" height="24px" viewBox="0 0 24 24" fill="none">
23
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M5 18V13.5862H6.5V16.5H17.5V13.5862H19V18H5Z" fill="currentColor"/>
24
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12.75 11.1287L14.5253 9.35331L15.586 10.414L12 14L8.41393 10.414L9.47459 9.35331L11.25 11.1287V6H12.75V11.1287Z" fill="currentColor"/>
25
+ </svg>Download
26
+ </a>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ <script src="../../assets/wrapper.js" defer></script>
32
+ </body>
33
+ </html>
@@ -0,0 +1,48 @@
1
+ function formatSvg() {
2
+ const bubble = document.getElementById("bubble");
3
+ const svgEl = bubble.querySelector("svg");
4
+
5
+ if (svgEl) {
6
+ svgEl.removeAttribute("width");
7
+ svgEl.removeAttribute("height");
8
+ svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet");
9
+ svgEl.style.removeProperty('width');
10
+ svgEl.style.removeProperty('height');
11
+
12
+ const svgTitleEl = svgEl.querySelector("title");
13
+ if (svgTitleEl) {
14
+ svgTitleEl.remove();
15
+ }
16
+ }
17
+ }
18
+
19
+ function addDownloadLinkListener() {
20
+ const bubble = document.getElementById("bubble");
21
+ const svgEl = bubble.querySelector("svg");
22
+ const svg = new XMLSerializer().serializeToString(svgEl);
23
+ const refId = bubble.dataset.uuid;
24
+
25
+ const linkEl = document.getElementById("link");
26
+ linkEl.addEventListener("click", (e) => {
27
+ e.preventDefault();
28
+
29
+ const blob = new Blob([svg], { type: "image/svg+xml" });
30
+ const url = URL.createObjectURL(blob);
31
+
32
+ const a = document.createElement("a");
33
+ a.href = url;
34
+ a.id = `discovery-${refId}`;
35
+ a.download = `discovery${refId ? "-" + refId : ""}.svg`;
36
+
37
+ document.body.appendChild(a);
38
+ a.click();
39
+ document.body.removeChild(a);
40
+
41
+ URL.revokeObjectURL(url);
42
+ });
43
+ }
44
+
45
+ document.addEventListener("DOMContentLoaded", () => {
46
+ formatSvg();
47
+ addDownloadLinkListener();
48
+ });
package/src/auth.js CHANGED
@@ -2,15 +2,14 @@ const fs = require("fs");
2
2
  const { NODE42_DIR, TOKENS_FILE, API_URL, EP_SIGNIN, EP_REFRESH, EP_ME } = require("./config");
3
3
  const { handleError } = require("./errors");
4
4
  const { getUserWithIndex } = require("./user");
5
- const { clearScreen, ask, startSpinner } = require("./utils");
5
+ const { ask, startSpinner } = require("./utils");
6
6
 
7
7
  const db = require("./db");
8
- const pkg = require("../package.json");
9
8
  const C = require("./colors");
10
9
 
11
10
 
12
11
  async function login() {
13
- clearScreen(`Node42 CLI v${pkg.version}\n\n${C.BOLD}Sign in to your account${C.RESET}`);
12
+ console.log(`${C.BOLD}Sign in to your account${C.RESET}`);
14
13
  let user = getUserWithIndex(0);
15
14
 
16
15
  const username = await ask("Username", user.userMail ?? "");
@@ -65,6 +64,7 @@ function logout() {
65
64
  fs.unlinkSync(TOKENS_FILE);
66
65
  }
67
66
  db.clear("user");
67
+ console.log(`${C.RED}Sign out complete${C.RESET}\n`);
68
68
  }
69
69
 
70
70
  function loadTokens() {
package/src/cli.js CHANGED
@@ -4,7 +4,7 @@ const { Command } = require("commander");
4
4
  const { login, logout, checkAuth } = require("./auth");
5
5
  const { getUserWithIndex, getUserUsage } = require("./user");
6
6
  const { runDiscovery } = require("./discover");
7
- const { clearScreen, startSpinner, validateEnv, validateId, createAppDirs, capitalize, cleanAppDirs } = require("./utils");
7
+ const { startSpinner, validateEnv, validateId, createAppDirs, capitalize, cleanAppDirs } = require("./utils");
8
8
  const { NODE42_DIR, ARTEFACTS_DIR, DEFAULT_OUTPUT, DEFAULT_FORMAT } = require("./config");
9
9
 
10
10
  createAppDirs();
@@ -19,7 +19,7 @@ const path = require("path");
19
19
 
20
20
  program
21
21
  .name("n42")
22
- .description("Node42 Command-line interface for eDelivery path discovery and diagnostics")
22
+ .description("Node42 CLI for eDelivery path discovery and diagnostics")
23
23
  .version(pkg.version);
24
24
 
25
25
  program
@@ -65,7 +65,7 @@ program
65
65
 
66
66
  program
67
67
  .command("me")
68
- .description("Returns identity and billing status for the authenticated user.")
68
+ .description("Returns identity and service usage for the authenticated user.")
69
69
  .action(async () => {
70
70
  const stopSpinner = startSpinner();
71
71
 
@@ -73,13 +73,12 @@ program
73
73
  stopSpinner();
74
74
 
75
75
  if (!authenticated) {
76
- console.error("Not authenticated");
77
76
  process.exit(1);
78
77
  }
79
78
 
80
79
  const user = getUserWithIndex(0);
81
80
  const currentMonth = new Date().toISOString().slice(0, 7);
82
- console.log(`Node42 CLI v${pkg.version}
81
+ console.log(`Node42 Account (CLI v${pkg.version})
83
82
  ${C.BOLD}User${C.RESET}
84
83
  ID : ${C.CYAN}${user.id}${C.RESET}
85
84
  Name : ${user.userName}
@@ -100,7 +99,7 @@ program
100
99
 
101
100
  program
102
101
  .command("usage <service>")
103
- .description("Returns usage for the authenticated user.")
102
+ .description("Returns service usage for the authenticated user.")
104
103
  .option("-m, --month <yyyy-mm>", "Show usage for a specific month")
105
104
  .action((service, options) => {
106
105
  const user = getUserWithIndex(0);
@@ -110,14 +109,13 @@ program
110
109
  usage = 0;
111
110
  }
112
111
 
113
- clearScreen(`Node42 CLI v${pkg.version}\n`);
114
112
  console.log(`${C.BOLD}${capitalize(service)} usage${C.RESET}`);
115
113
  console.log(` • ${currentMonth}: ${C.RED}${usage}${C.RESET}\n`);
116
114
  });
117
115
 
118
116
  program
119
117
  .command("history [participantId]")
120
- .description("Show local discovery history for a participant")
118
+ .description("Show local history with filtering")
121
119
  .option("--today", "Show only today's artefacts")
122
120
  .option("--day <yyyy-mm-dd>", "Show artefacts for a specific day")
123
121
  .option("--last <n>", "Show only last N results", parseInt)
@@ -156,14 +154,12 @@ program
156
154
  }
157
155
 
158
156
  if (!artefacts.length) {
159
- clearScreen(`Node42 CLI v${pkg.version}\n`);
160
157
  const filter = dayFilter !== null ? ` (${dayFilter})` : ``;
161
158
  console.log(`${C.RED}No artefacts found.${C.RESET}${C.DIM}${filter}${C.RESET}\n`);
162
159
  return;
163
160
  }
164
161
 
165
162
  // ---- OUTPUT ----
166
- clearScreen(`Node42 CLI v${pkg.version}\n`);
167
163
  console.log(`${C.BOLD}Found ${artefacts.length} artefact(s)${filterInfo}${C.RESET}\n`);
168
164
 
169
165
  const DATE = "DATE".padEnd(19);
@@ -176,7 +172,13 @@ program
176
172
  const iso = d.toISOString(); // 2026-01-30T16:53:28.123Z
177
173
  const date = iso.slice(0, 10); // 2026-01-30
178
174
  const time = iso.slice(11, 19); // 16:53:28
179
- const file = path.join(ARTEFACTS_DIR, `${item.file}`);
175
+
176
+ let file;
177
+ if (item.file.includes(".svg")) {
178
+ file = path.join(ARTEFACTS_DIR, `${item.file.replace(".svg", ".html")}`);
179
+ } else {
180
+ file = path.join(ARTEFACTS_DIR, `${item.file}`);
181
+ }
180
182
  const link = `\u001B]8;;file://${file}\u0007Open\u001B]8;;\u0007`
181
183
 
182
184
  let pid = item.participantId;
@@ -208,8 +210,6 @@ discover
208
210
  .option("--reverse-lookup", "Enable reverse lookup", false)
209
211
  .option("--probe-endpoints", "Probe resolved endpoints", false)
210
212
  .action((participantId, options) => {
211
- clearScreen(`Node42 CLI v${pkg.version}`);
212
-
213
213
  try { validateEnv(options.env); }
214
214
  catch (e) {
215
215
  console.error(e.message);
package/src/discover.js CHANGED
@@ -1,13 +1,12 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
- const pkg = require("../package.json");
4
3
  const db = require("./db");
5
4
  const C = require("./colors");
6
5
 
7
6
  const { fetchWithAuth } = require("./auth");
8
- const { API_URL, EP_DISCOVER, DEFAULT_OUTPUT, DEFAULT_FORMAT, ARTEFACTS_DIR } = require("./config");
7
+ const { API_URL, EP_DISCOVER, DEFAULT_OUTPUT, DEFAULT_FORMAT, NODE42_DIR, ARTEFACTS_DIR } = require("./config");
9
8
  const { getUserWithIndex, setUserUsage } = require("./user");
10
- const { clearScreen, startSpinner, buildDocLabel, promptForDocument, getShortId, getArtefactExt } = require("./utils");
9
+ const { startSpinner, buildDocLabel, promptForDocument, getShortId, getArtefactExt } = require("./utils");
11
10
  const { handleError } = require("./errors");
12
11
 
13
12
  const DEFAULT_DISCOVERY_INPUT = {
@@ -35,6 +34,27 @@ const DEFAULT_DISCOVERY_INPUT = {
35
34
  const discoveryInput = DEFAULT_DISCOVERY_INPUT;
36
35
  let docSelected = false;
37
36
 
37
+ function wrapSvg(fileId, refId, svg) {
38
+ let html;
39
+
40
+ const now = new Date();
41
+ const timeText = now.toLocaleTimeString([], {
42
+ hour: "2-digit",
43
+ minute: "2-digit"
44
+ });
45
+
46
+ const templateFile = path.join(NODE42_DIR, "assets/wrapper.html.template");
47
+ const template = fs.readFileSync(templateFile, "utf8");
48
+
49
+ html = template.replace("<!-- SVG -->", svg);
50
+ html = html.replace("<!-- TIME -->", `${timeText} • ${refId}`);
51
+ html = html.replace("/--UUID--/", fileId);
52
+
53
+ const htmlFile = path.join(ARTEFACTS_DIR, `${fileId}.html`);
54
+ fs.writeFileSync(htmlFile, html);
55
+ return htmlFile;
56
+ }
57
+
38
58
  async function processSupportedDocuments(encodedDocs, onDone) {
39
59
  if (encodedDocs && !docSelected) {
40
60
  const docs = JSON.parse(Buffer.from(encodedDocs, "base64").toString("utf8"))
@@ -88,7 +108,6 @@ async function runDiscovery(participantId, options) {
88
108
  }
89
109
  };
90
110
 
91
- clearScreen(`Node42 CLI v${pkg.version}\n`);
92
111
  const stopSpinner = startSpinner();
93
112
 
94
113
  const url = `${API_URL}/${EP_DISCOVER}?output=${output}&format=${format}`;
@@ -153,7 +172,8 @@ async function runDiscovery(participantId, options) {
153
172
  const file = path.join(ARTEFACTS_DIR, `${fileName}`);
154
173
  fs.writeFileSync(file, svg);
155
174
 
156
- const link = `\u001B]8;;file://${file}\u0007Open\u001B]8;;\u0007`;
175
+ const htmlFile = wrapSvg(fileId, refId, svg);
176
+ const link = `\u001B]8;;file://${htmlFile}\u0007Open\u001B]8;;\u0007`;
157
177
 
158
178
  console.log(`${C.BOLD}Discovery completed${C.RESET}`);
159
179
  console.log(`PID : ${participantId}`);
package/src/utils.js CHANGED
@@ -1,16 +1,24 @@
1
- const fs = require("fs");
1
+
2
2
  const inquirer = require("inquirer");
3
3
  const readline = require("readline");
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+
4
8
  const config = require("./config");
5
9
  const pkg = require("../package.json");
6
10
  const db = require("./db");
7
11
  const C = require("./colors");
8
12
 
9
13
 
10
- function clearScreen(text) {
11
- process.stdout.write("\x1Bc");
12
- if (text) {
14
+ function writeHeader(text, clearScreen=false) {
15
+ if (clearScreen) {
16
+ process.stdout.write("\x1Bc");
17
+ }
18
+ if (text && text.length > 0) {
13
19
  process.stdout.write(text + "\n");
20
+ } else {
21
+ process.stdout.write(`Node42 CLI v${pkg.version}`);
14
22
  }
15
23
  }
16
24
 
@@ -115,6 +123,10 @@ function createAppDirs(force=false) {
115
123
  fs.mkdirSync(config.TRANSACTIONS_DIR, { recursive: true });
116
124
  fs.mkdirSync(config.VALIDATIONS_DIR, { recursive: true });
117
125
 
126
+ const wrapperSrc = path.join(__dirname, "assets");
127
+ const wrapperDest = path.join(config.NODE42_DIR, "assets");
128
+ fs.cpSync(wrapperSrc, wrapperDest, { recursive: true });
129
+
118
130
  if (!fs.existsSync(config.CONFIG_FILE) || force) {
119
131
  fs.writeFileSync(
120
132
  config.CONFIG_FILE,
@@ -141,8 +153,6 @@ function cleanAppDirs(options) {
141
153
  return;
142
154
  }
143
155
 
144
- clearScreen(`Node42 CLI v${pkg.version}\n`);
145
-
146
156
  const removed = [];
147
157
 
148
158
  if ((all || tokens) && fs.existsSync(config.TOKENS_FILE)) {
@@ -252,4 +262,4 @@ function getArtefactExt(output, format) {
252
262
  }
253
263
  }
254
264
 
255
- module.exports = { clearScreen, startSpinner, ask, buildDocLabel, promptForDocument, validateEnv, validateId, getShortId, capitalize, createAppDirs, cleanAppDirs, getArtefactExt };
265
+ module.exports = { writeHeader, startSpinner, ask, buildDocLabel, promptForDocument, validateEnv, validateId, getShortId, capitalize, createAppDirs, cleanAppDirs, getArtefactExt };
package/test/auth.test.js CHANGED
@@ -30,7 +30,7 @@ describe("auth", () => {
30
30
  utils = require("../src/utils");
31
31
  user = require("../src/user");
32
32
 
33
- sinon.stub(utils, "clearScreen");
33
+ //sinon.stub(utils, "writeHeader");
34
34
  sinon.stub(utils, "ask")
35
35
  .onFirstCall().resolves("user")
36
36
  .onSecondCall().resolves("secret");