bitty-tui 0.0.21 → 0.0.22
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 +2 -0
- package/dist/clients/bw.js +63 -14
- package/dist/components/TextInput.js +1 -1
- package/dist/dashboard/DashboardView.js +1 -1
- package/dist/dashboard/components/VaultList.js +3 -2
- package/dist/debug.d.ts +3 -0
- package/dist/debug.js +15 -0
- package/package.json +9 -9
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { readPackageUpSync } from "read-package-up";
|
|
|
8
8
|
import { art } from "./theme/art.js";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { debugLogPath } from "./debug.js";
|
|
11
12
|
const args = process.argv.slice(2);
|
|
12
13
|
if (args.includes("--version") || args.includes("-v")) {
|
|
13
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -24,6 +25,7 @@ if (args.includes("--help") || args.includes("-h")) {
|
|
|
24
25
|
Options
|
|
25
26
|
--help Show help
|
|
26
27
|
--version Show version
|
|
28
|
+
--debug Write API debug logs to ${debugLogPath}
|
|
27
29
|
`);
|
|
28
30
|
process.exit(0);
|
|
29
31
|
}
|
package/dist/clients/bw.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import crypto from "node:crypto";
|
|
21
21
|
import * as argon2 from "argon2";
|
|
22
|
+
import { debugEnabled, debugLog } from "../debug.js";
|
|
22
23
|
export class FetchError extends Error {
|
|
23
24
|
status;
|
|
24
25
|
data;
|
|
@@ -32,7 +33,26 @@ export class FetchError extends Error {
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
const fetchApi = async (...args) => {
|
|
36
|
+
if (debugEnabled) {
|
|
37
|
+
const [input, init] = args;
|
|
38
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
39
|
+
const url = input instanceof Request ? input.url : String(input);
|
|
40
|
+
const body = init?.body != null ? String(init.body) : "(none)";
|
|
41
|
+
const headers = new Headers(init?.headers);
|
|
42
|
+
const headersStr = [...headers.entries()]
|
|
43
|
+
.map(([k, v]) => ` ${k}: ${v}`)
|
|
44
|
+
.join("\n");
|
|
45
|
+
debugLog(`→ ${method} ${url}`);
|
|
46
|
+
debugLog(` headers:\n${headersStr || " (none)"}`);
|
|
47
|
+
debugLog(` body: ${body}`);
|
|
48
|
+
}
|
|
35
49
|
const response = await fetch(...args);
|
|
50
|
+
if (debugEnabled) {
|
|
51
|
+
const clone = response.clone();
|
|
52
|
+
const responseBody = await clone.text();
|
|
53
|
+
debugLog(`← ${response.status} ${response.statusText}`);
|
|
54
|
+
debugLog(` response: ${responseBody}`);
|
|
55
|
+
}
|
|
36
56
|
if (!response.ok) {
|
|
37
57
|
const data = await response.text();
|
|
38
58
|
throw new FetchError(response.status, data);
|
|
@@ -73,6 +93,17 @@ export var KdfType;
|
|
|
73
93
|
KdfType[KdfType["Argon2id"] = 1] = "Argon2id";
|
|
74
94
|
})(KdfType || (KdfType = {}));
|
|
75
95
|
const DEVICE_IDENTIFIER = "928f9664-5559-4a7b-9853-caf5bfa5dd57";
|
|
96
|
+
const DEVICE_TYPE = "9"; // ChromeBrowser → ClientName::Web in the SDK
|
|
97
|
+
const CLIENT_NAME = "web";
|
|
98
|
+
const CLIENT_VERSION = "2025.9.0";
|
|
99
|
+
const USER_AGENT = "Bitwarden_Bitty";
|
|
100
|
+
const defaultHeaders = () => ({
|
|
101
|
+
"Device-Identifier": DEVICE_IDENTIFIER,
|
|
102
|
+
"Bitwarden-Client-Name": CLIENT_NAME,
|
|
103
|
+
"Bitwarden-Client-Version": CLIENT_VERSION,
|
|
104
|
+
"Device-Type": DEVICE_TYPE,
|
|
105
|
+
"User-Agent": USER_AGENT,
|
|
106
|
+
});
|
|
76
107
|
class Bw {
|
|
77
108
|
/**
|
|
78
109
|
* Derives the master key and related keys from the user's email and password.
|
|
@@ -373,6 +404,8 @@ export class Client {
|
|
|
373
404
|
const prelogin = await fetchApi(`${this.identityUrl}/accounts/prelogin`, {
|
|
374
405
|
method: "POST",
|
|
375
406
|
headers: {
|
|
407
|
+
...defaultHeaders(),
|
|
408
|
+
Accept: "application/json",
|
|
376
409
|
"Content-Type": "application/json",
|
|
377
410
|
},
|
|
378
411
|
body: JSON.stringify({ email }),
|
|
@@ -381,24 +414,24 @@ export class Client {
|
|
|
381
414
|
this.keys = keys;
|
|
382
415
|
}
|
|
383
416
|
const bodyParams = new URLSearchParams();
|
|
384
|
-
bodyParams.append("
|
|
385
|
-
bodyParams.append("
|
|
386
|
-
bodyParams.append("grant_type", "password");
|
|
387
|
-
bodyParams.append("deviceName", "chrome");
|
|
417
|
+
bodyParams.append("scope", "api offline_access");
|
|
418
|
+
bodyParams.append("client_id", "web");
|
|
388
419
|
bodyParams.append("deviceType", "9");
|
|
389
420
|
bodyParams.append("deviceIdentifier", DEVICE_IDENTIFIER);
|
|
390
|
-
bodyParams.append("
|
|
391
|
-
bodyParams.append("
|
|
421
|
+
bodyParams.append("deviceName", "firefox");
|
|
422
|
+
bodyParams.append("grant_type", "password");
|
|
423
|
+
bodyParams.append("username", email);
|
|
424
|
+
bodyParams.append("password", keys.masterPasswordHash);
|
|
392
425
|
for (const [key, value] of Object.entries(opts || {})) {
|
|
393
426
|
bodyParams.append(key, value);
|
|
394
427
|
}
|
|
395
428
|
const identityReq = await fetchApi(`${this.identityUrl}/connect/token`, {
|
|
396
429
|
method: "POST",
|
|
397
430
|
headers: {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
"
|
|
401
|
-
|
|
431
|
+
...defaultHeaders(),
|
|
432
|
+
Accept: "application/json",
|
|
433
|
+
"Cache-Control": "no-store",
|
|
434
|
+
Pragma: "no-cache",
|
|
402
435
|
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
403
436
|
},
|
|
404
437
|
body: bodyParams.toString(),
|
|
@@ -421,6 +454,8 @@ export class Client {
|
|
|
421
454
|
return fetchApi(`${this.apiUrl}/two-factor/send-email-login`, {
|
|
422
455
|
method: "POST",
|
|
423
456
|
headers: {
|
|
457
|
+
...defaultHeaders(),
|
|
458
|
+
Accept: "application/json",
|
|
424
459
|
"Content-Type": "application/json",
|
|
425
460
|
},
|
|
426
461
|
body: JSON.stringify({
|
|
@@ -440,14 +475,17 @@ export class Client {
|
|
|
440
475
|
throw new Error("No refresh token available. Please login first.");
|
|
441
476
|
}
|
|
442
477
|
const bodyParams = new URLSearchParams();
|
|
443
|
-
bodyParams.append("refresh_token", this.refreshToken);
|
|
444
478
|
bodyParams.append("grant_type", "refresh_token");
|
|
479
|
+
bodyParams.append("refresh_token", this.refreshToken);
|
|
445
480
|
bodyParams.append("client_id", "web");
|
|
446
|
-
bodyParams.append("scope", "api offline_access");
|
|
447
481
|
const identityReq = await fetchApi(`${this.identityUrl}/connect/token`, {
|
|
448
482
|
method: "POST",
|
|
449
483
|
headers: {
|
|
450
|
-
|
|
484
|
+
...defaultHeaders(),
|
|
485
|
+
Accept: "application/json",
|
|
486
|
+
"Cache-Control": "no-store",
|
|
487
|
+
Pragma: "no-cache",
|
|
488
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
451
489
|
},
|
|
452
490
|
body: bodyParams.toString(),
|
|
453
491
|
}).then((r) => r.json());
|
|
@@ -466,8 +504,9 @@ export class Client {
|
|
|
466
504
|
this.syncCache = await fetchApi(`${this.apiUrl}/sync?excludeDomains=true`, {
|
|
467
505
|
method: "GET",
|
|
468
506
|
headers: {
|
|
507
|
+
...defaultHeaders(),
|
|
508
|
+
Accept: "application/json",
|
|
469
509
|
Authorization: `Bearer ${this.token}`,
|
|
470
|
-
"bitwarden-client-version": "2025.9.0",
|
|
471
510
|
},
|
|
472
511
|
}).then((r) => r.json());
|
|
473
512
|
this.decryptOrgKeys();
|
|
@@ -638,6 +677,8 @@ export class Client {
|
|
|
638
677
|
const s = await fetchApi(url, {
|
|
639
678
|
method: "POST",
|
|
640
679
|
headers: {
|
|
680
|
+
// ...defaultHeaders(),
|
|
681
|
+
// Accept: "application/json",
|
|
641
682
|
Authorization: `Bearer ${this.token}`,
|
|
642
683
|
"Content-Type": "application/json",
|
|
643
684
|
},
|
|
@@ -685,6 +726,8 @@ export class Client {
|
|
|
685
726
|
const s = await fetchApi(`${this.apiUrl}/ciphers/${id}`, {
|
|
686
727
|
method: "PUT",
|
|
687
728
|
headers: {
|
|
729
|
+
// ...defaultHeaders(),
|
|
730
|
+
// Accept: "application/json",
|
|
688
731
|
Authorization: `Bearer ${this.token}`,
|
|
689
732
|
"Content-Type": "application/json",
|
|
690
733
|
},
|
|
@@ -699,6 +742,8 @@ export class Client {
|
|
|
699
742
|
await fetchApi(`${this.apiUrl}/ciphers/${id}`, {
|
|
700
743
|
method: "DELETE",
|
|
701
744
|
headers: {
|
|
745
|
+
// ...defaultHeaders(),
|
|
746
|
+
// Accept: "application/json",
|
|
702
747
|
Authorization: `Bearer ${this.token}`,
|
|
703
748
|
},
|
|
704
749
|
});
|
|
@@ -722,6 +767,8 @@ export class Client {
|
|
|
722
767
|
const s = await fetchApi(`${this.apiUrl}/ciphers/${id}/share`, {
|
|
723
768
|
method: "PUT",
|
|
724
769
|
headers: {
|
|
770
|
+
// ...defaultHeaders(),
|
|
771
|
+
// Accept: "application/json",
|
|
725
772
|
Authorization: `Bearer ${this.token}`,
|
|
726
773
|
"Content-Type": "application/json",
|
|
727
774
|
},
|
|
@@ -739,6 +786,8 @@ export class Client {
|
|
|
739
786
|
const s = await fetchApi(`${this.apiUrl}/ciphers/${id}/collections_v2`, {
|
|
740
787
|
method: "PUT",
|
|
741
788
|
headers: {
|
|
789
|
+
// ...defaultHeaders(),
|
|
790
|
+
// Accept: "application/json",
|
|
742
791
|
Authorization: `Bearer ${this.token}`,
|
|
743
792
|
"Content-Type": "application/json",
|
|
744
793
|
},
|
|
@@ -138,7 +138,7 @@ export function DashboardView({ onLogout }) {
|
|
|
138
138
|
setShowDetails(true);
|
|
139
139
|
setTimeout(focusNext, 50);
|
|
140
140
|
}, [showDetails]);
|
|
141
|
-
return (_jsxs(Box, { flexDirection: "column", width: "100%", height: stdout.rows - 2, children: [_jsx(Box, { borderStyle: "double", borderColor: primary, paddingX: 2, justifyContent: "center", flexShrink: 0, children: _jsx(Text, { bold: true, color: primary, children: "BiTTY" }) }), _jsxs(Box, { children: [_jsx(Box, { width: "40%", children: _jsx(TextInput, { id: "search", placeholder: focusedComponent === "search" ? "" : "[/] Search in vault", value: searchQuery, isActive: false, onChange: setSearchQuery, onSubmit: () => {
|
|
141
|
+
return (_jsxs(Box, { flexDirection: "column", width: "100%", height: stdout.rows - 2, children: [_jsx(Box, { borderStyle: "double", borderColor: primary, paddingX: 2, justifyContent: "center", flexShrink: 0, children: _jsx(Text, { bold: true, color: primary, children: "BiTTY" }) }), _jsxs(Box, { flexShrink: 0, children: [_jsx(Box, { width: "40%", children: _jsx(TextInput, { id: "search", placeholder: focusedComponent === "search" ? "" : "[/] Search in vault", value: searchQuery, isActive: false, onChange: setSearchQuery, onSubmit: () => {
|
|
142
142
|
setFocusedComponent("list");
|
|
143
143
|
focusNext();
|
|
144
144
|
} }) }), _jsxs(Box, { width: "60%", paddingX: 1, justifyContent: "space-between", children: [statusMessage ? (_jsx(Box, { padding: 1, flexShrink: 1, children: _jsx(Text, { color: statusMessageColor, children: statusMessage }) })) : (_jsx(Box, {})), selectedCipher && (_jsxs(Box, { gap: 1, flexShrink: 0, children: [_jsx(TabButton, { active: false, onClick: async () => {
|
|
@@ -38,7 +38,8 @@ export function VaultList({ filteredCiphers, isFocused, selected, onSelect, }) {
|
|
|
38
38
|
});
|
|
39
39
|
useInput((input, key) => {
|
|
40
40
|
const cipher = selected !== null ? filteredCiphers[selected] : null;
|
|
41
|
-
let field
|
|
41
|
+
let field;
|
|
42
|
+
let fldName;
|
|
42
43
|
if (key.ctrl && input === "y") {
|
|
43
44
|
switch (cipher?.type) {
|
|
44
45
|
case CipherType.Login:
|
|
@@ -74,7 +75,7 @@ export function VaultList({ filteredCiphers, isFocused, selected, onSelect, }) {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
if (field) {
|
|
77
|
-
clipboard.
|
|
78
|
+
clipboard.write(field);
|
|
78
79
|
showStatusMessage(`📋 Copied ${fldName} to clipboard!`, "success");
|
|
79
80
|
}
|
|
80
81
|
}, { isActive: isFocused });
|
package/dist/debug.d.ts
ADDED
package/dist/debug.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
export const debugEnabled = process.argv.includes("--debug");
|
|
5
|
+
const logDir = path.join(os.homedir(), ".config", "bitty");
|
|
6
|
+
export const debugLogPath = path.join(logDir, "debug.log");
|
|
7
|
+
if (debugEnabled) {
|
|
8
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
9
|
+
fs.writeFileSync(debugLogPath, `--- debug session started ${new Date().toISOString()} ---\n`);
|
|
10
|
+
}
|
|
11
|
+
export function debugLog(message) {
|
|
12
|
+
if (!debugEnabled)
|
|
13
|
+
return;
|
|
14
|
+
fs.appendFileSync(debugLogPath, `[${new Date().toISOString()}] ${message}\n`);
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bitty-tui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "https://github.com/mceck/bitty",
|
|
6
6
|
"keywords": [
|
|
@@ -32,18 +32,18 @@
|
|
|
32
32
|
"argon2": "^0.44.0",
|
|
33
33
|
"chalk": "^5.6.2",
|
|
34
34
|
"clipboardy": "^5.3.1",
|
|
35
|
-
"ink": "^6.
|
|
36
|
-
"otplib": "^13.
|
|
37
|
-
"react": "^19.
|
|
35
|
+
"ink": "^6.8.0",
|
|
36
|
+
"otplib": "^13.4.0",
|
|
37
|
+
"react": "^19.2.5",
|
|
38
38
|
"read-package-up": "^12.0.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@sindresorhus/tsconfig": "^8.0
|
|
42
|
-
"@types/node": "^24.
|
|
43
|
-
"@types/react": "^19.
|
|
44
|
-
"eslint-plugin-react": "^7.
|
|
41
|
+
"@sindresorhus/tsconfig": "^8.1.0",
|
|
42
|
+
"@types/node": "^24.12.2",
|
|
43
|
+
"@types/react": "^19.2.14",
|
|
44
|
+
"eslint-plugin-react": "^7.37.5",
|
|
45
45
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
46
46
|
"ink-testing-library": "^4.0.0",
|
|
47
|
-
"typescript": "^5.
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
48
|
}
|
|
49
49
|
}
|