@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 CHANGED
@@ -1,17 +1,15 @@
1
1
  #!/usr/bin/env node
2
- // Thin trampoline: resolves the grok binary from the matching per-platform
3
- // optional dependency package and execs it.
2
+ // Thin trampoline: resolves the grok binary and execs it.
4
3
  //
5
- // Falls back to bootstrapping the canonical ~/.grok/bin/grok-<version> symlink
6
- // layout if postinstall hasn't run (e.g. npx, or postinstall failure).
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
- // Binary location strategy (in priority order):
9
- // 1. ~/.grok/bin/grok — canonical versioned symlink (postinstall.js)
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
- const CANONICAL_DIR = path.join(os.homedir(), '.grok', 'bin');
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
- // Decompress a brotli-compressed binary to a sibling path. Atomic via tmp+rename.
45
- function decompressBrotli(brPath, outPath) {
46
- const compressed = fs.readFileSync(brPath);
47
- const decompressed = zlib.brotliDecompressSync(compressed);
48
- const tmp = outPath + `.tmp.${process.pid}`;
49
- fs.writeFileSync(tmp, decompressed);
50
- if (!IS_WINDOWS) fs.chmodSync(tmp, 0o755);
51
- try { fs.renameSync(tmp, outPath); } catch {}
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
- // Bootstrap the canonical versioned-symlink layout from a source binary.
55
- // Returns the canonical path on success, or the source path on failure.
56
- function bootstrapCanonical(sourceBinPath, version) {
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
- const tmpPath = versionedPath + `.tmp.${process.pid}`;
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
- return CANONICAL_PATH;
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 sourceBinPath;
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
- // Decompress on first use if needed (atomic via tmp+rename).
110
- if (!fs.existsSync(rawPath)) {
111
- if (fs.existsSync(brPath)) {
112
- decompressBrotli(brPath, rawPath);
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
- if (!fs.existsSync(rawPath)) {
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
 
@@ -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
- const CANONICAL_DIR = path.join(os.homedir(), '.grok', 'bin');
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
- // Install a vendored binary: versioned filename + symlink (Unix) or copy (Windows).
61
- // Binaries are shipped brotli-compressed in the per-platform npm tarball to keep
62
- // each sub-package well under npm's ~200 MB tarball limit. This function
63
- // decompresses them before installing into the canonical layout.
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
- // Only copy if this exact version isn't already installed.
88
- if (!fs.existsSync(versionedPath)) {
89
- const tmpPath = versionedPath + `.tmp.${process.pid}`;
90
- try {
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((a, b) => {
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 = path.join(os.homedir(), '.grok');
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(os.homedir(), '.grok', 'completions');
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.110",
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.110",
35
- "@xai-official/grok-darwin-x64": "0.2.110",
36
- "@xai-official/grok-linux-arm64": "0.2.110",
37
- "@xai-official/grok-linux-x64": "0.2.110",
38
- "@xai-official/grok-win32-arm64": "0.2.110",
39
- "@xai-official/grok-win32-x64": "0.2.110"
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
  }