@hicoders/devkit 1.0.0

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.
@@ -0,0 +1,30 @@
1
+ // Theme context — provides the active Theme to every screen and a `cycle()` to
2
+ // switch it. The choice is loaded from and saved to ~/.devkit.json, so it sticks
3
+ // across runs and across tools. Components read colors via useTheme(); the
4
+ // shared ListSelect calls useThemeControls().cycle() on the `t` key.
5
+
6
+ import { createContext, useCallback, useContext, useState, type ReactNode } from "react";
7
+ import { defaultTheme, nextTheme, themeByName, type Theme } from "./theme";
8
+ import { loadConfig, saveConfig } from "../core/config";
9
+
10
+ interface ThemeControls {
11
+ theme: Theme;
12
+ cycle: () => void;
13
+ }
14
+
15
+ const ThemeCtx = createContext<ThemeControls>({ theme: defaultTheme, cycle: () => {} });
16
+
17
+ export const useTheme = (): Theme => useContext(ThemeCtx).theme;
18
+ export const useThemeControls = (): ThemeControls => useContext(ThemeCtx);
19
+
20
+ export function ThemeProvider({ children }: { children: ReactNode }) {
21
+ const [theme, setTheme] = useState<Theme>(() => themeByName(loadConfig().theme));
22
+ const cycle = useCallback(() => {
23
+ setTheme((cur) => {
24
+ const next = nextTheme(cur.name);
25
+ saveConfig({ theme: next.name });
26
+ return next;
27
+ });
28
+ }, []);
29
+ return <ThemeCtx.Provider value={{ theme, cycle }}>{children}</ThemeCtx.Provider>;
30
+ }
@@ -0,0 +1,97 @@
1
+ // Shared visual tokens for every devkit TUI screen, so the suite looks uniform.
2
+ // Multiple named presets; the active one is chosen via the theme context and
3
+ // cycled with `t` (persisted in ~/.devkit.json). Add a preset by appending to
4
+ // THEMES — every screen picks it up automatically.
5
+
6
+ export interface Theme {
7
+ name: string;
8
+ accent: string; // primary brand / highlight color
9
+ accentDim: string; // section headers, secondary accent
10
+ fg: string; // normal text
11
+ dim: string; // muted/secondary text
12
+ muted: string; // between fg and dim
13
+ green: string; // success
14
+ red: string; // danger
15
+ yellow: string; // warning / confirm
16
+ selBg: string; // highlighted-row background
17
+ selFg: string; // highlighted-row text
18
+ }
19
+
20
+ export const THEMES: Theme[] = [
21
+ {
22
+ name: "midnight",
23
+ accent: "#7C9CF4",
24
+ accentDim: "#5566AA",
25
+ fg: "#E6E6E6",
26
+ dim: "#7A7A7A",
27
+ muted: "#9AA0AA",
28
+ green: "#5BD75B",
29
+ red: "#FF6B6B",
30
+ yellow: "#E5C07B",
31
+ selBg: "#293356",
32
+ selFg: "#FFFFFF",
33
+ },
34
+ {
35
+ name: "matrix",
36
+ accent: "#4AE24A",
37
+ accentDim: "#2E8B2E",
38
+ fg: "#C8F7C8",
39
+ dim: "#5A7A5A",
40
+ muted: "#79A079",
41
+ green: "#5BD75B",
42
+ red: "#FF6B6B",
43
+ yellow: "#D7D75B",
44
+ selBg: "#143314",
45
+ selFg: "#EAFFEA",
46
+ },
47
+ {
48
+ name: "amber",
49
+ accent: "#E5A94B",
50
+ accentDim: "#A0762E",
51
+ fg: "#F0E6D6",
52
+ dim: "#8A7A5A",
53
+ muted: "#B0A080",
54
+ green: "#9BD75B",
55
+ red: "#FF7B6B",
56
+ yellow: "#E5C07B",
57
+ selBg: "#3A2E14",
58
+ selFg: "#FFF6E6",
59
+ },
60
+ {
61
+ name: "rose",
62
+ accent: "#FF6B9D",
63
+ accentDim: "#AA4470",
64
+ fg: "#F5E0EA",
65
+ dim: "#8A6A7A",
66
+ muted: "#B080A0",
67
+ green: "#5BD75B",
68
+ red: "#FF6B6B",
69
+ yellow: "#E5C07B",
70
+ selBg: "#3A1428",
71
+ selFg: "#FFF0F6",
72
+ },
73
+ {
74
+ name: "mono",
75
+ accent: "#FFFFFF",
76
+ accentDim: "#888888",
77
+ fg: "#DDDDDD",
78
+ dim: "#777777",
79
+ muted: "#999999",
80
+ green: "#BBBBBB",
81
+ red: "#FF6B6B",
82
+ yellow: "#CCCCCC",
83
+ selBg: "#333333",
84
+ selFg: "#FFFFFF",
85
+ },
86
+ ];
87
+
88
+ export const defaultTheme = THEMES[0]!;
89
+
90
+ export function themeByName(name: string | undefined): Theme {
91
+ return THEMES.find((t) => t.name === name) ?? defaultTheme;
92
+ }
93
+
94
+ export function nextTheme(current: string): Theme {
95
+ const i = THEMES.findIndex((t) => t.name === current);
96
+ return THEMES[(i + 1) % THEMES.length]!;
97
+ }
@@ -0,0 +1,27 @@
1
+ // Registry of tools shown in the `devkit` hub. Each entry points at a screen
2
+ // file in this folder that the hub spawns as a child process.
3
+ //
4
+ // Adding a tool: build pkg/core/<tool>.ts + pkg/tui/<tool>.tsx, then add a row
5
+ // here and a bin/<tool>.bat shim.
6
+
7
+ export interface ToolDef {
8
+ id: string;
9
+ label: string;
10
+ description: string;
11
+ file: string; // filename within pkg/tui (spawned via `bun`)
12
+ }
13
+
14
+ export const TOOLS: ToolDef[] = [
15
+ {
16
+ id: "killport",
17
+ label: "killport",
18
+ description: "Find and kill processes by port",
19
+ file: "killport.tsx",
20
+ },
21
+ {
22
+ id: "launch",
23
+ label: "launch",
24
+ description: "Start your dev projects",
25
+ file: "launch.tsx",
26
+ },
27
+ ];
@@ -0,0 +1,70 @@
1
+ // winmouse — work around Bun #25663 on Windows.
2
+ //
3
+ // On Windows, `process.stdin.setRawMode(true)` OVERWRITES the console input mode
4
+ // with just ENABLE_VIRTUAL_TERMINAL_INPUT (0x200) instead of preserving the
5
+ // existing flags. That wipes ENABLE_MOUSE_INPUT, so OpenTUI (which enables mouse
6
+ // and then calls setRawMode) never receives any mouse events — and it also wipes
7
+ // ENABLE_QUICK_EDIT_MODE, which is why terminal text selection stops working.
8
+ //
9
+ // Fix: monkey-patch setRawMode so that after Bun flips raw mode on, we re-apply
10
+ // the mouse flags via the Win32 console API. Doing it in the patch (not once)
11
+ // means it survives every later raw-mode toggle by OpenTUI. No-op off Windows.
12
+ //
13
+ // Ref: https://github.com/oven-sh/bun/issues/25663
14
+
15
+ import { dlopen, FFIType, ptr } from "bun:ffi";
16
+
17
+ const STD_INPUT_HANDLE = -10;
18
+ const ENABLE_MOUSE_INPUT = 0x0010;
19
+ const ENABLE_QUICK_EDIT_MODE = 0x0040;
20
+ const ENABLE_EXTENDED_FLAGS = 0x0080;
21
+ const ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;
22
+
23
+ let installed = false;
24
+
25
+ export function enableWindowsMouse(): void {
26
+ if (installed || process.platform !== "win32") return;
27
+ installed = true;
28
+
29
+ let k32;
30
+ try {
31
+ k32 = dlopen("kernel32.dll", {
32
+ GetStdHandle: { args: [FFIType.i32], returns: FFIType.ptr },
33
+ GetConsoleMode: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
34
+ SetConsoleMode: { args: [FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
35
+ });
36
+ } catch {
37
+ return; // FFI unavailable — silently skip (keyboard still works)
38
+ }
39
+
40
+ const hIn = k32.symbols.GetStdHandle(STD_INPUT_HANDLE);
41
+
42
+ // Add the mouse flags back to whatever mode is currently set, keeping VT input
43
+ // and ensuring QuickEdit stays off (so mouse goes to the app, not selection).
44
+ const applyMouseFlags = () => {
45
+ try {
46
+ const buf = new Uint32Array(1);
47
+ if (!k32.symbols.GetConsoleMode(hIn, ptr(buf))) return;
48
+ const mode =
49
+ ((buf[0]! | ENABLE_EXTENDED_FLAGS | ENABLE_MOUSE_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT) &
50
+ ~ENABLE_QUICK_EDIT_MODE) >>>
51
+ 0;
52
+ k32.symbols.SetConsoleMode(hIn, mode);
53
+ } catch {
54
+ /* ignore — non-console stdin, etc. */
55
+ }
56
+ };
57
+
58
+ const stdin = process.stdin as NodeJS.ReadStream & {
59
+ setRawMode?: (mode: boolean) => unknown;
60
+ };
61
+ const original = stdin.setRawMode?.bind(stdin);
62
+ if (original) {
63
+ stdin.setRawMode = (mode: boolean) => {
64
+ const result = original(mode);
65
+ if (mode) applyMouseFlags(); // re-add the flags Bun's setRawMode just wiped
66
+ return result;
67
+ };
68
+ }
69
+ applyMouseFlags(); // in case raw mode is already on
70
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "types": ["bun"],
8
+ "allowJs": true,
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "noEmit": true,
12
+ "esModuleInterop": true,
13
+ "verbatimModuleSyntax": true,
14
+ "jsx": "react-jsx",
15
+ "jsxImportSource": "@opentui/react"
16
+ },
17
+ "include": ["pkg/**/*.ts", "pkg/**/*.tsx"]
18
+ }