readback 0.0.0-alpha.4 → 0.0.0-alpha.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/README.md +51 -29
- package/dist/bin/index.js +78 -0
- package/dist/package.json +59 -0
- package/dist/src/asr-task.js +14 -0
- package/dist/src/asr-worker.js +37 -0
- package/dist/src/bandpass.js +57 -0
- package/dist/src/banner.js +19 -0
- package/dist/src/capture.js +315 -0
- package/dist/src/classifier-manager.js +117 -0
- package/dist/src/classify-atc-pilot.js +18 -0
- package/dist/src/cleaner.js +36 -0
- package/dist/src/console.js +11 -0
- package/dist/src/generated/airlines.js +5768 -0
- package/dist/src/generated/nav-data.js +203401 -0
- package/dist/src/generated/waypoint-data.js +203401 -0
- package/dist/src/model-manager.js +90 -0
- package/dist/src/normalization/airlines.js +5773 -0
- package/dist/src/normalization/altitude.js +134 -0
- package/dist/src/normalization/atc-phrases.js +315 -0
- package/dist/src/normalization/callsigns.js +87 -0
- package/dist/src/normalization/flight-level.js +26 -0
- package/dist/src/normalization/frequency.js +30 -0
- package/dist/src/normalization/heading.js +46 -0
- package/dist/src/normalization/headings.js +42 -0
- package/dist/src/normalization/keywords.js +141 -0
- package/dist/src/normalization/nav-data.js +252 -0
- package/dist/src/normalization/numbers.js +405 -0
- package/dist/src/normalization/phonetic.js +56 -0
- package/dist/src/normalization/qnh.js +23 -0
- package/dist/src/normalization/runway.js +35 -0
- package/dist/src/normalization/speed.js +66 -0
- package/dist/src/normalization/squawk.js +23 -0
- package/dist/src/normalization/tokenizer.js +33 -0
- package/dist/src/normalization/waypoint-data.js +203401 -0
- package/dist/src/normalization/waypoints.js +183 -0
- package/dist/src/presentation/renderer.js +134 -0
- package/dist/src/progress-manager.js +42 -0
- package/dist/src/prompt-manager.js +114 -0
- package/dist/src/setup-manager.js +167 -0
- package/dist/src/startup-cleanup.js +18 -0
- package/dist/src/status-line.js +297 -0
- package/dist/src/types.js +21 -0
- package/dist/src/vad-worker.js +171 -0
- package/dist/src/whisper-worker.js +46 -0
- package/models/silero_vad_v6.onnx +0 -0
- package/package.json +21 -12
- package/bin/index.ts +0 -68
- package/src/capture.ts +0 -242
- package/src/cleaner.ts +0 -35
- package/src/console.ts +0 -12
- package/src/model-manager.ts +0 -230
- package/src/normalization/atc-phrases.ts +0 -385
- package/src/normalization/callsigns.ts +0 -164
- package/src/normalization/keywords.ts +0 -137
- package/src/normalization/numbers.ts +0 -197
- package/src/normalization/phonetic.ts +0 -58
- package/src/normalization/tokenizer.ts +0 -26
- package/src/normalization/waypoints.ts +0 -194
- package/src/presentation/renderer.ts +0 -92
- package/src/types.ts +0 -45
- package/src/vad-worker.ts +0 -26
- package/src/whisper-worker.ts +0 -37
package/README.md
CHANGED
|
@@ -1,28 +1,42 @@
|
|
|
1
|
-
|
|
1
|
+
```
|
|
2
|
+
█▀▄ █▀▀ █▀█ █▀▄ █▀▄ █▀█ █▀▀ █ █
|
|
3
|
+
█▀▄ █▀▀ █▀█ █ █ █▀▄ █▀█ █ █▀▄
|
|
4
|
+
█ █ █ █ █ █ █ █ █ █ █ █ █ █
|
|
5
|
+
▀ ▀ ▀▀▀ ▀ ▀ ▀▀ ▀▀ ▀ ▀ ▀▀▀ ▀ ▀
|
|
6
|
+
```
|
|
2
7
|
|
|
3
8
|
**readback** is a real-time ATC (Air Traffic Control) transcription tool that captures system audio, transcribes it using local ATC-fine-tuned [Whisper](https://en.wikipedia.org/wiki/Whisper_(speech_recognition_system)) models, and formats the output with aviation-specific syntax highlighting. Useful for flight simmers on [VATSIM](https://vatsim.net) and [IVAO](https://www.ivao.aero) networks who (like me) struggle with following ATC communications.
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
> **Disclaimer**: Transcription is far from perfect. The AI will make mistakes with fast speech, accents, background noise, and similar-sounding words. Always verify critical information (altitudes, headings, frequencies) and ask ATC to repeat if unsure. Never use for real-world aviation.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
### Step 1: Build tools (only if prebuilt binaries are unavailable)
|
|
15
|
+
|
|
16
|
+
Most users won’t need this. It’s only required if npm can’t find a compatible prebuilt binary for a native dependency (e.g. `smart-whisper`, `native-recorder-nodejs`) and falls back to building from source.
|
|
17
|
+
|
|
18
|
+
**macOS**: `xcode-select --install`
|
|
6
19
|
|
|
7
|
-
**
|
|
20
|
+
**Windows**: Install Visual Studio Build Tools with the C++ workload (MSVC).
|
|
8
21
|
|
|
9
|
-
|
|
22
|
+
**Linux (Debian/Ubuntu)**: `sudo apt-get install build-essential python3`
|
|
10
23
|
|
|
11
|
-
### Step
|
|
24
|
+
### Step 2 (Linux only): System audio capture tools
|
|
12
25
|
|
|
13
|
-
|
|
26
|
+
On Linux, readback captures system audio by spawning either PipeWire (`pw-cat`) or PulseAudio (`parec`).
|
|
14
27
|
|
|
15
|
-
|
|
28
|
+
Install one of the following:
|
|
16
29
|
|
|
17
|
-
|
|
30
|
+
- PipeWire: `sudo apt install pipewire-bin` (provides `pw-cat`)
|
|
31
|
+
- PulseAudio: `sudo apt install pulseaudio-utils` (provides `parec`)
|
|
18
32
|
|
|
19
|
-
### Step
|
|
33
|
+
### Step 3: Install Node.js
|
|
20
34
|
|
|
21
|
-
You need Node.js version 22
|
|
35
|
+
You need Node.js version 22 or higher installed on your system:
|
|
22
36
|
|
|
23
|
-
1. **Download Node.js**: Go to https://nodejs.org/en/download/ and download the **LTS version** (Long Term Support)
|
|
24
|
-
2. **Install**: Run the downloaded installer
|
|
25
|
-
3. **Verify**: Open Terminal, type `node --version` and hit the enter key (you should see something like `v24.x.x`)
|
|
37
|
+
3.1. **Download Node.js**: Go to https://nodejs.org/en/download/ and download the **LTS version** (Long Term Support)
|
|
38
|
+
3.2. **Install**: Run the downloaded installer
|
|
39
|
+
3.3. **Verify**: Open Terminal, type `node --version` and hit the enter key (you should see something like `v24.x.x`)
|
|
26
40
|
|
|
27
41
|
### Step 3: Install readback globally
|
|
28
42
|
|
|
@@ -34,32 +48,40 @@ npm install -g readback
|
|
|
34
48
|
|
|
35
49
|
This installs readback globally so you can run it from anywhere.
|
|
36
50
|
|
|
51
|
+
If the instal fails with node-gyp errors, install Step 1 tools and retry.
|
|
52
|
+
|
|
37
53
|
---
|
|
38
54
|
|
|
39
|
-
##
|
|
55
|
+
## Usage
|
|
40
56
|
|
|
41
57
|
In your terminal, run:
|
|
42
58
|
```bash
|
|
43
59
|
readback
|
|
44
60
|
```
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
### First time run
|
|
63
|
+
|
|
64
|
+
The first time you run readback, it will check if necessary models exist on your machine and guide you through the download process. Models are stored in `~/.readback/models`.
|
|
65
|
+
|
|
66
|
+
After all models are installed, readback will attempt to capture your system audio. This may trigger an operating system security prompt asking for microphone or audio recording permissions.
|
|
67
|
+
|
|
68
|
+
Please grant this permission. Although readback listens to output (what you hear), operating systems often classify this as "Microphone" or "Screen Recording" access. Once granted, you may need to restart your terminal or the application.
|
|
69
|
+
|
|
70
|
+
### Options
|
|
47
71
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
You can bring your own model too:
|
|
72
|
+
To list all available options run:
|
|
51
73
|
|
|
52
74
|
```bash
|
|
53
|
-
|
|
75
|
+
readback --help
|
|
54
76
|
```
|
|
55
77
|
|
|
56
78
|
### Exiting
|
|
57
79
|
|
|
58
|
-
Press
|
|
80
|
+
Press <kbd>Ctrl</kbd> + <kbd>C</kbd>
|
|
59
81
|
|
|
60
82
|
---
|
|
61
83
|
|
|
62
|
-
##
|
|
84
|
+
## Updating
|
|
63
85
|
|
|
64
86
|
Open Terminal and run:
|
|
65
87
|
|
|
@@ -69,36 +91,36 @@ npm update -g readback
|
|
|
69
91
|
|
|
70
92
|
---
|
|
71
93
|
|
|
72
|
-
##
|
|
94
|
+
## Uninstalling
|
|
73
95
|
|
|
74
96
|
### Uninstall npm package
|
|
75
97
|
|
|
76
98
|
Open Terminal and run:
|
|
77
99
|
|
|
78
100
|
```bash
|
|
79
|
-
npm uninstall -g readback
|
|
101
|
+
readback --purge && npm uninstall -g readback
|
|
80
102
|
```
|
|
81
103
|
|
|
82
|
-
|
|
104
|
+
This will first delete the `.readback` folder in your home directory and then uninstall the globally installed npm module.
|
|
83
105
|
|
|
84
106
|
---
|
|
85
107
|
|
|
86
|
-
##
|
|
108
|
+
## License
|
|
87
109
|
|
|
88
110
|
MIT License - See repository for details
|
|
89
111
|
|
|
90
112
|
---
|
|
91
113
|
|
|
92
|
-
##
|
|
114
|
+
## Credits
|
|
93
115
|
|
|
94
|
-
This project would not be possible without the ATC-fine-tuned models provided by [Jack Tol](https://huggingface.co/jacktol).
|
|
116
|
+
This project would not be possible without the ATC-fine-tuned Whisper models provided by [Jack Tol](https://huggingface.co/jacktol).
|
|
95
117
|
|
|
96
118
|
---
|
|
97
119
|
|
|
98
|
-
##
|
|
120
|
+
## Issues and contributions
|
|
99
121
|
|
|
100
122
|
Found a bug or want to add features? Submit issues or pull requests on the [GitHub repository](https://github.com/borisdiakur/readback).
|
|
101
123
|
|
|
102
124
|
---
|
|
103
125
|
|
|
104
|
-
|
|
126
|
+
Happy flying!
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../src/banner.js";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { styleText } from "node:util";
|
|
5
|
+
import { ensureAllModels, purgeData } from "../src/setup-manager.js";
|
|
6
|
+
import { cleanTranscript } from "../src/cleaner.js";
|
|
7
|
+
import { startCapture } from "../src/capture.js";
|
|
8
|
+
import { startupCleanup } from "../src/startup-cleanup.js";
|
|
9
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
10
|
+
import { BottomStatusLine } from "../src/status-line.js";
|
|
11
|
+
import { printTranscript } from "../src/presentation/renderer.js";
|
|
12
|
+
await startupCleanup();
|
|
13
|
+
const program = new Command();
|
|
14
|
+
program
|
|
15
|
+
.version(packageJson.version)
|
|
16
|
+
.description(`${packageJson.name}\n${packageJson.description}`)
|
|
17
|
+
.option("--debug", "Enable debug output showing token stream")
|
|
18
|
+
.option("--raw", "Disable all cleaning and formatting")
|
|
19
|
+
.option("--no-altitude", "Disable altitude formatting")
|
|
20
|
+
.option("--no-callsigns", "Disable callsign detection and normalization")
|
|
21
|
+
.option("--no-colors", "Disable coloring")
|
|
22
|
+
.option("--no-fl", "Disable flight level abbreviation")
|
|
23
|
+
.option("--no-frequency", "Disable frequency formatting")
|
|
24
|
+
.option("--no-heading", "Disable heading formatting")
|
|
25
|
+
.option("--no-keywords", "Disable keyword highlighting")
|
|
26
|
+
.option("--no-numbers", "Disable number-word conversion")
|
|
27
|
+
.option("--no-phonetic", "Disable phonetic formatting")
|
|
28
|
+
.option("--no-qnh", "Disable qnh formatting")
|
|
29
|
+
.option("--no-runways", "Disable runway formatting")
|
|
30
|
+
.option("--no-speed", "Disable speed formatting")
|
|
31
|
+
.option("--no-squawk", "Disable squawk formatting")
|
|
32
|
+
.option("--no-waypoints", "Disable waypoint formatting")
|
|
33
|
+
.option("--purge", "Delete all data including downloaded models and exit")
|
|
34
|
+
.helpOption("-h, --help", "Display this help text")
|
|
35
|
+
.parse(process.argv);
|
|
36
|
+
const opts = program.opts();
|
|
37
|
+
if (opts.purge) {
|
|
38
|
+
purgeData();
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
let sigintSeen = false;
|
|
42
|
+
process.on("SIGINT", () => {
|
|
43
|
+
if (sigintSeen)
|
|
44
|
+
return;
|
|
45
|
+
sigintSeen = true;
|
|
46
|
+
console.log("\nBye!");
|
|
47
|
+
process.exit(0);
|
|
48
|
+
});
|
|
49
|
+
const paths = await ensureAllModels();
|
|
50
|
+
const status = new BottomStatusLine();
|
|
51
|
+
startCapture(async (text) => {
|
|
52
|
+
if (opts.raw) {
|
|
53
|
+
console.log(text);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const result = cleanTranscript(text, {
|
|
57
|
+
altitude: opts.altitude,
|
|
58
|
+
callsigns: opts.callsigns,
|
|
59
|
+
colors: opts.colors,
|
|
60
|
+
debug: opts.debug,
|
|
61
|
+
fl: opts.fl,
|
|
62
|
+
frequency: opts.frequency,
|
|
63
|
+
heading: opts.heading,
|
|
64
|
+
keywords: opts.keywords,
|
|
65
|
+
numbers: opts.numbers,
|
|
66
|
+
phonetic: opts.phonetic,
|
|
67
|
+
qnh: opts.qnh,
|
|
68
|
+
raw: opts.raw,
|
|
69
|
+
runways: opts.runways,
|
|
70
|
+
speed: opts.speed,
|
|
71
|
+
squawk: opts.squawk,
|
|
72
|
+
waypoints: opts.waypoints,
|
|
73
|
+
});
|
|
74
|
+
if (opts.debug) {
|
|
75
|
+
console.log(" " + styleText("dim", JSON.stringify(result.tokens)));
|
|
76
|
+
}
|
|
77
|
+
printTranscript(result.output, status);
|
|
78
|
+
}, status, paths.asr);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "readback",
|
|
3
|
+
"version": "0.0.0-alpha.6",
|
|
4
|
+
"description": "Transcribes ATC transmissions into readable text.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ATC",
|
|
7
|
+
"aviation"
|
|
8
|
+
],
|
|
9
|
+
"homepage": "https://github.com/borisdiakur/readback#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/borisdiakur/readback/issues"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/borisdiakur/readback.git"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Boris Diakur (https://borisdiakur.de)",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "dist/bin/index.js",
|
|
21
|
+
"bin": {
|
|
22
|
+
"readback": "dist/bin/index.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist/",
|
|
26
|
+
"models/",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"start": "node ./dist/bin/index.js",
|
|
32
|
+
"start:debug": "node ./dist/bin/index.js --debug",
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"build:start": "tsc && npm start",
|
|
35
|
+
"build:watch": "tsc --watch",
|
|
36
|
+
"extractNavData": "node scripts/extract-nav-data.ts",
|
|
37
|
+
"test": "node --test tests/**/*.spec.ts",
|
|
38
|
+
"test:cover": "node --experimental-test-coverage --test tests/**/*.spec.ts"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"cli-progress": "^3.12.0",
|
|
42
|
+
"cmake-js": "^8.0.0",
|
|
43
|
+
"commander": "^14.0.3",
|
|
44
|
+
"native-recorder-nodejs": "^1.2.0",
|
|
45
|
+
"onnxruntime-node": "^1.24.1",
|
|
46
|
+
"smart-whisper": "^0.8.1",
|
|
47
|
+
"speex-resampler": "^3.0.1",
|
|
48
|
+
"wrap-ansi": "^9.0.2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/cli-progress": "^3.11.6",
|
|
52
|
+
"@types/node": "^25.2.2",
|
|
53
|
+
"prettier": "^3.8.1",
|
|
54
|
+
"typescript": "^5.9.3"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=22"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Whisper } from "smart-whisper";
|
|
2
|
+
let whisper = null;
|
|
3
|
+
let loadedPath = null;
|
|
4
|
+
export default async function run({ audio, asrModelPath }) {
|
|
5
|
+
if (!whisper || loadedPath !== asrModelPath) {
|
|
6
|
+
whisper = new Whisper(asrModelPath, { gpu: true });
|
|
7
|
+
loadedPath = asrModelPath;
|
|
8
|
+
}
|
|
9
|
+
const audioData = new Float32Array(audio);
|
|
10
|
+
const task = await whisper.transcribe(audioData, { language: "en" });
|
|
11
|
+
const result = await task.result;
|
|
12
|
+
const text = result.map((s) => s.text.trim().toLowerCase()).join(" ");
|
|
13
|
+
return { text };
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Whisper } from "smart-whisper";
|
|
2
|
+
const ASR_MODEL_PATH = process.env.ASR_MODEL_PATH;
|
|
3
|
+
if (!ASR_MODEL_PATH) {
|
|
4
|
+
throw new Error("ASR_MODEL_PATH environment variable not set");
|
|
5
|
+
}
|
|
6
|
+
let whisper;
|
|
7
|
+
async function init() {
|
|
8
|
+
try {
|
|
9
|
+
whisper = new Whisper(ASR_MODEL_PATH, { gpu: true });
|
|
10
|
+
if (process.send)
|
|
11
|
+
process.send({ ready: true });
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
console.error(" ASR worker init failed:", err);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
process.on("message", async (msg) => {
|
|
19
|
+
if (!msg.audio || msg.audio.length === 0)
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
22
|
+
const audioData = new Float32Array(msg.audio);
|
|
23
|
+
const task = await whisper.transcribe(audioData, {
|
|
24
|
+
language: "en",
|
|
25
|
+
});
|
|
26
|
+
const result = await task.result;
|
|
27
|
+
const text = result.map((s) => s.text.trim().toLowerCase()).join(" ");
|
|
28
|
+
if (process.send)
|
|
29
|
+
process.send({ text: text, seq: msg.seq });
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error(" ASR worker processing error:", err);
|
|
33
|
+
if (process.send)
|
|
34
|
+
process.send({ text: "", seq: msg.seq, error: String(err) });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
init();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export class BandpassFilter {
|
|
2
|
+
x1 = 0;
|
|
3
|
+
x2 = 0;
|
|
4
|
+
y1 = 0;
|
|
5
|
+
y2 = 0;
|
|
6
|
+
// Coefficients
|
|
7
|
+
b0 = 0;
|
|
8
|
+
b1 = 0;
|
|
9
|
+
b2 = 0;
|
|
10
|
+
a1 = 0;
|
|
11
|
+
a2 = 0;
|
|
12
|
+
constructor(sampleRate, centerFreq, q) {
|
|
13
|
+
this.recalculate(sampleRate, centerFreq, q);
|
|
14
|
+
}
|
|
15
|
+
recalculate(sampleRate, centerFreq, q) {
|
|
16
|
+
// Standard BPF (Constant Peak Gain) implementation
|
|
17
|
+
const w0 = (2 * Math.PI * centerFreq) / sampleRate;
|
|
18
|
+
const alpha = Math.sin(w0) / (2 * q);
|
|
19
|
+
const a0 = 1 + alpha;
|
|
20
|
+
this.b0 = alpha / a0;
|
|
21
|
+
this.b1 = 0;
|
|
22
|
+
this.b2 = -alpha / a0;
|
|
23
|
+
this.a1 = (-2 * Math.cos(w0)) / a0;
|
|
24
|
+
this.a2 = (1 - alpha) / a0;
|
|
25
|
+
// Reset state on recalc (optional, avoids pop)
|
|
26
|
+
this.reset();
|
|
27
|
+
}
|
|
28
|
+
reset() {
|
|
29
|
+
this.x1 = 0;
|
|
30
|
+
this.x2 = 0;
|
|
31
|
+
this.y1 = 0;
|
|
32
|
+
this.y2 = 0;
|
|
33
|
+
}
|
|
34
|
+
process(input) {
|
|
35
|
+
const output = new Float32Array(input.length);
|
|
36
|
+
// Local copy for speed
|
|
37
|
+
let { x1, x2, y1, y2 } = this;
|
|
38
|
+
const { b0, b1, b2, a1, a2 } = this;
|
|
39
|
+
for (let i = 0; i < input.length; i++) {
|
|
40
|
+
const x = input[i];
|
|
41
|
+
// Direct Form I or II (this is DFI)
|
|
42
|
+
const y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
|
|
43
|
+
output[i] = y;
|
|
44
|
+
// Shift
|
|
45
|
+
x2 = x1;
|
|
46
|
+
x1 = x;
|
|
47
|
+
y2 = y1;
|
|
48
|
+
y1 = y;
|
|
49
|
+
}
|
|
50
|
+
// Save state
|
|
51
|
+
this.x1 = x1;
|
|
52
|
+
this.x2 = x2;
|
|
53
|
+
this.y1 = y1;
|
|
54
|
+
this.y2 = y2;
|
|
55
|
+
return output;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { styleText } from "node:util";
|
|
2
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
3
|
+
const g = (t) => styleText(["dim", "white"], t);
|
|
4
|
+
const w = (t) => styleText(["whiteBright"], t);
|
|
5
|
+
const a = (t) => t
|
|
6
|
+
.split("")
|
|
7
|
+
.map((c, i) => i < 2 ||
|
|
8
|
+
i > t.length - 2 ||
|
|
9
|
+
i === Math.floor(t.length / 2) - 1 ||
|
|
10
|
+
i === Math.floor(t.length / 2) + 1 ||
|
|
11
|
+
(i + 1) % 2 === 0
|
|
12
|
+
? g(c)
|
|
13
|
+
: w(c))
|
|
14
|
+
.join("");
|
|
15
|
+
const v = styleText("dim", " v" + packageJson.version);
|
|
16
|
+
console.log(a("█▀▄ █▀▀ █▀█ █▀▄ █▀▄ █▀█ █▀▀ █ █"));
|
|
17
|
+
console.log(a("█▀▄ █▀▀ █▀█ █ █ █▀▄ █▀█ █ █▀▄"));
|
|
18
|
+
console.log(a("█ █ █ █ █ █ █ █ █ █ █ █ █ █"));
|
|
19
|
+
console.log(a("▀ ▀ ▀▀▀ ▀ ▀ ▀▀ ▀▀ ▀ ▀ ▀▀▀ ▀ ▀") + v);
|