@xai-official/grok 0.2.110 → 0.2.112
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/bin/grok +75 -62
- package/bin/postinstall.js +54 -39
- package/package.json +7 -7
package/bin/grok
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Thin trampoline: resolves the grok binary
|
|
3
|
-
// optional dependency package and execs it.
|
|
2
|
+
// Thin trampoline: resolves the grok binary and execs it.
|
|
4
3
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// Resolution order:
|
|
5
|
+
// 1. $GROK_HOME/bin/grok — canonical versioned symlink (installed by postinstall.js)
|
|
6
|
+
// 2. bootstrap it from the per-platform @xai-official/grok-<platform> package,
|
|
7
|
+
// decompressing the brotli payload straight into $GROK_HOME/bin
|
|
8
|
+
// 3. last resort (no resolvable version or an unwritable home): decompress the
|
|
9
|
+
// payload in place under node_modules and exec that
|
|
7
10
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// 2. @xai-official/grok-<platform>/bin/grok[.exe] — decompressed sibling
|
|
11
|
-
// 3. @xai-official/grok-<platform>/bin/grok[.exe].br — brotli-compressed
|
|
12
|
-
//
|
|
13
|
-
// Per-platform binaries are shipped brotli-compressed to stay well under
|
|
14
|
-
// npm's ~200 MB tarball ceiling. See sibling packages @xai-official/grok-*.
|
|
11
|
+
// Per-platform binaries ship brotli-compressed to stay under npm's ~200 MB
|
|
12
|
+
// tarball ceiling. See sibling packages @xai-official/grok-*.
|
|
15
13
|
const { spawn } = require('child_process');
|
|
16
14
|
const path = require('path');
|
|
17
15
|
const fs = require('fs');
|
|
@@ -22,7 +20,14 @@ const pkgName = '@xai-official/grok';
|
|
|
22
20
|
const IS_WINDOWS = process.platform === 'win32';
|
|
23
21
|
const EXE = IS_WINDOWS ? '.exe' : '';
|
|
24
22
|
const BIN_NAME = `grok${EXE}`;
|
|
25
|
-
|
|
23
|
+
// $GROK_HOME/bin (else ~/.grok/bin), matching the Rust grok_home(), including
|
|
24
|
+
// its canonicalized-home default (so a symlinked $HOME resolves the same way).
|
|
25
|
+
function defaultGrokHome() {
|
|
26
|
+
const home = os.homedir();
|
|
27
|
+
try { return path.join(fs.realpathSync(home), '.grok'); } catch { return path.join(home, '.grok'); }
|
|
28
|
+
}
|
|
29
|
+
const GROK_HOME = process.env.GROK_HOME ?? defaultGrokHome();
|
|
30
|
+
const CANONICAL_DIR = path.join(GROK_HOME, 'bin');
|
|
26
31
|
const CANONICAL_PATH = path.join(CANONICAL_DIR, BIN_NAME);
|
|
27
32
|
|
|
28
33
|
function readLocalVersion() {
|
|
@@ -41,53 +46,63 @@ function resolvePlatformPackageDir() {
|
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
function writeVendorBinary(brPath, rawPath, destPath) {
|
|
50
|
+
const tmp = destPath + `.tmp.${process.pid}`;
|
|
51
|
+
try {
|
|
52
|
+
if (fs.existsSync(brPath)) {
|
|
53
|
+
fs.writeFileSync(tmp, zlib.brotliDecompressSync(fs.readFileSync(brPath)));
|
|
54
|
+
} else if (fs.existsSync(rawPath)) {
|
|
55
|
+
fs.copyFileSync(rawPath, tmp);
|
|
56
|
+
} else {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (!IS_WINDOWS) fs.chmodSync(tmp, 0o755);
|
|
60
|
+
fs.renameSync(tmp, destPath);
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
} finally {
|
|
65
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
66
|
+
}
|
|
52
67
|
}
|
|
53
68
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
function swapCanonical(versionedName, versionedPath) {
|
|
70
|
+
if (!IS_WINDOWS) {
|
|
71
|
+
const tmpLink = CANONICAL_PATH + `.link.${process.pid}`;
|
|
72
|
+
try { fs.unlinkSync(tmpLink); } catch {}
|
|
73
|
+
fs.symlinkSync(versionedName, tmpLink);
|
|
74
|
+
fs.renameSync(tmpLink, CANONICAL_PATH);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const oldPath = CANONICAL_PATH + '.old';
|
|
78
|
+
try { fs.unlinkSync(oldPath); } catch {}
|
|
79
|
+
try {
|
|
80
|
+
try { fs.unlinkSync(CANONICAL_PATH); } catch {}
|
|
81
|
+
fs.copyFileSync(versionedPath, CANONICAL_PATH);
|
|
82
|
+
} catch {
|
|
83
|
+
fs.renameSync(CANONICAL_PATH, oldPath);
|
|
84
|
+
try {
|
|
85
|
+
fs.copyFileSync(versionedPath, CANONICAL_PATH);
|
|
86
|
+
} catch {
|
|
87
|
+
try { fs.renameSync(oldPath, CANONICAL_PATH); } catch {}
|
|
88
|
+
throw new Error('locked');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function bootstrapCanonical(brPath, rawPath, version) {
|
|
57
94
|
try {
|
|
58
95
|
fs.mkdirSync(CANONICAL_DIR, { recursive: true });
|
|
59
96
|
const versionedName = `grok-${version}${EXE}`;
|
|
60
97
|
const versionedPath = path.join(CANONICAL_DIR, versionedName);
|
|
61
|
-
if (!fs.existsSync(versionedPath)) {
|
|
62
|
-
|
|
63
|
-
fs.copyFileSync(sourceBinPath, tmpPath);
|
|
64
|
-
if (!IS_WINDOWS) fs.chmodSync(tmpPath, 0o755);
|
|
65
|
-
fs.renameSync(tmpPath, versionedPath);
|
|
66
|
-
}
|
|
67
|
-
if (IS_WINDOWS) {
|
|
68
|
-
const oldPath = CANONICAL_PATH + '.old';
|
|
69
|
-
try { fs.unlinkSync(oldPath); } catch {}
|
|
70
|
-
try {
|
|
71
|
-
try { fs.unlinkSync(CANONICAL_PATH); } catch {}
|
|
72
|
-
fs.copyFileSync(versionedPath, CANONICAL_PATH);
|
|
73
|
-
} catch {
|
|
74
|
-
fs.renameSync(CANONICAL_PATH, oldPath);
|
|
75
|
-
try {
|
|
76
|
-
fs.copyFileSync(versionedPath, CANONICAL_PATH);
|
|
77
|
-
} catch {
|
|
78
|
-
try { fs.renameSync(oldPath, CANONICAL_PATH); } catch {}
|
|
79
|
-
throw new Error('locked');
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
const tmpLink = CANONICAL_PATH + `.link.${process.pid}`;
|
|
84
|
-
try { fs.unlinkSync(tmpLink); } catch {}
|
|
85
|
-
fs.symlinkSync(versionedName, tmpLink);
|
|
86
|
-
fs.renameSync(tmpLink, CANONICAL_PATH);
|
|
98
|
+
if (!fs.existsSync(versionedPath) && !writeVendorBinary(brPath, rawPath, versionedPath)) {
|
|
99
|
+
return null;
|
|
87
100
|
}
|
|
88
|
-
|
|
101
|
+
swapCanonical(versionedName, versionedPath);
|
|
102
|
+
// null on a broken wire-up so the caller falls back to in-place launch.
|
|
103
|
+
return fs.existsSync(CANONICAL_PATH) ? CANONICAL_PATH : null;
|
|
89
104
|
} catch {
|
|
90
|
-
return
|
|
105
|
+
return null;
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
|
|
@@ -105,22 +120,20 @@ function resolveBinary() {
|
|
|
105
120
|
|
|
106
121
|
const rawPath = path.join(platformDir, 'bin', BIN_NAME);
|
|
107
122
|
const brPath = rawPath + '.br';
|
|
123
|
+
const version = readLocalVersion();
|
|
108
124
|
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
// Prefer the canonical layout, decompressing straight into CANONICAL_DIR so
|
|
126
|
+
// no second uncompressed copy lands under node_modules.
|
|
127
|
+
if (version) {
|
|
128
|
+
const bootstrapped = bootstrapCanonical(brPath, rawPath, version);
|
|
129
|
+
if (bootstrapped) return bootstrapped;
|
|
114
130
|
}
|
|
115
|
-
|
|
131
|
+
|
|
132
|
+
// Fallback (unresolved version or unwritable home): materialize in place.
|
|
133
|
+
if (!fs.existsSync(rawPath) && !writeVendorBinary(brPath, rawPath, rawPath)) {
|
|
116
134
|
console.error(`${pkgName}: missing binary at ${rawPath}`);
|
|
117
135
|
process.exit(1);
|
|
118
136
|
}
|
|
119
|
-
|
|
120
|
-
const version = readLocalVersion();
|
|
121
|
-
if (version) {
|
|
122
|
-
return bootstrapCanonical(rawPath, version);
|
|
123
|
-
}
|
|
124
137
|
return rawPath;
|
|
125
138
|
}
|
|
126
139
|
|
package/bin/postinstall.js
CHANGED
|
@@ -16,7 +16,15 @@ const zlib = require('zlib');
|
|
|
16
16
|
const { execSync } = require('child_process');
|
|
17
17
|
const TOML = require('@iarna/toml');
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// $GROK_HOME (else ~/.grok), matching the Rust grok_home() including its
|
|
20
|
+
// canonicalized-home default. Lets fleets relocate the binary off a slow $HOME
|
|
21
|
+
// (NFS); old code hardcoded os.homedir().
|
|
22
|
+
function defaultGrokHome() {
|
|
23
|
+
const home = os.homedir();
|
|
24
|
+
try { return path.join(fs.realpathSync(home), '.grok'); } catch { return path.join(home, '.grok'); }
|
|
25
|
+
}
|
|
26
|
+
const GROK_HOME = process.env.GROK_HOME ?? defaultGrokHome();
|
|
27
|
+
const CANONICAL_DIR = path.join(GROK_HOME, 'bin');
|
|
20
28
|
|
|
21
29
|
const key = `${process.platform}-${process.arch}`;
|
|
22
30
|
const SUPPORTED = new Set([
|
|
@@ -57,43 +65,39 @@ const EXE = IS_WINDOWS ? '.exe' : '';
|
|
|
57
65
|
|
|
58
66
|
fs.mkdirSync(CANONICAL_DIR, { recursive: true });
|
|
59
67
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
function writeVendorBinary(brPath, rawPath, destPath) {
|
|
69
|
+
const tmp = destPath + `.tmp.${process.pid}`;
|
|
70
|
+
try {
|
|
71
|
+
if (fs.existsSync(brPath)) {
|
|
72
|
+
fs.writeFileSync(tmp, zlib.brotliDecompressSync(fs.readFileSync(brPath)));
|
|
73
|
+
} else if (fs.existsSync(rawPath)) {
|
|
74
|
+
fs.copyFileSync(rawPath, tmp);
|
|
75
|
+
} else {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (!IS_WINDOWS) fs.chmodSync(tmp, 0o755);
|
|
79
|
+
fs.renameSync(tmp, destPath);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
} finally {
|
|
84
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
64
88
|
function installBinary(binName, sourceDir, vendorSubpath) {
|
|
65
89
|
const brPath = path.join(sourceDir, 'bin', vendorSubpath + '.br');
|
|
66
90
|
const rawPath = path.join(sourceDir, 'bin', vendorSubpath);
|
|
67
|
-
let vendoredBinPath;
|
|
68
|
-
if (fs.existsSync(brPath)) {
|
|
69
|
-
const compressed = fs.readFileSync(brPath);
|
|
70
|
-
const decompressed = zlib.brotliDecompressSync(compressed);
|
|
71
|
-
vendoredBinPath = rawPath;
|
|
72
|
-
fs.writeFileSync(vendoredBinPath, decompressed);
|
|
73
|
-
if (!IS_WINDOWS) fs.chmodSync(vendoredBinPath, 0o755);
|
|
74
|
-
try { fs.unlinkSync(brPath); } catch {}
|
|
75
|
-
} else if (fs.existsSync(rawPath)) {
|
|
76
|
-
vendoredBinPath = rawPath;
|
|
77
|
-
} else {
|
|
78
|
-
console.error(`@xai-official/grok: missing binary at ${brPath}`);
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
91
|
|
|
82
92
|
const versionedName = `${binName}-${version}${EXE}`;
|
|
83
93
|
const versionedPath = path.join(CANONICAL_DIR, versionedName);
|
|
84
94
|
const canonicalName = `${binName}${EXE}`;
|
|
85
95
|
const canonicalPath = path.join(CANONICAL_DIR, canonicalName);
|
|
86
96
|
|
|
87
|
-
//
|
|
88
|
-
if (!fs.existsSync(versionedPath)) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
fs.copyFileSync(vendoredBinPath, tmpPath);
|
|
92
|
-
if (!IS_WINDOWS) fs.chmodSync(tmpPath, 0o755);
|
|
93
|
-
fs.renameSync(tmpPath, versionedPath);
|
|
94
|
-
} finally {
|
|
95
|
-
try { fs.unlinkSync(tmpPath); } catch {}
|
|
96
|
-
}
|
|
97
|
+
// Skip if this exact version is already installed.
|
|
98
|
+
if (!fs.existsSync(versionedPath) && !writeVendorBinary(brPath, rawPath, versionedPath)) {
|
|
99
|
+
console.error(`@xai-official/grok: missing binary at ${brPath}`);
|
|
100
|
+
return false;
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
if (IS_WINDOWS) {
|
|
@@ -128,10 +132,28 @@ function installBinary(binName, sourceDir, vendorSubpath) {
|
|
|
128
132
|
fs.renameSync(tmpLink, canonicalPath);
|
|
129
133
|
}
|
|
130
134
|
|
|
135
|
+
// Don't report a broken wire-up as success.
|
|
136
|
+
if (!fs.existsSync(canonicalPath)) {
|
|
137
|
+
console.error(`@xai-official/grok: ${canonicalName} did not resolve after install`);
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
131
141
|
console.log(`${binName} ${version} installed to ${canonicalPath} -> ${versionedName}`);
|
|
132
142
|
return true;
|
|
133
143
|
}
|
|
134
144
|
|
|
145
|
+
// Comparator: sort "<prefix>X.Y.Z" filenames by version, newest first.
|
|
146
|
+
function byVersionDescending(prefix) {
|
|
147
|
+
return (a, b) => {
|
|
148
|
+
const pa = a.slice(prefix.length).split('.').map(Number);
|
|
149
|
+
const pb = b.slice(prefix.length).split('.').map(Number);
|
|
150
|
+
for (let i = 0; i < 3; i++) {
|
|
151
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pb[i] || 0) - (pa[i] || 0);
|
|
152
|
+
}
|
|
153
|
+
return 0;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
135
157
|
// Best-effort cleanup of old versioned binaries for a given binary name.
|
|
136
158
|
// Keeps the current version and the previous one (in case a process is still
|
|
137
159
|
// running the old binary and hasn't fully loaded all pages yet).
|
|
@@ -149,14 +171,7 @@ function cleanupOldVersions(binName) {
|
|
|
149
171
|
const suffix = e.slice(prefix.length);
|
|
150
172
|
return /^\d/.test(suffix);
|
|
151
173
|
})
|
|
152
|
-
.sort((
|
|
153
|
-
const pa = a.slice(prefix.length).split('.').map(Number);
|
|
154
|
-
const pb = b.slice(prefix.length).split('.').map(Number);
|
|
155
|
-
for (let i = 0; i < 3; i++) {
|
|
156
|
-
if ((pa[i] || 0) !== (pb[i] || 0)) return (pb[i] || 0) - (pa[i] || 0);
|
|
157
|
-
}
|
|
158
|
-
return 0;
|
|
159
|
-
});
|
|
174
|
+
.sort(byVersionDescending(prefix));
|
|
160
175
|
for (const old of versionedBinaries.slice(1)) {
|
|
161
176
|
try { fs.unlinkSync(path.join(CANONICAL_DIR, old)); } catch {}
|
|
162
177
|
}
|
|
@@ -176,7 +191,7 @@ cleanupOldVersions('grok');
|
|
|
176
191
|
cleanupOldVersions('grok-pager');
|
|
177
192
|
|
|
178
193
|
// Write installer config
|
|
179
|
-
const configDir =
|
|
194
|
+
const configDir = GROK_HOME;
|
|
180
195
|
const configPath = path.join(configDir, 'config.toml');
|
|
181
196
|
let obj = {};
|
|
182
197
|
try { obj = TOML.parse(fs.readFileSync(configPath, 'utf8')); } catch { }
|
|
@@ -208,7 +223,7 @@ const GROK_PATH = path.join(CANONICAL_DIR, `grok${EXE}`);
|
|
|
208
223
|
if (process.env.GROK_INSTALL_COMPLETIONS === '1' && !IS_WINDOWS) {
|
|
209
224
|
try {
|
|
210
225
|
const { spawnSync } = require('child_process');
|
|
211
|
-
const completionsDir = path.join(
|
|
226
|
+
const completionsDir = path.join(GROK_HOME, 'completions');
|
|
212
227
|
const bashPath = path.join(completionsDir, 'bash', 'grok.bash');
|
|
213
228
|
const zshPath = path.join(completionsDir, 'zsh', '_grok');
|
|
214
229
|
fs.mkdirSync(path.dirname(bashPath), { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xai-official/grok",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.112",
|
|
4
4
|
"description": "Bring Grok into your terminal",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"@iarna/toml": "^3.0.0"
|
|
32
32
|
},
|
|
33
33
|
"optionalDependencies": {
|
|
34
|
-
"@xai-official/grok-darwin-arm64": "0.2.
|
|
35
|
-
"@xai-official/grok-darwin-x64": "0.2.
|
|
36
|
-
"@xai-official/grok-linux-arm64": "0.2.
|
|
37
|
-
"@xai-official/grok-linux-x64": "0.2.
|
|
38
|
-
"@xai-official/grok-win32-arm64": "0.2.
|
|
39
|
-
"@xai-official/grok-win32-x64": "0.2.
|
|
34
|
+
"@xai-official/grok-darwin-arm64": "0.2.112",
|
|
35
|
+
"@xai-official/grok-darwin-x64": "0.2.112",
|
|
36
|
+
"@xai-official/grok-linux-arm64": "0.2.112",
|
|
37
|
+
"@xai-official/grok-linux-x64": "0.2.112",
|
|
38
|
+
"@xai-official/grok-win32-arm64": "0.2.112",
|
|
39
|
+
"@xai-official/grok-win32-x64": "0.2.112"
|
|
40
40
|
}
|
|
41
41
|
}
|