grok-bot-cli 0.2.2 → 0.2.3
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 +1 -1
- package/package.json +1 -1
- package/src/app-session.js +40 -10
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Manage [Grok Bot](https://cursor.com/help/grok-bot/plans) agents, groups, and me
|
|
|
14
14
|
npm install --global grok-bot-cli
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Requires Node.js 18+ and the Grok Bot macOS
|
|
17
|
+
Requires Node.js 18+ and the Grok Bot desktop app on macOS or Linux. Open Grok Bot and sign in once; `gbot` automatically uses the app's encrypted session and routing credentials. No token copying is required. On Linux the app keeps its session under `~/.config/Grok Bot` (or `$XDG_CONFIG_HOME`); when it is stored in the system keyring, `gbot` reads the key with `secret-tool` (package `libsecret-tools`).
|
|
18
18
|
|
|
19
19
|
## Use
|
|
20
20
|
|
package/package.json
CHANGED
package/src/app-session.js
CHANGED
|
@@ -4,7 +4,12 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// Chromium OSCrypt: "v10" = fixed password (macOS: the Keychain password; Linux: "peanuts"),
|
|
8
|
+
// "v11" = Linux Secret Service password. macOS stretches with 1003 PBKDF2 rounds, Linux with 1.
|
|
9
|
+
const SAFE_STORAGE_PREFIX_V10 = "v10";
|
|
10
|
+
const SAFE_STORAGE_PREFIX_V11 = "v11";
|
|
11
|
+
const LINUX_BASIC_TEXT_PASSWORD = "peanuts";
|
|
12
|
+
const SUPPORTED_PLATFORMS = new Set(["darwin", "linux"]);
|
|
8
13
|
|
|
9
14
|
export class GrokBotGatewaySessionError extends Error {
|
|
10
15
|
constructor(code, message) {
|
|
@@ -49,13 +54,21 @@ function encryptedPayload(wrapped) {
|
|
|
49
54
|
return encrypted;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
function safeStorageKey(password, platform) {
|
|
58
|
+
const iterations = platform === "linux" ? 1 : 1003;
|
|
59
|
+
return crypto.pbkdf2Sync(password, "saltysalt", iterations, 16, "sha1");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function decryptSafeStorageString(encryptedBase64, password, platform = "darwin") {
|
|
53
63
|
const encrypted = Buffer.from(encryptedBase64, "base64");
|
|
54
|
-
|
|
64
|
+
const prefix = encrypted.subarray(0, 3).toString("latin1");
|
|
65
|
+
const linuxBasicText = platform === "linux" && prefix === SAFE_STORAGE_PREFIX_V10;
|
|
66
|
+
const keyring = prefix === SAFE_STORAGE_PREFIX_V10 || (platform === "linux" && prefix === SAFE_STORAGE_PREFIX_V11);
|
|
67
|
+
if (!keyring) {
|
|
55
68
|
throw new Error("Unsupported Grok Bot Safe Storage format.");
|
|
56
69
|
}
|
|
57
70
|
|
|
58
|
-
const key =
|
|
71
|
+
const key = safeStorageKey(linuxBasicText ? LINUX_BASIC_TEXT_PASSWORD : password, platform);
|
|
59
72
|
const decipher = crypto.createDecipheriv(
|
|
60
73
|
"aes-128-cbc",
|
|
61
74
|
key,
|
|
@@ -67,7 +80,11 @@ export function decryptSafeStorageString(encryptedBase64, password) {
|
|
|
67
80
|
]).toString("utf8");
|
|
68
81
|
}
|
|
69
82
|
|
|
70
|
-
export function grokBotGatewayDescriptorPath(home = homedir()) {
|
|
83
|
+
export function grokBotGatewayDescriptorPath(home = homedir(), platform = process.platform, env = process.env) {
|
|
84
|
+
if (platform === "linux") {
|
|
85
|
+
const configHome = env.XDG_CONFIG_HOME || join(home, ".config");
|
|
86
|
+
return join(configHome, "Grok Bot/gateway-descriptor.json");
|
|
87
|
+
}
|
|
71
88
|
return join(
|
|
72
89
|
home,
|
|
73
90
|
"Library/Application Support/Grok Bot/gateway-descriptor.json",
|
|
@@ -77,11 +94,19 @@ export function grokBotGatewayDescriptorPath(home = homedir()) {
|
|
|
77
94
|
export function hasGrokBotGatewaySession({
|
|
78
95
|
platform = process.platform,
|
|
79
96
|
home = homedir(),
|
|
97
|
+
env = process.env,
|
|
80
98
|
} = {}) {
|
|
81
|
-
return platform
|
|
99
|
+
return SUPPORTED_PLATFORMS.has(platform) && existsSync(grokBotGatewayDescriptorPath(home, platform, env));
|
|
82
100
|
}
|
|
83
101
|
|
|
84
|
-
function readKeychainPassword() {
|
|
102
|
+
function readKeychainPassword(platform = process.platform) {
|
|
103
|
+
if (platform === "linux") {
|
|
104
|
+
return execFileSync(
|
|
105
|
+
"secret-tool",
|
|
106
|
+
["lookup", "xdg:schema", "chrome_libsecret_os_crypt_password_v2", "application", "Grok Bot"],
|
|
107
|
+
{ encoding: "utf8" },
|
|
108
|
+
).trimEnd();
|
|
109
|
+
}
|
|
85
110
|
return execFileSync(
|
|
86
111
|
"/usr/bin/security",
|
|
87
112
|
["find-generic-password", "-w", "-s", "Grok Bot Safe Storage"],
|
|
@@ -92,18 +117,23 @@ function readKeychainPassword() {
|
|
|
92
117
|
export function loadGrokBotGatewaySession({
|
|
93
118
|
platform = process.platform,
|
|
94
119
|
home = homedir(),
|
|
120
|
+
env = process.env,
|
|
95
121
|
getKeychainPassword = readKeychainPassword,
|
|
96
122
|
} = {}) {
|
|
97
|
-
if (platform
|
|
123
|
+
if (!SUPPORTED_PLATFORMS.has(platform)) return null;
|
|
98
124
|
|
|
99
|
-
const path = grokBotGatewayDescriptorPath(home);
|
|
125
|
+
const path = grokBotGatewayDescriptorPath(home, platform, env);
|
|
100
126
|
if (!existsSync(path)) return null;
|
|
101
127
|
|
|
102
128
|
const wrapped = JSON.parse(readFileSync(path, "utf8"));
|
|
103
129
|
const encrypted = encryptedPayload(wrapped);
|
|
130
|
+
const prefix = Buffer.from(encrypted, "base64").subarray(0, 3).toString("latin1");
|
|
131
|
+
// Linux v10 is the keyring-less basic_text backend; no secret store to ask.
|
|
132
|
+
const needsKeychain = !(platform === "linux" && prefix === SAFE_STORAGE_PREFIX_V10);
|
|
104
133
|
const clear = decryptSafeStorageString(
|
|
105
134
|
encrypted,
|
|
106
|
-
getKeychainPassword(),
|
|
135
|
+
needsKeychain ? getKeychainPassword(platform) : LINUX_BASIC_TEXT_PASSWORD,
|
|
136
|
+
platform,
|
|
107
137
|
);
|
|
108
138
|
const descriptor = JSON.parse(clear);
|
|
109
139
|
if (!descriptor.baseUrl || !descriptor.token) {
|