@xberg-io/liter-llm-cli 1.11.1 → 1.11.2

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.
Files changed (3) hide show
  1. package/bin/liter-llm.js +28 -21
  2. package/install.js +135 -97
  3. package/package.json +1 -1
package/bin/liter-llm.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- // ~keep Launcher: exec the downloaded native liter-llm binary, forwarding argv and
3
- // ~keep inheriting stdio. If the binary is missing (postinstall failed), download it
4
- // ~keep on demand before exec.
2
+ // ~keep Launcher: exec the downloaded native liter-llm binary, forwarding argv
3
+ // and ~keep inheriting stdio. If the binary is missing (postinstall failed),
4
+ // download it ~keep on demand before exec.
5
5
  import fs from "node:fs";
6
6
  import os from "node:os";
7
7
  import path from "node:path";
8
- import { fileURLToPath } from "node:url";
9
- import { spawnSync } from "node:child_process";
8
+ import {fileURLToPath} from "node:url";
9
+ import {spawnSync} from "node:child_process";
10
10
 
11
11
  const BIN_NAME = "liter-llm";
12
12
 
@@ -18,13 +18,16 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
18
  // ~keep install.js extracts the binary into this same bin/ directory.
19
19
  const binPath = path.join(__dirname, binaryName());
20
20
 
21
- // ~keep A cached binary is only usable if it is non-empty and (on non-Windows) has an
22
- // ~keep exec bit. A truncated or non-executable file means a corrupt cache: re-download.
21
+ // ~keep A cached binary is only usable if it is non-empty and (on non-Windows)
22
+ // has an ~keep exec bit. A truncated or non-executable file means a corrupt
23
+ // cache: re-download.
23
24
  function isHealthy(file) {
24
25
  try {
25
26
  const stat = fs.statSync(file);
26
- if (stat.size <= 0) return false;
27
- if (os.type() !== "Windows_NT" && (stat.mode & 0o111) === 0) return false;
27
+ if (stat.size <= 0)
28
+ return false;
29
+ if (os.type() !== "Windows_NT" && (stat.mode & 0o111) === 0)
30
+ return false;
28
31
  return true;
29
32
  } catch {
30
33
  return false;
@@ -32,20 +35,22 @@ function isHealthy(file) {
32
35
  }
33
36
 
34
37
  async function ensureBinary() {
35
- if (fs.existsSync(binPath) && isHealthy(binPath)) return;
36
- process.stderr.write(`${BIN_NAME}: binary missing or corrupt, attempting download...\n`);
37
- // ~keep Call main() explicitly rather than relying on import side-effects: ESM
38
- // ~keep caches modules, so the installer's top-level run is gated to direct
39
- // ~keep invocation only and would not fire on import.
40
- const { main } = await import("../install.js");
38
+ if (fs.existsSync(binPath) && isHealthy(binPath))
39
+ return;
40
+ process.stderr.write(
41
+ `${BIN_NAME}: binary missing or corrupt, attempting download...\n`);
42
+ // ~keep Call main() explicitly rather than relying on import side-effects:
43
+ // ESM ~keep caches modules, so the installer's top-level run is gated to
44
+ // direct ~keep invocation only and would not fire on import.
45
+ const {main} = await import("../install.js");
41
46
  await main();
42
47
  }
43
48
 
44
49
  function printUnavailable() {
45
50
  process.stderr.write(
46
- `${BIN_NAME} is not available for your platform yet. Install it with:\n` +
47
- ` brew install xberg-io/tap/liter-llm\n` +
48
- ` or use the Xberg plugin: /plugin marketplace add xberg-io/plugins\n`,
51
+ `${BIN_NAME} is not available for your platform yet. Install it with:\n` +
52
+ ` brew install xberg-io/tap/liter-llm\n` +
53
+ ` or use the Xberg plugin: /plugin marketplace add xberg-io/plugins\n`,
49
54
  );
50
55
  }
51
56
 
@@ -55,16 +60,18 @@ async function main() {
55
60
  printUnavailable();
56
61
  process.exit(1);
57
62
  }
58
- const result = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
63
+ const result = spawnSync(binPath, process.argv.slice(2), {stdio : "inherit"});
59
64
  if (result.error) {
60
- process.stderr.write(`${BIN_NAME}: failed to spawn binary: ${result.error.message}\n`);
65
+ process.stderr.write(
66
+ `${BIN_NAME}: failed to spawn binary: ${result.error.message}\n`);
61
67
  process.exit(1);
62
68
  }
63
69
  process.exit(result.status ?? 0);
64
70
  }
65
71
 
66
72
  main().catch((err) => {
67
- // ~keep No standalone CLI for this platform: print the graceful install hint, not a stack.
73
+ // ~keep No standalone CLI for this platform: print the graceful install hint,
74
+ // not a stack.
68
75
  if (err && err.name === "CliUnavailableError") {
69
76
  printUnavailable();
70
77
  process.exit(1);
package/install.js CHANGED
@@ -1,10 +1,10 @@
1
+ import {execFileSync, spawnSync} from "node:child_process";
2
+ import crypto from "node:crypto";
1
3
  import fs from "node:fs";
4
+ import https from "node:https";
2
5
  import os from "node:os";
3
6
  import path from "node:path";
4
- import https from "node:https";
5
- import crypto from "node:crypto";
6
- import { fileURLToPath, pathToFileURL } from "node:url";
7
- import { spawnSync, execFileSync } from "node:child_process";
7
+ import {fileURLToPath, pathToFileURL} from "node:url";
8
8
 
9
9
  const REPO = "xberg-io/liter-llm";
10
10
  const BIN_NAME = "liter-llm";
@@ -17,17 +17,22 @@ function targetTriple() {
17
17
  const arch = os.arch();
18
18
 
19
19
  if (type === "Windows_NT") {
20
- if (arch === "x64") return "x86_64-pc-windows-msvc";
20
+ if (arch === "x64")
21
+ return "x86_64-pc-windows-msvc";
21
22
  throw new Error(`unsupported Windows arch: ${arch}`);
22
23
  }
23
24
  if (type === "Linux") {
24
- if (arch === "x64") return "x86_64-unknown-linux-gnu";
25
- if (arch === "arm64") return "aarch64-unknown-linux-gnu";
25
+ if (arch === "x64")
26
+ return "x86_64-unknown-linux-gnu";
27
+ if (arch === "arm64")
28
+ return "aarch64-unknown-linux-gnu";
26
29
  throw new Error(`unsupported Linux arch: ${arch}`);
27
30
  }
28
31
  if (type === "Darwin") {
29
- if (arch === "arm64") return "aarch64-apple-darwin";
30
- if (arch === "x64") return "x86_64-apple-darwin";
32
+ if (arch === "arm64")
33
+ return "aarch64-apple-darwin";
34
+ if (arch === "x64")
35
+ return "x86_64-apple-darwin";
31
36
  throw new Error(`unsupported macOS arch: ${arch}`);
32
37
  }
33
38
  throw new Error(`unsupported platform: ${type} ${arch}`);
@@ -37,30 +42,35 @@ function binaryName() {
37
42
  return os.type() === "Windows_NT" ? `${BIN_NAME}.exe` : BIN_NAME;
38
43
  }
39
44
 
40
- function httpGetBuffer(url, { headers = {} } = {}, maxRedirects = 5) {
45
+ function httpGetBuffer(url, {headers = {}} = {}, maxRedirects = 5) {
41
46
  return new Promise((resolve, reject) => {
42
- if (maxRedirects < 0) return reject(new Error("too many redirects"));
47
+ if (maxRedirects < 0)
48
+ return reject(new Error("too many redirects"));
43
49
  if (!/^https:\/\//i.test(url)) {
44
50
  return reject(new Error(`refusing non-https URL: ${url}`));
45
51
  }
46
- const req = https.get(url, { headers: { "User-Agent": USER_AGENT, ...headers } }, (res) => {
47
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
48
- res.resume();
49
- const next = res.headers.location;
50
- if (!/^https:\/\//i.test(next)) {
51
- return reject(new Error(`refusing non-https redirect to: ${next}`));
52
- }
53
- return httpGetBuffer(next, { headers }, maxRedirects - 1).then(resolve, reject);
54
- }
55
- if (res.statusCode !== 200) {
56
- res.resume();
57
- return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
58
- }
59
- const chunks = [];
60
- res.on("data", (c) => chunks.push(c));
61
- res.on("end", () => resolve(Buffer.concat(chunks)));
62
- res.on("error", reject);
63
- });
52
+ const req = https.get(
53
+ url, {headers : {"User-Agent" : USER_AGENT, ...headers}}, (res) => {
54
+ if (res.statusCode >= 300 && res.statusCode < 400 &&
55
+ res.headers.location) {
56
+ res.resume();
57
+ const next = res.headers.location;
58
+ if (!/^https:\/\//i.test(next)) {
59
+ return reject(
60
+ new Error(`refusing non-https redirect to: ${next}`));
61
+ }
62
+ return httpGetBuffer(next, {headers}, maxRedirects - 1)
63
+ .then(resolve, reject);
64
+ }
65
+ if (res.statusCode !== 200) {
66
+ res.resume();
67
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
68
+ }
69
+ const chunks = [];
70
+ res.on("data", (c) => chunks.push(c));
71
+ res.on("end", () => resolve(Buffer.concat(chunks)));
72
+ res.on("error", reject);
73
+ });
64
74
  req.on("error", reject);
65
75
  req.setTimeout(60000, () => {
66
76
  req.destroy();
@@ -70,7 +80,8 @@ function httpGetBuffer(url, { headers = {} } = {}, maxRedirects = 5) {
70
80
  }
71
81
 
72
82
  async function httpGetJson(url) {
73
- const buf = await httpGetBuffer(url, { headers: { Accept: "application/vnd.github+json" } });
83
+ const buf = await httpGetBuffer(
84
+ url, {headers : {Accept : "application/vnd.github+json"}});
74
85
  return JSON.parse(buf.toString("utf8"));
75
86
  }
76
87
 
@@ -106,19 +117,24 @@ export function isNonCliArtifact(name) {
106
117
  export function assetScore(name) {
107
118
  const n = (name || "").toLowerCase();
108
119
  let score = 0;
109
- if (n.includes("cli")) score += 2;
110
- if (n.includes(BIN_NAME.toLowerCase())) score += 1;
120
+ if (n.includes("cli"))
121
+ score += 2;
122
+ if (n.includes(BIN_NAME.toLowerCase()))
123
+ score += 1;
111
124
  return score;
112
125
  }
113
126
 
114
127
  export function selectArchiveName(names, triple) {
115
128
  const survivors = (names || []).filter((name) => {
116
129
  const n = (name || "").toLowerCase();
117
- if (!n.includes(triple)) return false;
118
- if (!(n.endsWith(".tar.gz") || n.endsWith(".zip"))) return false;
130
+ if (!n.includes(triple))
131
+ return false;
132
+ if (!(n.endsWith(".tar.gz") || n.endsWith(".zip")))
133
+ return false;
119
134
  return !isNonCliArtifact(n);
120
135
  });
121
- if (survivors.length === 0) return null;
136
+ if (survivors.length === 0)
137
+ return null;
122
138
  survivors.sort((a, b) => assetScore(b) - assetScore(a));
123
139
  return survivors[0];
124
140
  }
@@ -127,15 +143,16 @@ async function resolveRelease() {
127
143
  const triple = targetTriple();
128
144
  const pinned = process.env[VERSION_ENV];
129
145
  const apiUrl = pinned
130
- ? `https://api.github.com/repos/${REPO}/releases/tags/${encodeURIComponent(pinned)}`
131
- : `https://api.github.com/repos/${REPO}/releases/latest`;
146
+ ? `https://api.github.com/repos/${REPO}/releases/tags/${
147
+ encodeURIComponent(pinned)}`
148
+ : `https://api.github.com/repos/${REPO}/releases/latest`;
132
149
 
133
150
  let release;
134
151
  try {
135
152
  release = await httpGetJson(apiUrl);
136
153
  } catch (err) {
137
154
  if (pinned && /HTTP 404/.test(err.message)) {
138
- throw new Error(`release tag '${pinned}' not found`, { cause: err });
155
+ throw new Error(`release tag '${pinned}' not found`, {cause : err});
139
156
  }
140
157
  throw err;
141
158
  }
@@ -143,16 +160,18 @@ async function resolveRelease() {
143
160
  const tag = release.tag_name || pinned || "latest";
144
161
 
145
162
  const chosenName = selectArchiveName(
146
- assets.map((a) => a.name),
147
- triple,
163
+ assets.map((a) => a.name),
164
+ triple,
148
165
  );
149
166
  if (!chosenName) {
150
- throw new CliUnavailableError(`no standalone CLI asset for target triple "${triple}" in ${REPO} release ${tag}`);
167
+ throw new CliUnavailableError(`no standalone CLI asset for target triple "${
168
+ triple}" in ${REPO} release ${tag}`);
151
169
  }
152
170
  const archive = assets.find((a) => a.name === chosenName);
153
- const checksums = assets.find((a) => (a.name || "").toUpperCase().includes("SHA256SUMS"));
171
+ const checksums =
172
+ assets.find((a) => (a.name || "").toUpperCase().includes("SHA256SUMS"));
154
173
 
155
- return { tag, triple, archive, checksums };
174
+ return {tag, triple, archive, checksums};
156
175
  }
157
176
 
158
177
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -161,11 +180,14 @@ const BIN_DIR = path.join(__dirname, "bin");
161
180
  function expectedDigest(text, assetName) {
162
181
  for (const raw of text.split(/\r?\n/)) {
163
182
  const line = raw.trim();
164
- if (!line) continue;
183
+ if (!line)
184
+ continue;
165
185
  const parts = line.split(/\s+/);
166
- if (parts.length < 2) continue;
186
+ if (parts.length < 2)
187
+ continue;
167
188
  const name = parts[parts.length - 1].replace(/^\*/, "");
168
- if (name === assetName) return parts[0].toLowerCase();
189
+ if (name === assetName)
190
+ return parts[0].toLowerCase();
169
191
  }
170
192
  return null;
171
193
  }
@@ -173,49 +195,58 @@ function expectedDigest(text, assetName) {
173
195
  async function verifyOrWarn(archiveBuf, archiveName, checksums) {
174
196
  if (!checksums) {
175
197
  process.stderr.write(
176
- `WARNING: no SHA256SUMS asset found for ${archiveName}; ` +
177
- `installing over HTTPS without checksum verification.\n`,
198
+ `WARNING: no SHA256SUMS asset found for ${archiveName}; ` +
199
+ `installing over HTTPS without checksum verification.\n`,
178
200
  );
179
201
  return;
180
202
  }
181
- const sumsText = (await httpGetBuffer(checksums.browser_download_url)).toString("utf8");
203
+ const sumsText =
204
+ (await httpGetBuffer(checksums.browser_download_url)).toString("utf8");
182
205
  const expected = expectedDigest(sumsText, archiveName);
183
206
  if (!expected) {
184
207
  throw new Error(
185
- `no checksum entry for ${archiveName} in ${checksums.name} — refusing to install unverified binary`,
208
+ `no checksum entry for ${archiveName} in ${
209
+ checksums.name} — refusing to install unverified binary`,
186
210
  );
187
211
  }
188
- const actual = crypto.createHash("sha256").update(archiveBuf).digest("hex").toLowerCase();
212
+ const actual = crypto.createHash("sha256")
213
+ .update(archiveBuf)
214
+ .digest("hex")
215
+ .toLowerCase();
189
216
  if (actual !== expected) {
190
- throw new Error(`checksum mismatch for ${archiveName} (expected ${expected}, got ${actual})`);
217
+ throw new Error(`checksum mismatch for ${archiveName} (expected ${
218
+ expected}, got ${actual})`);
191
219
  }
192
220
  process.stderr.write(`Checksum verified for ${archiveName}.\n`);
193
221
  }
194
222
 
195
223
  function isUnsafeEntry(name) {
196
224
  const entry = String(name).replace(/\\/g, "/").trim();
197
- if (!entry) return false;
198
- if (entry.startsWith("/")) return true;
199
- if (/^[a-zA-Z]:/.test(entry)) return true;
200
- if (entry.startsWith("//")) return true;
225
+ if (!entry)
226
+ return false;
227
+ if (entry.startsWith("/"))
228
+ return true;
229
+ if (/^[a-zA-Z]:/.test(entry))
230
+ return true;
231
+ if (entry.startsWith("//"))
232
+ return true;
201
233
  return entry.split("/").some((part) => part === "..");
202
234
  }
203
235
 
204
236
  function listTarEntries(archivePath) {
205
- const result = spawnSync("tar", ["-tzf", archivePath]);
237
+ const result = spawnSync("tar", [ "-tzf", archivePath ]);
206
238
  if (result.status !== 0) {
207
239
  const stderr = result.stderr ? result.stderr.toString() : "";
208
240
  throw new Error(`tar listing failed: ${stderr || result.error}`);
209
241
  }
210
- return result.stdout
211
- .toString()
212
- .split(/\r?\n/)
213
- .map((s) => s.trim())
214
- .filter(Boolean);
242
+ return result.stdout.toString()
243
+ .split(/\r?\n/)
244
+ .map((s) => s.trim())
245
+ .filter(Boolean);
215
246
  }
216
247
 
217
248
  function extractTarGz(archivePath, destDir) {
218
- const result = spawnSync("tar", ["-xzf", archivePath, "-C", destDir]);
249
+ const result = spawnSync("tar", [ "-xzf", archivePath, "-C", destDir ]);
219
250
  if (result.status !== 0) {
220
251
  const stderr = result.stderr ? result.stderr.toString() : "";
221
252
  throw new Error(`tar extraction failed: ${stderr || result.error}`);
@@ -225,28 +256,26 @@ function extractTarGz(archivePath, destDir) {
225
256
  function listZipEntries(archivePath) {
226
257
  if (os.type() === "Windows_NT") {
227
258
  const script =
228
- "$ErrorActionPreference='Stop';" +
229
- "Add-Type -AssemblyName System.IO.Compression.FileSystem;" +
230
- "[System.IO.Compression.ZipFile]::OpenRead($args[0]).Entries |" +
231
- " ForEach-Object { $_.FullName }";
232
- const out = execFileSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", script, archivePath], {
233
- encoding: "utf8",
234
- });
235
- return out
236
- .split(/\r?\n/)
237
- .map((s) => s.trim())
238
- .filter(Boolean);
259
+ "$ErrorActionPreference='Stop';" +
260
+ "Add-Type -AssemblyName System.IO.Compression.FileSystem;" +
261
+ "[System.IO.Compression.ZipFile]::OpenRead($args[0]).Entries |" +
262
+ " ForEach-Object { $_.FullName }";
263
+ const out = execFileSync(
264
+ "powershell",
265
+ [ "-NoProfile", "-NonInteractive", "-Command", script, archivePath ], {
266
+ encoding : "utf8",
267
+ });
268
+ return out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
239
269
  }
240
- const result = spawnSync("unzip", ["-Z1", archivePath]);
270
+ const result = spawnSync("unzip", [ "-Z1", archivePath ]);
241
271
  if (result.status !== 0) {
242
272
  const stderr = result.stderr ? result.stderr.toString() : "";
243
273
  throw new Error(`zip listing failed: ${stderr || result.error}`);
244
274
  }
245
- return result.stdout
246
- .toString()
247
- .split(/\r?\n/)
248
- .map((s) => s.trim())
249
- .filter(Boolean);
275
+ return result.stdout.toString()
276
+ .split(/\r?\n/)
277
+ .map((s) => s.trim())
278
+ .filter(Boolean);
250
279
  }
251
280
 
252
281
  function extractZip(archivePath, destDir) {
@@ -268,7 +297,7 @@ function extractZip(archivePath, destDir) {
268
297
  }
269
298
  return;
270
299
  }
271
- const result = spawnSync("unzip", ["-o", archivePath, "-d", destDir]);
300
+ const result = spawnSync("unzip", [ "-o", archivePath, "-d", destDir ]);
272
301
  if (result.status !== 0) {
273
302
  const stderr = result.stderr ? result.stderr.toString() : "";
274
303
  throw new Error(`zip extraction failed: ${stderr || result.error}`);
@@ -276,11 +305,12 @@ function extractZip(archivePath, destDir) {
276
305
  }
277
306
 
278
307
  function findBinary(dir, name) {
279
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
308
+ for (const entry of fs.readdirSync(dir, {withFileTypes : true})) {
280
309
  const full = path.join(dir, entry.name);
281
310
  if (entry.isDirectory()) {
282
311
  const found = findBinary(full, name);
283
- if (found) return found;
312
+ if (found)
313
+ return found;
284
314
  } else if (entry.name === name) {
285
315
  return full;
286
316
  }
@@ -289,18 +319,22 @@ function findBinary(dir, name) {
289
319
  }
290
320
 
291
321
  function findDir(dir, name) {
292
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
293
- if (!entry.isDirectory()) continue;
294
- if (entry.name === name) return path.join(dir, entry.name);
322
+ for (const entry of fs.readdirSync(dir, {withFileTypes : true})) {
323
+ if (!entry.isDirectory())
324
+ continue;
325
+ if (entry.name === name)
326
+ return path.join(dir, entry.name);
295
327
  const found = findDir(path.join(dir, entry.name), name);
296
- if (found) return found;
328
+ if (found)
329
+ return found;
297
330
  }
298
331
  return null;
299
332
  }
300
333
 
301
334
  function safeExtract(archivePath, archiveName, dest) {
302
335
  const isZip = archiveName.toLowerCase().endsWith(".zip");
303
- const entries = isZip ? listZipEntries(archivePath) : listTarEntries(archivePath);
336
+ const entries =
337
+ isZip ? listZipEntries(archivePath) : listTarEntries(archivePath);
304
338
  for (const entry of entries) {
305
339
  if (isUnsafeEntry(entry)) {
306
340
  throw new Error(`refusing unsafe archive entry: ${entry}`);
@@ -318,18 +352,19 @@ function safeExtract(archivePath, archiveName, dest) {
318
352
  const binName = binaryName();
319
353
  const extractedBin = findBinary(tmpDir, binName);
320
354
  if (!extractedBin) {
321
- throw new CliUnavailableError(`archive ${archiveName} did not contain expected CLI binary ${binName}`);
355
+ throw new CliUnavailableError(`archive ${
356
+ archiveName} did not contain expected CLI binary ${binName}`);
322
357
  }
323
358
  const finalBin = path.join(dest, binName);
324
359
  fs.copyFileSync(extractedBin, finalBin);
325
360
 
326
361
  const libDir = findDir(tmpDir, "lib");
327
362
  if (libDir) {
328
- fs.cpSync(libDir, path.join(dest, "lib"), { recursive: true });
363
+ fs.cpSync(libDir, path.join(dest, "lib"), {recursive : true});
329
364
  }
330
365
  return finalBin;
331
366
  } finally {
332
- fs.rmSync(tmpDir, { recursive: true, force: true });
367
+ fs.rmSync(tmpDir, {recursive : true, force : true});
333
368
  }
334
369
  }
335
370
 
@@ -341,14 +376,17 @@ export async function main() {
341
376
  const stat = fs.statSync(finalPath);
342
377
  const sizeOk = stat.size > 0;
343
378
  const execOk = os.type() === "Windows_NT" || (stat.mode & 0o111) !== 0;
344
- if (sizeOk && execOk) return;
345
- } catch {}
379
+ if (sizeOk && execOk)
380
+ return;
381
+ } catch {
382
+ }
346
383
  }
347
384
 
348
- fs.mkdirSync(BIN_DIR, { recursive: true });
385
+ fs.mkdirSync(BIN_DIR, {recursive : true});
349
386
 
350
- const { tag, archive, checksums } = await resolveRelease();
351
- process.stderr.write(`Downloading ${BIN_NAME} ${tag} asset ${archive.name}...\n`);
387
+ const {tag, archive, checksums} = await resolveRelease();
388
+ process.stderr.write(
389
+ `Downloading ${BIN_NAME} ${tag} asset ${archive.name}...\n`);
352
390
 
353
391
  const archiveBuf = await httpGetBuffer(archive.browser_download_url);
354
392
  await verifyOrWarn(archiveBuf, archive.name, checksums);
@@ -359,7 +397,7 @@ export async function main() {
359
397
  fs.writeFileSync(archivePath, archiveBuf);
360
398
  safeExtract(archivePath, archive.name, BIN_DIR);
361
399
  } finally {
362
- fs.rmSync(stageDir, { recursive: true, force: true });
400
+ fs.rmSync(stageDir, {recursive : true, force : true});
363
401
  }
364
402
 
365
403
  if (os.type() !== "Windows_NT") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xberg-io/liter-llm-cli",
3
- "version": "1.11.1",
3
+ "version": "1.11.2",
4
4
  "description": "CLI proxy for liter-llm — downloads and runs the native liter-llm binary from GitHub releases.",
5
5
  "license": "MIT",
6
6
  "author": "Na'aman Hirschfeld",