brightctrl 0.0.0 → 0.0.2

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 ADDED
@@ -0,0 +1,85 @@
1
+ # BrightCtrl
2
+
3
+ A lightweight DDC/CI monitor brightness controller for Linux — now a terminal UI built with [React](https://react.dev) / [Ink](https://github.com/vadimdemedes/ink) on [Bun](https://bun.sh).
4
+
5
+ ![Platform](https://img.shields.io/badge/platform-Linux-lightgrey)
6
+
7
+ ## Features
8
+
9
+ - Auto-detects all DDC/CI-capable monitors
10
+ - Per-monitor brightness bars with live percentage
11
+ - Arrow key / vim (`h`/`l`) brightness adjustment (5% steps)
12
+ - **Sync mode** (`s`) — control all monitors at once
13
+ - Direct input dialog (`i`) — type a specific brightness value
14
+ - 500ms debounce on DDC writes (no flooding the i2c bus)
15
+ - Brightness-dependent color coding (red ≤20%, cyan 21-89%, yellow ≥90%)
16
+
17
+ ## Requirements
18
+
19
+ ### System
20
+
21
+ | Distro | Command |
22
+ |---|---|
23
+ | Arch / Manjaro | `sudo pacman -S ddcutil` |
24
+ | Debian / Ubuntu | `sudo apt install ddcutil` |
25
+ | Fedora / RHEL | `sudo dnf install ddcutil` |
26
+
27
+ Then set up the i2c kernel module and add yourself to the i2c group:
28
+
29
+ ```bash
30
+ sudo usermod -aG i2c $USER
31
+ sudo modprobe i2c-dev
32
+
33
+ # Persist across reboots:
34
+ echo 'i2c-dev' | sudo tee /etc/modules-load.d/i2c.conf
35
+
36
+ # Log out and back in for the group change to take effect.
37
+ ```
38
+
39
+ Verify everything works:
40
+
41
+ ```bash
42
+ ddcutil detect
43
+ ```
44
+
45
+ ### Runtime
46
+
47
+ - [Node.js](https://nodejs.org) >= 18
48
+ - Or [Bun](https://bun.sh) 1.x (for development)
49
+
50
+ ## Install & Run
51
+
52
+ ### Quick — no install needed
53
+
54
+ ```bash
55
+ npx brightctrl
56
+ ```
57
+
58
+ ### Global install
59
+
60
+ ```bash
61
+ npm install -g brightctrl
62
+ brightctrl
63
+ ```
64
+
65
+ ### From source
66
+
67
+ ```bash
68
+ git clone https://github.com/shahriyardx/brightctrl.git
69
+ cd brightctrl
70
+ bun install
71
+ bun src/index.tsx
72
+ ```
73
+
74
+ ### Controls
75
+
76
+ | Key | Action |
77
+ |---|---|
78
+ | `↑` / `↓` | Select monitor |
79
+ | `←` / `h` | Decrease brightness 5% |
80
+ | `→` / `l` | Increase brightness 5% |
81
+ | `i` | Open input dialog (type 0–100, Enter to confirm) |
82
+ | `s` | Toggle sync mode (all monitors) |
83
+ | `r` | Refresh monitor detection |
84
+ | `q` | Quit |
85
+
package/dist/index.js ADDED
@@ -0,0 +1,535 @@
1
+ #!/usr/bin/env node
2
+ // @bun
3
+
4
+ // src/index.tsx
5
+ import { render } from "ink";
6
+
7
+ // src/app.tsx
8
+ import { Box, Text, useApp, useInput } from "ink";
9
+ import { useCallback, useEffect, useRef, useState } from "react";
10
+
11
+ // src/ddcutil.ts
12
+ import { execFile } from "node:child_process";
13
+ import { promisify } from "node:util";
14
+ var execFileAsync = promisify(execFile);
15
+ var isWindows = process.platform === "win32";
16
+ var isMac = process.platform === "darwin";
17
+ function ddcutil(args, timeout = 8000) {
18
+ return execFileAsync("ddcutil", args, {
19
+ timeout,
20
+ encoding: "utf-8"
21
+ }).then((r) => r.stdout);
22
+ }
23
+ function powerShell(script, timeout = 8000) {
24
+ return execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeout, encoding: "utf-8" }).then((r) => r.stdout.trim());
25
+ }
26
+ async function checkDdcutil() {
27
+ if (isWindows) {
28
+ try {
29
+ const out = await powerShell("Get-CimInstance -Namespace root/WMI -ClassName WmiMonitorBrightness | ConvertTo-Json -Depth 3", 5000);
30
+ return out.length > 0 && out !== "null";
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+ if (isMac)
36
+ return false;
37
+ try {
38
+ await execFileAsync("which", ["ddcutil"]);
39
+ return true;
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+ async function detectMonitorsLinux() {
45
+ const stdout = await ddcutil(["detect", "--brief"], 15000);
46
+ const monitors = [];
47
+ let current = null;
48
+ for (const line of stdout.split(`
49
+ `)) {
50
+ const t = line.trim();
51
+ if (t.startsWith("Display ")) {
52
+ if (current?.index != null)
53
+ monitors.push(current);
54
+ const m = t.match(/Display\s+(\d+)/);
55
+ const idxStr = m?.[1];
56
+ current = {
57
+ index: idxStr ? Number.parseInt(idxStr, 10) : monitors.length + 1,
58
+ name: "Unknown Monitor",
59
+ bus: ""
60
+ };
61
+ } else if (t.includes("I2C bus:")) {
62
+ if (current)
63
+ current.bus = t.replace("I2C bus:", "").trim();
64
+ } else if (t.startsWith("Monitor:")) {
65
+ if (current) {
66
+ const p = t.replace("Monitor:", "").trim();
67
+ current.name = p.replace(/\s+unspecified/i, "").trim() || "Monitor";
68
+ }
69
+ }
70
+ }
71
+ if (current?.index != null)
72
+ monitors.push(current);
73
+ return monitors;
74
+ }
75
+ async function detectMonitorsWindows() {
76
+ const ps = `$b=Get-CimInstance -Namespace root/WMI -ClassName WmiMonitorBrightness; $r=@(); $i=0; foreach($m in $b){$i++; $r+=[PSCustomObject]@{index=$i;name=[string]($m.InstanceName -replace '.*\\\\([^\\\\]+)\\\\.*','$1');bus=$m.InstanceName}}; $r | ConvertTo-Json -Depth 3`;
77
+ const stdout = await powerShell(ps, 1e4);
78
+ if (!stdout || stdout === "null")
79
+ return [];
80
+ const data = JSON.parse(stdout);
81
+ const arr = Array.isArray(data) ? data : [data];
82
+ return arr.map((d, i) => ({
83
+ index: i + 1,
84
+ name: typeof d.name === "string" && d.name ? d.name : "Monitor",
85
+ bus: typeof d.bus === "string" ? d.bus : ""
86
+ }));
87
+ }
88
+ async function detectMonitorsMac() {
89
+ return [];
90
+ }
91
+ async function getBrightnessMac(_displayIndex) {
92
+ return null;
93
+ }
94
+ async function setBrightnessMac(_displayIndex, _value) {
95
+ return false;
96
+ }
97
+ async function detectMonitors() {
98
+ if (isWindows)
99
+ return detectMonitorsWindows();
100
+ if (isMac)
101
+ return detectMonitorsMac();
102
+ return detectMonitorsLinux();
103
+ }
104
+ async function getBrightnessLinux(displayIndex) {
105
+ try {
106
+ const stdout = await ddcutil(["getvcp", "10", `--display=${displayIndex}`], 5000);
107
+ const m = stdout.match(/current value\s*=\s*(\d+)/);
108
+ const val = m?.[1];
109
+ return val ? Number.parseInt(val, 10) : null;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ async function getBrightnessWindows(displayIndex) {
115
+ try {
116
+ const idx = Math.max(0, displayIndex - 1);
117
+ const ps = `$b=Get-CimInstance -Namespace root/WMI -ClassName WmiMonitorBrightness; if($b -and $b.Count -ge ${idx + 1}){$b[${idx}].CurrentBrightness}else{-1}`;
118
+ const stdout = await powerShell(ps, 5000);
119
+ const v = Number.parseInt(stdout, 10);
120
+ return Number.isNaN(v) || v < 0 ? null : v;
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+ async function getBrightness(displayIndex) {
126
+ if (isWindows)
127
+ return getBrightnessWindows(displayIndex);
128
+ if (isMac)
129
+ return getBrightnessMac(displayIndex);
130
+ return getBrightnessLinux(displayIndex);
131
+ }
132
+ async function setBrightnessLinux(displayIndex, value) {
133
+ try {
134
+ await ddcutil(["setvcp", "10", String(Math.round(value)), `--display=${displayIndex}`], 5000);
135
+ return true;
136
+ } catch {
137
+ return false;
138
+ }
139
+ }
140
+ async function setBrightnessWindows(displayIndex, value) {
141
+ try {
142
+ const idx = Math.max(0, displayIndex - 1);
143
+ const ps = `$m=Get-CimInstance -Namespace root/WMI -ClassName WmiMonitorBrightnessMethods; if($m -and $m.Count -ge ${idx + 1}){$m[${idx}].WmiSetBrightness(1,${Math.round(value)})}`;
144
+ await powerShell(ps, 5000);
145
+ return true;
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+ async function setBrightness(displayIndex, value) {
151
+ if (isWindows)
152
+ return setBrightnessWindows(displayIndex, value);
153
+ if (isMac)
154
+ return setBrightnessMac(displayIndex, value);
155
+ return setBrightnessLinux(displayIndex, value);
156
+ }
157
+
158
+ // src/app.tsx
159
+ import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
160
+ function BrightnessBar({
161
+ value,
162
+ width = 25
163
+ }) {
164
+ const filled = Math.round(value / 100 * width);
165
+ const empty = width - filled;
166
+ const color = value <= 20 ? "red" : value >= 90 ? "yellow" : "cyan";
167
+ return /* @__PURE__ */ jsxDEV(Box, {
168
+ children: [
169
+ /* @__PURE__ */ jsxDEV(Text, {
170
+ color,
171
+ children: filled > 0 ? "█".repeat(filled) : ""
172
+ }, undefined, false, undefined, this),
173
+ /* @__PURE__ */ jsxDEV(Text, {
174
+ color: "#444",
175
+ children: empty > 0 ? "░".repeat(empty) : ""
176
+ }, undefined, false, undefined, this),
177
+ /* @__PURE__ */ jsxDEV(Text, {
178
+ children: " "
179
+ }, undefined, false, undefined, this),
180
+ /* @__PURE__ */ jsxDEV(Text, {
181
+ color,
182
+ bold: true,
183
+ children: [
184
+ Math.round(value),
185
+ "%"
186
+ ]
187
+ }, undefined, true, undefined, this)
188
+ ]
189
+ }, undefined, true, undefined, this);
190
+ }
191
+ function MonitorCard({
192
+ monitor,
193
+ selected
194
+ }) {
195
+ return /* @__PURE__ */ jsxDEV(Box, {
196
+ borderStyle: selected ? "round" : "single",
197
+ borderColor: selected ? "cyan" : "gray",
198
+ flexDirection: "column",
199
+ paddingX: 1,
200
+ paddingY: 0,
201
+ children: [
202
+ /* @__PURE__ */ jsxDEV(Box, {
203
+ gap: 1,
204
+ children: [
205
+ /* @__PURE__ */ jsxDEV(Text, {
206
+ bold: true,
207
+ color: selected ? "cyan" : "white",
208
+ children: [
209
+ "Display ",
210
+ monitor.index
211
+ ]
212
+ }, undefined, true, undefined, this),
213
+ /* @__PURE__ */ jsxDEV(Text, {
214
+ color: "white",
215
+ children: monitor.name
216
+ }, undefined, false, undefined, this),
217
+ /* @__PURE__ */ jsxDEV(Text, {
218
+ color: "gray",
219
+ children: monitor.bus
220
+ }, undefined, false, undefined, this),
221
+ selected ? /* @__PURE__ */ jsxDEV(Text, {
222
+ color: "cyan",
223
+ children: "◄"
224
+ }, undefined, false, undefined, this) : null
225
+ ]
226
+ }, undefined, true, undefined, this),
227
+ /* @__PURE__ */ jsxDEV(Box, {
228
+ children: /* @__PURE__ */ jsxDEV(BrightnessBar, {
229
+ value: monitor.brightness
230
+ }, undefined, false, undefined, this)
231
+ }, undefined, false, undefined, this)
232
+ ]
233
+ }, undefined, true, undefined, this);
234
+ }
235
+ function ErrorPanel() {
236
+ const plat = process.platform;
237
+ if (plat === "darwin")
238
+ return null;
239
+ return /* @__PURE__ */ jsxDEV(Box, {
240
+ flexDirection: "column",
241
+ marginTop: 1,
242
+ children: [
243
+ /* @__PURE__ */ jsxDEV(Text, {
244
+ color: "yellow",
245
+ children: "Troubleshooting:"
246
+ }, undefined, false, undefined, this),
247
+ plat === "win32" ? /* @__PURE__ */ jsxDEV(Fragment, {
248
+ children: [
249
+ /* @__PURE__ */ jsxDEV(Text, {
250
+ color: "gray",
251
+ children: "Some GPUs/monitors don't expose brightness via WMI."
252
+ }, undefined, false, undefined, this),
253
+ /* @__PURE__ */ jsxDEV(Text, {
254
+ color: "gray",
255
+ children: "Try installing MonitorController from Windows Store."
256
+ }, undefined, false, undefined, this)
257
+ ]
258
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
259
+ children: [
260
+ /* @__PURE__ */ jsxDEV(Text, {
261
+ color: "gray",
262
+ children: " sudo pacman -S ddcutil"
263
+ }, undefined, false, undefined, this),
264
+ /* @__PURE__ */ jsxDEV(Text, {
265
+ color: "gray",
266
+ children: " sudo usermod -aG i2c $USER"
267
+ }, undefined, false, undefined, this),
268
+ /* @__PURE__ */ jsxDEV(Text, {
269
+ color: "gray",
270
+ children: " sudo modprobe i2c-dev"
271
+ }, undefined, false, undefined, this),
272
+ /* @__PURE__ */ jsxDEV(Text, {
273
+ color: "gray",
274
+ children: "echo 'i2c-dev' | sudo tee /etc/modules-load.d/i2c.conf"
275
+ }, undefined, false, undefined, this),
276
+ /* @__PURE__ */ jsxDEV(Text, {
277
+ color: "gray",
278
+ children: " (log out and in for group change)"
279
+ }, undefined, false, undefined, this)
280
+ ]
281
+ }, undefined, true, undefined, this)
282
+ ]
283
+ }, undefined, true, undefined, this);
284
+ }
285
+ function App() {
286
+ const { exit } = useApp();
287
+ const [monitors, setMonitors] = useState([]);
288
+ const [selected, setSelected] = useState(0);
289
+ const [syncMode, setSyncMode] = useState(false);
290
+ const [status, setStatus] = useState("Starting...");
291
+ const [error, setError] = useState(null);
292
+ const [loading, setLoading] = useState(true);
293
+ const [typing, setTyping] = useState(false);
294
+ const [typed, setTyped] = useState("");
295
+ const debounceTimer = useRef(null);
296
+ const pendingRef = useRef(new Map);
297
+ function scheduleBrightness(index, value) {
298
+ pendingRef.current.set(index, value);
299
+ if (debounceTimer.current)
300
+ clearTimeout(debounceTimer.current);
301
+ debounceTimer.current = setTimeout(() => {
302
+ const vals = new Map(pendingRef.current);
303
+ pendingRef.current.clear();
304
+ for (const [idx, v] of vals) {
305
+ setBrightness(idx, v);
306
+ }
307
+ }, 500);
308
+ }
309
+ const load = useCallback(async () => {
310
+ setLoading(true);
311
+ setError(null);
312
+ setStatus("Checking ddcutil...");
313
+ const hasBackend = await checkDdcutil();
314
+ if (!hasBackend) {
315
+ if (process.platform === "win32") {
316
+ setError("No monitors with WMI brightness control found");
317
+ } else if (process.platform === "darwin") {
318
+ setError("macOS is not supported at this moment");
319
+ } else {
320
+ setError("ddcutil not found. Install: sudo pacman -S ddcutil");
321
+ }
322
+ setStatus("backend unavailable");
323
+ setLoading(false);
324
+ return;
325
+ }
326
+ setStatus("Detecting monitors...");
327
+ const infoList = await detectMonitors();
328
+ if (infoList.length === 0) {
329
+ setError("No DDC/CI monitors detected");
330
+ setStatus("No monitors found");
331
+ setLoading(false);
332
+ return;
333
+ }
334
+ setStatus(`Reading brightness for ${infoList.length} monitor(s)...`);
335
+ const loaded = [];
336
+ for (const info of infoList) {
337
+ const b = await getBrightness(info.index);
338
+ loaded.push({ ...info, brightness: b ?? 50 });
339
+ }
340
+ setMonitors(loaded);
341
+ setSelected((s) => Math.min(s, loaded.length - 1));
342
+ setStatus(`${loaded.length} monitor(s) — DDC/CI via ddcutil`);
343
+ setLoading(false);
344
+ }, []);
345
+ useEffect(() => {
346
+ load();
347
+ }, [load]);
348
+ function adjustBrightness(delta) {
349
+ if (monitors.length === 0)
350
+ return;
351
+ const idx = selected;
352
+ setMonitors((prev) => prev.map((m, i) => {
353
+ if (!syncMode && i !== idx)
354
+ return m;
355
+ const v = Math.max(0, Math.min(100, m.brightness + delta));
356
+ scheduleBrightness(m.index, v);
357
+ return { ...m, brightness: v };
358
+ }));
359
+ }
360
+ useInput((input, key) => {
361
+ if (typing) {
362
+ if (key.escape) {
363
+ setTyping(false);
364
+ setTyped("");
365
+ } else if (key.return) {
366
+ const v = Number.parseInt(typed, 10);
367
+ if (!Number.isNaN(v) && v >= 0 && v <= 100) {
368
+ setMonitors((prev) => prev.map((m, i) => {
369
+ if (!syncMode && i !== selected)
370
+ return m;
371
+ scheduleBrightness(m.index, v);
372
+ return { ...m, brightness: v };
373
+ }));
374
+ }
375
+ setTyping(false);
376
+ setTyped("");
377
+ } else if (key.backspace) {
378
+ setTyped((p) => p.slice(0, -1));
379
+ } else if (/^[0-9]$/.test(input)) {
380
+ setTyped((p) => (p + input).slice(0, 3));
381
+ }
382
+ return;
383
+ }
384
+ if (input === "q")
385
+ exit();
386
+ if (key.upArrow) {
387
+ setSelected((s) => Math.max(0, s - 1));
388
+ } else if (key.downArrow) {
389
+ setSelected((s) => Math.min(monitors.length - 1, s + 1));
390
+ } else if (input === "h" || key.leftArrow) {
391
+ adjustBrightness(-5);
392
+ } else if (input === "l" || key.rightArrow) {
393
+ adjustBrightness(5);
394
+ } else if (input === "s") {
395
+ setSyncMode((s) => !s);
396
+ } else if (input === "r") {
397
+ load();
398
+ } else if (input === "i") {
399
+ setTyping(true);
400
+ setTyped("");
401
+ }
402
+ });
403
+ return /* @__PURE__ */ jsxDEV(Box, {
404
+ flexDirection: "column",
405
+ padding: 1,
406
+ children: [
407
+ /* @__PURE__ */ jsxDEV(Box, {
408
+ gap: 2,
409
+ children: [
410
+ /* @__PURE__ */ jsxDEV(Text, {
411
+ bold: true,
412
+ color: "cyan",
413
+ children: "BrightCtrl"
414
+ }, undefined, false, undefined, this),
415
+ /* @__PURE__ */ jsxDEV(Text, {
416
+ color: "gray",
417
+ children: "DDC/CI Brightness Controller"
418
+ }, undefined, false, undefined, this)
419
+ ]
420
+ }, undefined, true, undefined, this),
421
+ /* @__PURE__ */ jsxDEV(Text, {
422
+ color: "#333",
423
+ children: "─".repeat(56)
424
+ }, undefined, false, undefined, this),
425
+ /* @__PURE__ */ jsxDEV(Box, {
426
+ marginBottom: 1,
427
+ gap: 3,
428
+ children: [
429
+ /* @__PURE__ */ jsxDEV(Text, {
430
+ color: syncMode ? "green" : "gray",
431
+ children: [
432
+ "[",
433
+ syncMode ? "✓" : " ",
434
+ "] Sync All ",
435
+ /* @__PURE__ */ jsxDEV(Text, {
436
+ color: "gray",
437
+ children: "(s)"
438
+ }, undefined, false, undefined, this)
439
+ ]
440
+ }, undefined, true, undefined, this),
441
+ /* @__PURE__ */ jsxDEV(Text, {
442
+ children: [
443
+ "⟳ Refresh ",
444
+ /* @__PURE__ */ jsxDEV(Text, {
445
+ color: "gray",
446
+ children: "(r)"
447
+ }, undefined, false, undefined, this)
448
+ ]
449
+ }, undefined, true, undefined, this),
450
+ /* @__PURE__ */ jsxDEV(Text, {
451
+ children: [
452
+ "✕ Quit ",
453
+ /* @__PURE__ */ jsxDEV(Text, {
454
+ color: "gray",
455
+ children: "(q)"
456
+ }, undefined, false, undefined, this)
457
+ ]
458
+ }, undefined, true, undefined, this)
459
+ ]
460
+ }, undefined, true, undefined, this),
461
+ /* @__PURE__ */ jsxDEV(Box, {
462
+ marginBottom: 1,
463
+ children: loading ? /* @__PURE__ */ jsxDEV(Text, {
464
+ color: "gray",
465
+ children: [
466
+ status,
467
+ "..."
468
+ ]
469
+ }, undefined, true, undefined, this) : error ? /* @__PURE__ */ jsxDEV(Text, {
470
+ color: "red",
471
+ children: error
472
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
473
+ color: "gray",
474
+ children: status
475
+ }, undefined, false, undefined, this)
476
+ }, undefined, false, undefined, this),
477
+ !loading && !error && monitors.length > 0 ? /* @__PURE__ */ jsxDEV(Box, {
478
+ flexDirection: "column",
479
+ gap: 0,
480
+ children: monitors.map((m, i) => /* @__PURE__ */ jsxDEV(MonitorCard, {
481
+ monitor: m,
482
+ selected: i === selected
483
+ }, m.index, false, undefined, this))
484
+ }, undefined, false, undefined, this) : null,
485
+ !loading && error ? /* @__PURE__ */ jsxDEV(ErrorPanel, {}, undefined, false, undefined, this) : null,
486
+ typing ? /* @__PURE__ */ jsxDEV(Box, {
487
+ flexDirection: "column",
488
+ marginTop: 1,
489
+ marginBottom: 1,
490
+ children: /* @__PURE__ */ jsxDEV(Box, {
491
+ borderStyle: "round",
492
+ borderColor: "cyan",
493
+ paddingX: 1,
494
+ flexDirection: "column",
495
+ children: [
496
+ /* @__PURE__ */ jsxDEV(Box, {
497
+ gap: 1,
498
+ children: [
499
+ /* @__PURE__ */ jsxDEV(Text, {
500
+ bold: true,
501
+ color: "cyan",
502
+ children: "Brightness:"
503
+ }, undefined, false, undefined, this),
504
+ /* @__PURE__ */ jsxDEV(Text, {
505
+ bold: true,
506
+ color: "white",
507
+ children: [
508
+ typed || "0",
509
+ "_"
510
+ ]
511
+ }, undefined, true, undefined, this)
512
+ ]
513
+ }, undefined, true, undefined, this),
514
+ /* @__PURE__ */ jsxDEV(Text, {
515
+ color: "gray",
516
+ children: "Enter 0-100 ↵ confirm ⎋ cancel"
517
+ }, undefined, false, undefined, this)
518
+ ]
519
+ }, undefined, true, undefined, this)
520
+ }, undefined, false, undefined, this) : null,
521
+ !loading ? /* @__PURE__ */ jsxDEV(Box, {
522
+ marginTop: 1,
523
+ children: error ? null : /* @__PURE__ */ jsxDEV(Text, {
524
+ color: "gray",
525
+ children: "↑↓ select h/l ←→ brightness i input s sync r refresh q quit"
526
+ }, undefined, false, undefined, this)
527
+ }, undefined, false, undefined, this) : null
528
+ ]
529
+ }, undefined, true, undefined, this);
530
+ }
531
+
532
+ // src/index.tsx
533
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
534
+ var { waitUntilExit } = render(/* @__PURE__ */ jsxDEV2(App, {}, undefined, false, undefined, this));
535
+ await waitUntilExit();
package/package.json CHANGED
@@ -1,11 +1,47 @@
1
1
  {
2
2
  "name": "brightctrl",
3
- "version": "0.0.0",
4
- "description": "Placeholder for brightctrl",
5
- "main": "index.js",
6
- "scripts": {},
7
- "keywords": [],
8
- "author": "shahriyardx (https://www.npmjs.com/~shahriyardx)",
9
- "license": "UNLICENSED",
10
- "private": false
11
- }
3
+ "version": "0.0.2",
4
+ "description": "Terminal UI for DDC/CI monitor brightness control",
5
+ "type": "module",
6
+ "bin": {
7
+ "brightctrl": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "scripts": {
17
+ "build": "bun build src/index.tsx --outdir dist --target node --format esm --external ink --external react --external react-reconciler --external scheduler --external '@types/react' && sed -i 's_^#!/usr/bin/env bun_#!/usr/bin/env node_' dist/index.js",
18
+ "prepublishOnly": "bun run build",
19
+ "lint": "biome check src/",
20
+ "format": "biome check --write src/",
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "dependencies": {
24
+ "ink": "^5.2.0",
25
+ "react": "^18.3.0"
26
+ },
27
+ "devDependencies": {
28
+ "@biomejs/biome": "^2.4.15",
29
+ "@types/react": "^18.3.0",
30
+ "typescript": "^5.0.0"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/shahriyardx/brightctrl.git"
35
+ },
36
+ "homepage": "https://github.com/shahriyardx/brightctrl",
37
+ "keywords": [
38
+ "brightness",
39
+ "ddc-ci",
40
+ "ddcutil",
41
+ "monitor",
42
+ "tui",
43
+ "ink",
44
+ "waybar"
45
+ ],
46
+ "license": "MIT"
47
+ }
package/index.js DELETED
@@ -1 +0,0 @@
1
- // Placeholder