@kljj04/cli-kit 1.1.0 → 1.2.1
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.long.md +245 -0
- package/README.md +4 -0
- package/index.js +3 -0
- package/package.json +1 -1
- package/src/color.js +18 -4
- package/src/progress.js +76 -8
package/README.long.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# @kljj04/cli-kit — Full Documentation
|
|
2
|
+
|
|
3
|
+
All-in-one terminal styling library for Node.js.
|
|
4
|
+
RGB truecolor, boxes, tables, spinners, and progress bars — all in one package, zero dependencies.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @kljj04/cli-kit
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Concept
|
|
17
|
+
|
|
18
|
+
All styling follows a consistent 3-step chain:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
kit.color(R, G, B).style(...args).context(value)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| Step | Method | Description |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| 1 | `color(r, g, b)` | Set RGB truecolor (0–255 each) |
|
|
27
|
+
| 2 | `style(...args)` | Set text styles + output type |
|
|
28
|
+
| 3 | `context(value)` | Render the output |
|
|
29
|
+
|
|
30
|
+
You can also write it across multiple lines:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
kit
|
|
34
|
+
.color(255, 100, 50)
|
|
35
|
+
.style('bold', 'box')
|
|
36
|
+
.context('Hello!')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## color(r, g, b)
|
|
42
|
+
|
|
43
|
+
Sets the foreground color using RGB truecolor.
|
|
44
|
+
Requires a terminal with truecolor support (Windows Terminal, iTerm2, most modern terminals).
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
kit.color(255, 0, 0) // red
|
|
48
|
+
kit.color(0, 255, 0) // green
|
|
49
|
+
kit.color(0, 0, 255) // blue
|
|
50
|
+
kit.color(255, 255, 255) // white
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## style(...args)
|
|
56
|
+
|
|
57
|
+
Accepts any combination of text styles and one output type.
|
|
58
|
+
|
|
59
|
+
### Text Styles
|
|
60
|
+
|
|
61
|
+
| Value | Description |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `bold` | Bold text |
|
|
64
|
+
| `dim` | Dimmed text |
|
|
65
|
+
| `italic` | Italic text |
|
|
66
|
+
| `underline` | Underlined text |
|
|
67
|
+
| `inverse` | Inverted fg/bg colors |
|
|
68
|
+
| `strikethrough` | Strikethrough text |
|
|
69
|
+
|
|
70
|
+
### Output Types
|
|
71
|
+
|
|
72
|
+
| Value | Description |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `print` | Plain text (default) |
|
|
75
|
+
| `box` | Unicode box border |
|
|
76
|
+
| `spinner` | Animated spinner |
|
|
77
|
+
| `progress` | Progress bar |
|
|
78
|
+
| `table` | Formatted table |
|
|
79
|
+
|
|
80
|
+
Multiple text styles can be combined. Only one output type should be passed.
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
kit.color(255, 100, 50).style('bold', 'italic', 'box').context('Hello!')
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Output Types
|
|
89
|
+
|
|
90
|
+
### print (default)
|
|
91
|
+
|
|
92
|
+
Prints styled text to stdout. Output type can be omitted.
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
kit.color(255, 100, 50).style('bold').context('Hello!')
|
|
96
|
+
kit.color(255, 100, 50).style('bold', 'print').context('Hello!')
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### box
|
|
102
|
+
|
|
103
|
+
Wraps text in a Unicode box border. Supports multiline strings (`\n`).
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
kit.color(100, 200, 255).style('box').context('Hello!')
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
┌────────┐
|
|
111
|
+
│ Hello! │
|
|
112
|
+
└────────┘
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Multiline:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
kit.color(150, 255, 100).style('box').context('Line one\nLine two\nLine three')
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
┌────────────┐
|
|
123
|
+
│ Line one │
|
|
124
|
+
│ Line two │
|
|
125
|
+
│ Line three │
|
|
126
|
+
└────────────┘
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### spinner
|
|
132
|
+
|
|
133
|
+
Returns a spinner object with a `.stop(text)` method.
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
const s = kit.color(0, 255, 200).style('spinner').context('Loading...')
|
|
137
|
+
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
s.stop('Done!') // clears spinner, prints final message
|
|
140
|
+
}, 2000)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| Method | Description |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `s.stop(text?)` | Stop spinner. Optionally print a final message. |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### progress
|
|
150
|
+
|
|
151
|
+
Pass a number from 0 to 100.
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
kit.color(255, 200, 0).style('progress').context(72)
|
|
155
|
+
// [██████████████████████░░░░░░░░] 72%
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Live mode** — pass `{ value, override: true }` to overwrite the same line.
|
|
159
|
+
Useful inside loops or intervals.
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
// style에 override 추가
|
|
163
|
+
kit.color(0, 255, 150).style('progress', 'override').context(i)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Example with interval:
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
let i = 0
|
|
170
|
+
const interval = setInterval(() => {
|
|
171
|
+
i++
|
|
172
|
+
kit.color(0, 255, 150).style('progress', 'override').context(i)
|
|
173
|
+
if (i >= 100) clearInterval(interval)
|
|
174
|
+
}, 30)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### table
|
|
180
|
+
|
|
181
|
+
Pass an array of objects. Keys are used as column headers automatically.
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
kit.color(200, 150, 255).style('table').context([
|
|
185
|
+
{ name: 'Alice', age: 20, role: 'dev' },
|
|
186
|
+
{ name: 'Bob', age: 25, role: 'designer' },
|
|
187
|
+
{ name: 'Carol', age: 30, role: 'PM' },
|
|
188
|
+
])
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
┌───────┬─────┬──────────┐
|
|
193
|
+
│ name │ age │ role │
|
|
194
|
+
┼───────┼─────┼──────────┼
|
|
195
|
+
│ Alice │ 20 │ dev │
|
|
196
|
+
│ Bob │ 25 │ designer │
|
|
197
|
+
│ Carol │ 30 │ PM │
|
|
198
|
+
└───────┴─────┴──────────┘
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Full Example
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
const kit = require('@kljj04/cli-kit')
|
|
207
|
+
|
|
208
|
+
// box
|
|
209
|
+
kit.color(0, 255, 100).style('bold', 'box').context('Welcome!')
|
|
210
|
+
|
|
211
|
+
// spinner
|
|
212
|
+
const s = kit.color(100, 200, 255).style('spinner').context('Loading...')
|
|
213
|
+
setTimeout(() => s.stop('Done!'), 1500)
|
|
214
|
+
|
|
215
|
+
// table
|
|
216
|
+
kit.color(200, 150, 255).style('table').context([
|
|
217
|
+
{ task: 'Build', status: 'OK' },
|
|
218
|
+
{ task: 'Deploy', status: 'OK' },
|
|
219
|
+
])
|
|
220
|
+
|
|
221
|
+
// progress (live)
|
|
222
|
+
let i = 0
|
|
223
|
+
const iv = setInterval(() => {
|
|
224
|
+
i++
|
|
225
|
+
kit.color(255, 200, 0).style('progress', 'override').context(i)
|
|
226
|
+
if (i >= 100) {
|
|
227
|
+
clearInterval(iv)
|
|
228
|
+
kit.color(0, 255, 100).style('bold').context('Complete!')
|
|
229
|
+
}
|
|
230
|
+
}, 20)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Planned Features
|
|
236
|
+
|
|
237
|
+
- `gradient([r,g,b], [r,g,b])` — per-character color interpolation for progress bars
|
|
238
|
+
- `style('round', 'box')` — rounded box corners
|
|
239
|
+
- `bgColor(r, g, b)` — background color support
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT
|
package/README.md
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
package/src/color.js
CHANGED
|
@@ -24,6 +24,9 @@ class KitBuilder {
|
|
|
24
24
|
this._b = 255;
|
|
25
25
|
this._styles = [];
|
|
26
26
|
this._outputType = 'print';
|
|
27
|
+
this._override = false;
|
|
28
|
+
this._gradientColors = null;
|
|
29
|
+
this._fixedGradient = false;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
color(r, g, b) {
|
|
@@ -33,10 +36,19 @@ class KitBuilder {
|
|
|
33
36
|
return this;
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
gradient(from, to) {
|
|
40
|
+
this._gradientColors = [from, to];
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
style(...args) {
|
|
37
45
|
const outputTypes = ['print', 'box', 'spinner', 'progress', 'table'];
|
|
38
46
|
args.forEach(arg => {
|
|
39
|
-
if (
|
|
47
|
+
if (arg === 'override') {
|
|
48
|
+
this._override = true;
|
|
49
|
+
} else if (arg === 'fixedgradient') {
|
|
50
|
+
this._fixedGradient = true;
|
|
51
|
+
} else if (outputTypes.includes(arg)) {
|
|
40
52
|
this._outputType = arg;
|
|
41
53
|
} else if (STYLES[arg]) {
|
|
42
54
|
this._styles.push(arg);
|
|
@@ -46,7 +58,7 @@ class KitBuilder {
|
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
_buildPrefix() {
|
|
49
|
-
const colorCode = rgb(this._r, this._g, this._b);
|
|
61
|
+
const colorCode = this._gradientColors ? '' : rgb(this._r, this._g, this._b);
|
|
50
62
|
const styleCodes = this._styles.map(s => STYLES[s]).join('');
|
|
51
63
|
return `${colorCode}${styleCodes}`;
|
|
52
64
|
}
|
|
@@ -63,15 +75,17 @@ class KitBuilder {
|
|
|
63
75
|
case 'spinner':
|
|
64
76
|
return require('./spinner').spinner(prefix, value);
|
|
65
77
|
case 'progress':
|
|
66
|
-
require('./progress').progress(prefix, value);
|
|
78
|
+
require('./progress').progress(prefix, value, this._override, this._gradientColors, this._fixedGradient);
|
|
67
79
|
break;
|
|
68
80
|
case 'table':
|
|
69
81
|
require('./table').table(prefix, value);
|
|
70
82
|
break;
|
|
71
83
|
}
|
|
72
|
-
// reset for next use
|
|
73
84
|
this._styles = [];
|
|
74
85
|
this._outputType = 'print';
|
|
86
|
+
this._override = false;
|
|
87
|
+
this._gradientColors = null;
|
|
88
|
+
this._fixedGradient = false;
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
|
package/src/progress.js
CHANGED
|
@@ -1,21 +1,89 @@
|
|
|
1
1
|
const { RESET } = require('./color');
|
|
2
2
|
|
|
3
|
-
function
|
|
3
|
+
function linearize(c) {
|
|
4
|
+
c /= 255;
|
|
5
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function delinearize(c) {
|
|
9
|
+
c = c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
10
|
+
return Math.round(Math.min(1, Math.max(0, c)) * 255);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function rgbToOklch(r, g, b) {
|
|
14
|
+
const lr = linearize(r), lg = linearize(g), lb = linearize(b);
|
|
15
|
+
const l = 0.4122214708*lr + 0.5363325363*lg + 0.0514459929*lb;
|
|
16
|
+
const m = 0.2119034982*lr + 0.6806995451*lg + 0.1073969566*lb;
|
|
17
|
+
const s = 0.0883024619*lr + 0.2817188376*lg + 0.6299787005*lb;
|
|
18
|
+
const l_ = Math.cbrt(l), m_ = Math.cbrt(m), s_ = Math.cbrt(s);
|
|
19
|
+
const L = 0.2104542553*l_ + 0.7936177850*m_ - 0.0040720468*s_;
|
|
20
|
+
const a = 1.9779984951*l_ - 2.4285922050*m_ + 0.4505937099*s_;
|
|
21
|
+
const bVal = 0.0259040371*l_ + 0.7827717662*m_ - 0.8086757660*s_;
|
|
22
|
+
const C = Math.sqrt(a*a + bVal*bVal);
|
|
23
|
+
const H = Math.atan2(bVal, a) * (180/Math.PI);
|
|
24
|
+
return [L, C, H < 0 ? H+360 : H];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function oklchToRgb(L, C, H) {
|
|
28
|
+
const hRad = H * (Math.PI/180);
|
|
29
|
+
const a = C*Math.cos(hRad);
|
|
30
|
+
const b = C*Math.sin(hRad);
|
|
31
|
+
const l_ = L+0.3963377774*a+0.2158037573*b;
|
|
32
|
+
const m_ = L-0.1055613458*a-0.0638541728*b;
|
|
33
|
+
const s_ = L-0.0894841775*a-1.2914855480*b;
|
|
34
|
+
const lc=l_*l_*l_, mc=m_*m_*m_, sc=s_*s_*s_;
|
|
35
|
+
const lr = 4.0767416621*lc - 3.3077115913*mc + 0.2309699292*sc;
|
|
36
|
+
const lg = -1.2684380046*lc + 2.6097574011*mc - 0.3413193965*sc;
|
|
37
|
+
const lb = -0.0041960863*lc - 0.7034186147*mc + 1.7076147010*sc;
|
|
38
|
+
return [delinearize(lr), delinearize(lg), delinearize(lb)];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function lerpOklch(from, to, t) {
|
|
42
|
+
const a = rgbToOklch(...from);
|
|
43
|
+
const b = rgbToOklch(...to);
|
|
44
|
+
let dH = b[2]-a[2];
|
|
45
|
+
if (dH > 180) dH -= 360;
|
|
46
|
+
if (dH < -180) dH += 360;
|
|
47
|
+
return oklchToRgb(a[0]+(b[0]-a[0])*t, a[1]+(b[1]-a[1])*t, a[2]+dH*t);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderBar(prefix, pct, gradientColors, fixedGradient) {
|
|
4
51
|
const width = 30;
|
|
5
52
|
const filled = Math.round((pct / 100) * width);
|
|
6
53
|
const empty = width - filled;
|
|
7
|
-
|
|
54
|
+
|
|
55
|
+
let bar = '';
|
|
56
|
+
if (gradientColors && fixedGradient) {
|
|
57
|
+
// 전체 30칸 기준으로 색 미리 계산, 채워진 만큼만 표시
|
|
58
|
+
for (let i = 0; i < filled; i++) {
|
|
59
|
+
const t = (width > 1) ? i / (width - 1) : 0;
|
|
60
|
+
const [r, g, b] = lerpOklch(gradientColors[0], gradientColors[1], t);
|
|
61
|
+
bar += `\x1b[38;2;${r};${g};${b}m█`;
|
|
62
|
+
}
|
|
63
|
+
bar += `${RESET}${'░'.repeat(empty)}`;
|
|
64
|
+
} else if (gradientColors) {
|
|
65
|
+
// 채워진 칸 기준 보간 (기존 gradient)
|
|
66
|
+
for (let i = 0; i < filled; i++) {
|
|
67
|
+
const t = filled > 1 ? i / (filled - 1) : 0;
|
|
68
|
+
const [r, g, b] = lerpOklch(gradientColors[0], gradientColors[1], t);
|
|
69
|
+
bar += `\x1b[38;2;${r};${g};${b}m█`;
|
|
70
|
+
}
|
|
71
|
+
bar += `${RESET}${'░'.repeat(empty)}`;
|
|
72
|
+
} else {
|
|
73
|
+
bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
74
|
+
}
|
|
75
|
+
|
|
8
76
|
return `${prefix}[${bar}] ${pct}%${RESET}`;
|
|
9
77
|
}
|
|
10
78
|
|
|
11
|
-
function progress(prefix, value) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
79
|
+
function progress(prefix, value, override, gradientColors, fixedGradient) {
|
|
80
|
+
const pct = Math.min(100, Math.max(0, Number(value)));
|
|
81
|
+
const bar = renderBar(prefix, pct, gradientColors, fixedGradient);
|
|
82
|
+
if (override) {
|
|
83
|
+
process.stdout.write(`\r${bar}`);
|
|
15
84
|
if (pct >= 100) process.stdout.write('\n');
|
|
16
85
|
} else {
|
|
17
|
-
|
|
18
|
-
console.log(renderBar(prefix, pct));
|
|
86
|
+
console.log(bar);
|
|
19
87
|
}
|
|
20
88
|
}
|
|
21
89
|
|