bitty-tui 0.0.4 → 0.0.5
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/dist/cli.js +0 -0
- package/dist/components/Checkbox.d.ts +9 -0
- package/dist/components/Checkbox.js +12 -0
- package/dist/dashboard/DashboardView.js +10 -3
- package/dist/hooks/bw.d.ts +1 -0
- package/dist/hooks/bw.js +11 -4
- package/dist/login/LoginView.js +10 -6
- package/package.json +1 -1
- package/readme.md +2 -0
package/dist/cli.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Box } from "ink";
|
|
2
|
+
type Props = {
|
|
3
|
+
isActive?: boolean;
|
|
4
|
+
label?: string;
|
|
5
|
+
value: boolean;
|
|
6
|
+
onToggle: (value: boolean) => void;
|
|
7
|
+
} & React.ComponentProps<typeof Box>;
|
|
8
|
+
export declare const Checkbox: ({ isActive, value, label, onToggle, ...props }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text, Box, useFocus, useInput } from "ink";
|
|
3
|
+
import { primary } from "../theme/style.js";
|
|
4
|
+
export const Checkbox = ({ isActive = true, value, label, onToggle, ...props }) => {
|
|
5
|
+
const { isFocused } = useFocus();
|
|
6
|
+
useInput((input, key) => {
|
|
7
|
+
if (input === " ") {
|
|
8
|
+
onToggle(!value);
|
|
9
|
+
}
|
|
10
|
+
}, { isActive: isFocused && isActive });
|
|
11
|
+
return (_jsxs(Box, { ...props, children: [_jsx(Box, { width: 5, height: 3, flexShrink: 0, borderStyle: "round", borderColor: isFocused && isActive ? primary : "gray", children: value && (_jsx(Box, { width: 1, height: 1, marginLeft: 1, children: _jsx(Text, { color: isFocused && isActive ? primary : "gray", children: "X" }) })) }), _jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { children: label }) })] }));
|
|
12
|
+
};
|
|
@@ -9,7 +9,7 @@ import { primary } from "../theme/style.js";
|
|
|
9
9
|
import { bwClient, clearConfig, emptyCipher, useBwSync } from "../hooks/bw.js";
|
|
10
10
|
import { useStatusMessage } from "../hooks/status-message.js";
|
|
11
11
|
export function DashboardView({ onLogout }) {
|
|
12
|
-
const { sync, fetchSync } = useBwSync();
|
|
12
|
+
const { sync, error, fetchSync } = useBwSync();
|
|
13
13
|
const [syncState, setSyncState] = useState(sync);
|
|
14
14
|
const [searchQuery, setSearchQuery] = useState("");
|
|
15
15
|
const [listIndex, setListIndex] = useState(0);
|
|
@@ -35,6 +35,10 @@ export function DashboardView({ onLogout }) {
|
|
|
35
35
|
}) ?? []).sort((a, b) => a.name.localeCompare(b.name));
|
|
36
36
|
}, [syncState, searchQuery]);
|
|
37
37
|
const selectedCipher = detailMode === "new" ? editedCipher : filteredCiphers[listIndex];
|
|
38
|
+
const logout = async () => {
|
|
39
|
+
await clearConfig();
|
|
40
|
+
onLogout();
|
|
41
|
+
};
|
|
38
42
|
useEffect(() => {
|
|
39
43
|
setListIndex(0);
|
|
40
44
|
}, [searchQuery]);
|
|
@@ -45,10 +49,13 @@ export function DashboardView({ onLogout }) {
|
|
|
45
49
|
if (focusedComponent === "detail")
|
|
46
50
|
focusNext();
|
|
47
51
|
}, [focusedComponent]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (error)
|
|
54
|
+
showStatusMessage(error, "error");
|
|
55
|
+
}, [error]);
|
|
48
56
|
useInput(async (input, key) => {
|
|
49
57
|
if (key.ctrl && input === "w") {
|
|
50
|
-
await
|
|
51
|
-
onLogout();
|
|
58
|
+
await logout();
|
|
52
59
|
return;
|
|
53
60
|
}
|
|
54
61
|
if (input === "/" && focusedComponent !== "search") {
|
package/dist/hooks/bw.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare function saveConfig(config: BwConfig): Promise<void>;
|
|
|
10
10
|
export declare function clearConfig(): Promise<void>;
|
|
11
11
|
export declare const useBwSync: () => {
|
|
12
12
|
sync: SyncResponse | null;
|
|
13
|
+
error: string | null;
|
|
13
14
|
fetchSync: (forceRefresh?: boolean) => Promise<void>;
|
|
14
15
|
};
|
|
15
16
|
export declare const emptyCipher: any;
|
package/dist/hooks/bw.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from "path";
|
|
|
4
4
|
import { CipherType, Client } from "../clients/bw.js";
|
|
5
5
|
import { useCallback, useEffect, useState } from "react";
|
|
6
6
|
export const bwClient = new Client();
|
|
7
|
-
const configPath = path.join(os.homedir(), ".config", "
|
|
7
|
+
const configPath = path.join(os.homedir(), ".config", "bitty", "config.json");
|
|
8
8
|
export async function loadConfig() {
|
|
9
9
|
try {
|
|
10
10
|
if (fs.existsSync(configPath)) {
|
|
@@ -83,14 +83,21 @@ export async function clearConfig() {
|
|
|
83
83
|
}
|
|
84
84
|
export const useBwSync = () => {
|
|
85
85
|
const [sync, setSync] = useState(null);
|
|
86
|
+
const [error, setError] = useState(null);
|
|
86
87
|
const fetchSync = useCallback(async (forceRefresh = true) => {
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
try {
|
|
89
|
+
setError(null);
|
|
90
|
+
const sync = await bwClient.getDecryptedSync({ forceRefresh });
|
|
91
|
+
setSync(sync);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
setError("Error fetching sync data");
|
|
95
|
+
}
|
|
89
96
|
}, []);
|
|
90
97
|
useEffect(() => {
|
|
91
98
|
fetchSync();
|
|
92
99
|
}, [fetchSync]);
|
|
93
|
-
return { sync, fetchSync };
|
|
100
|
+
return { sync, error, fetchSync };
|
|
94
101
|
};
|
|
95
102
|
export const emptyCipher = {
|
|
96
103
|
name: "",
|
package/dist/login/LoginView.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Button } from "../components/Button.js";
|
|
|
6
6
|
import { primary } from "../theme/style.js";
|
|
7
7
|
import { bwClient, loadConfig, saveConfig } from "../hooks/bw.js";
|
|
8
8
|
import { useStatusMessage } from "../hooks/status-message.js";
|
|
9
|
+
import { Checkbox } from "../components/Checkbox.js";
|
|
9
10
|
const art = `
|
|
10
11
|
███████████ ███ ███████████ ███████████ █████ █████
|
|
11
12
|
░░███░░░░░███ ░░░ ░█░░░███░░░█░█░░░███░░░█░░███ ░░███
|
|
@@ -21,6 +22,7 @@ export function LoginView({ onLogin }) {
|
|
|
21
22
|
const [url, setUrl] = useState("https://vault.bitwarden.eu");
|
|
22
23
|
const [email, setEmail] = useState("");
|
|
23
24
|
const [password, setPassword] = useState("");
|
|
25
|
+
const [rememberMe, setRememberMe] = useState(false);
|
|
24
26
|
const { stdout } = useStdout();
|
|
25
27
|
const { focusNext } = useFocusManager();
|
|
26
28
|
const { statusMessage, statusMessageColor, showStatusMessage } = useStatusMessage();
|
|
@@ -38,11 +40,13 @@ export function LoginView({ onLogin }) {
|
|
|
38
40
|
if (!bwClient.refreshToken || !bwClient.keys)
|
|
39
41
|
throw new Error("Missing URL or keys after login");
|
|
40
42
|
onLogin();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (rememberMe) {
|
|
44
|
+
saveConfig({
|
|
45
|
+
baseUrl: url?.trim().length ? url.trim() : undefined,
|
|
46
|
+
keys: bwClient.keys,
|
|
47
|
+
refreshToken: bwClient.refreshToken,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
46
50
|
}
|
|
47
51
|
catch (e) {
|
|
48
52
|
showStatusMessage("Login failed, please check your credentials.", "error");
|
|
@@ -71,5 +75,5 @@ export function LoginView({ onLogin }) {
|
|
|
71
75
|
else {
|
|
72
76
|
focusNext();
|
|
73
77
|
}
|
|
74
|
-
}, isPassword: true }), _jsx(Button, { onClick: handleLogin, children: "Log In" }), statusMessage && (_jsx(Box, { marginTop: 1, width: "100%", justifyContent: "center", children: _jsx(Text, { color: statusMessageColor, children: statusMessage }) }))] }))] }));
|
|
78
|
+
}, isPassword: true }), _jsxs(Box, { children: [_jsx(Checkbox, { label: "Remember me (less secure)", value: rememberMe, width: "50%", onToggle: setRememberMe }), _jsx(Button, { width: "50%", onClick: handleLogin, children: "Log In" })] }), statusMessage && (_jsx(Box, { marginTop: 1, width: "100%", justifyContent: "center", children: _jsx(Text, { color: statusMessageColor, children: statusMessage }) }))] }))] }));
|
|
75
79
|
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -15,6 +15,8 @@ Bitwarden compatible TUI for your terminal.
|
|
|
15
15
|
|
|
16
16
|
Works also with Vaultwarden.
|
|
17
17
|
|
|
18
|
+
If you check "Remember me" during login, your vault encryption keys will be stored in plain text in your home folder (`$HOME/.config/bitty/config.json`). Use this option only if you are the only user of your machine.
|
|
19
|
+
|
|
18
20
|
## Acknowledgments
|
|
19
21
|
|
|
20
22
|
- [Bitwarden](https://github.com/bitwarden)
|