@llmist/cli 9.1.2 → 9.3.0

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 ADDED
@@ -0,0 +1,107 @@
1
+ # @llmist/cli
2
+
3
+ <p align="center">
4
+ <a href="https://github.com/zbigniewsobiecki/llmist/actions/workflows/ci.yml"><img src="https://github.com/zbigniewsobiecki/llmist/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
5
+ <a href="https://www.npmjs.com/package/@llmist/cli"><img src="https://img.shields.io/npm/v/@llmist/cli.svg" alt="npm version"></a>
6
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License"></a>
7
+ </p>
8
+
9
+ **Command-line interface for llmist - run LLM agents from the terminal.**
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install -g @llmist/cli
15
+ # or
16
+ bunx @llmist/cli
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ # Set your API key
23
+ export OPENAI_API_KEY="sk-..."
24
+
25
+ # Quick completion
26
+ llmist complete "Explain TypeScript generics in one paragraph"
27
+
28
+ # Run an agent with gadgets
29
+ llmist agent "Search for files" --gadgets ./my-gadgets/
30
+
31
+ # Interactive chat
32
+ llmist chat
33
+ ```
34
+
35
+ ## Commands
36
+
37
+ | Command | Description |
38
+ |---------|-------------|
39
+ | `complete <prompt>` | One-shot LLM completion |
40
+ | `agent <prompt>` | Run agent with gadgets |
41
+ | `chat` | Interactive chat session |
42
+ | `tui` | Launch terminal UI |
43
+
44
+ ## Using Gadgets
45
+
46
+ Load gadgets from various sources:
47
+
48
+ ```bash
49
+ # Local directory
50
+ llmist agent "Do something" --gadgets ./gadgets/
51
+
52
+ # npm package
53
+ llmist agent "Search the web" --gadgets dhalsim/BrowseWeb
54
+
55
+ # Git URL
56
+ llmist agent "Process files" --gadgets github:user/repo
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ Create a `llmist.toml` file for reusable configurations:
62
+
63
+ ```toml
64
+ [agent]
65
+ model = "sonnet"
66
+ system = "You are a helpful assistant"
67
+
68
+ [gadgets]
69
+ paths = ["./gadgets"]
70
+ external = ["dhalsim/BrowseWeb"]
71
+
72
+ [display]
73
+ markdown = true
74
+ colors = true
75
+ ```
76
+
77
+ Use with:
78
+
79
+ ```bash
80
+ llmist agent "Do something" --config ./llmist.toml
81
+ ```
82
+
83
+ ## Terminal UI
84
+
85
+ The TUI provides an interactive interface to browse execution history, inspect raw payloads, and debug agent runs:
86
+
87
+ ```bash
88
+ llmist tui
89
+ ```
90
+
91
+ ## Documentation
92
+
93
+ Full documentation at [llmist.dev/cli](https://llmist.dev/cli/getting-started/introduction/)
94
+
95
+ - [Configuration Reference](https://llmist.dev/cli/configuration/toml-reference/)
96
+ - [Writing Gadgets](https://llmist.dev/cli/gadgets/local-gadgets/)
97
+ - [External Gadgets](https://llmist.dev/cli/gadgets/external-gadgets/)
98
+ - [TUI Guide](https://llmist.dev/cli/tui/overview/)
99
+
100
+ ## Related Packages
101
+
102
+ - [`llmist`](https://www.npmjs.com/package/llmist) - Core library
103
+ - [`@llmist/testing`](https://www.npmjs.com/package/@llmist/testing) - Testing utilities
104
+
105
+ ## License
106
+
107
+ MIT
package/dist/cli.js CHANGED
@@ -79,7 +79,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
79
79
  // package.json
80
80
  var package_default = {
81
81
  name: "@llmist/cli",
82
- version: "9.1.2",
82
+ version: "9.3.0",
83
83
  description: "CLI for llmist - run LLM agents from the command line",
84
84
  type: "module",
85
85
  main: "dist/cli.js",
@@ -94,6 +94,7 @@ var package_default = {
94
94
  clean: "rimraf dist",
95
95
  postinstall: "node scripts/postinstall.js"
96
96
  },
97
+ homepage: "https://llmist.dev",
97
98
  repository: {
98
99
  type: "git",
99
100
  url: "https://github.com/zbigniewsobiecki/llmist.git",
@@ -360,6 +361,84 @@ import { AbstractGadget } from "llmist";
360
361
  import { z as z2 } from "zod";
361
362
  import { createGadget as createGadget2 } from "llmist";
362
363
 
364
+ // src/spawn.ts
365
+ import { spawn as nodeSpawn } from "child_process";
366
+ var isBun = typeof Bun !== "undefined";
367
+ function adaptBunProcess(proc) {
368
+ return {
369
+ exited: proc.exited,
370
+ stdout: proc.stdout ?? null,
371
+ stderr: proc.stderr ?? null,
372
+ stdin: proc.stdin ? {
373
+ write(data) {
374
+ proc.stdin?.write(data);
375
+ },
376
+ end() {
377
+ proc.stdin?.end();
378
+ }
379
+ } : null,
380
+ kill: () => proc.kill()
381
+ };
382
+ }
383
+ function nodeStreamToReadableStream(nodeStream) {
384
+ if (!nodeStream) return null;
385
+ return new ReadableStream({
386
+ start(controller) {
387
+ nodeStream.on("data", (chunk) => {
388
+ controller.enqueue(new Uint8Array(chunk));
389
+ });
390
+ nodeStream.on("end", () => {
391
+ controller.close();
392
+ });
393
+ nodeStream.on("error", (err) => {
394
+ controller.error(err);
395
+ });
396
+ },
397
+ cancel() {
398
+ nodeStream.destroy();
399
+ }
400
+ });
401
+ }
402
+ function spawn(argv, options = {}) {
403
+ if (isBun) {
404
+ return adaptBunProcess(Bun.spawn(argv, options));
405
+ }
406
+ const [command, ...args] = argv;
407
+ const proc = nodeSpawn(command, args, {
408
+ cwd: options.cwd,
409
+ stdio: [
410
+ options.stdin === "pipe" ? "pipe" : options.stdin ?? "ignore",
411
+ options.stdout === "pipe" ? "pipe" : options.stdout ?? "ignore",
412
+ options.stderr === "pipe" ? "pipe" : options.stderr ?? "ignore"
413
+ ]
414
+ });
415
+ const exited = new Promise((resolve2, reject) => {
416
+ proc.on("exit", (code) => {
417
+ resolve2(code ?? 1);
418
+ });
419
+ proc.on("error", (err) => {
420
+ reject(err);
421
+ });
422
+ });
423
+ const stdin = proc.stdin ? {
424
+ write(data) {
425
+ proc.stdin?.write(data);
426
+ },
427
+ end() {
428
+ proc.stdin?.end();
429
+ }
430
+ } : null;
431
+ return {
432
+ exited,
433
+ stdout: nodeStreamToReadableStream(proc.stdout),
434
+ stderr: nodeStreamToReadableStream(proc.stderr),
435
+ stdin,
436
+ kill() {
437
+ proc.kill();
438
+ }
439
+ };
440
+ }
441
+
363
442
  // src/builtins/filesystem/utils.ts
364
443
  import fs from "fs";
365
444
  import path from "path";
@@ -449,11 +528,16 @@ q`
449
528
  const validatedPath = validatePathIsWithinCwd(filePath);
450
529
  const safeCommands = filterDangerousCommands(commands);
451
530
  try {
452
- const proc = Bun.spawn(["ed", validatedPath], {
531
+ const proc = spawn(["ed", validatedPath], {
453
532
  stdin: "pipe",
454
533
  stdout: "pipe",
455
534
  stderr: "pipe"
456
535
  });
536
+ if (!proc.stdin) {
537
+ return `path=${filePath}
538
+
539
+ error: Failed to open stdin for ed process`;
540
+ }
457
541
  proc.stdin.write(`${safeCommands}
458
542
  `);
459
543
  proc.stdin.end();
@@ -773,7 +857,7 @@ var runCommand = createGadget6({
773
857
  }
774
858
  let timeoutId;
775
859
  try {
776
- const proc = Bun.spawn(argv, {
860
+ const proc = spawn(argv, {
777
861
  cwd: workingDir,
778
862
  stdout: "pipe",
779
863
  stderr: "pipe"
@@ -848,49 +932,71 @@ function parseGadgetSpecifier(specifier) {
848
932
  let baseUrl;
849
933
  let ref;
850
934
  let preset;
935
+ let gadgetName;
936
+ const extractGadgetName = (str) => {
937
+ const match = str.match(/^(.*)\/([A-Z][a-zA-Z0-9]*)$/);
938
+ if (match) {
939
+ return { rest: match[1], gadgetName: match[2] };
940
+ }
941
+ return { rest: str };
942
+ };
851
943
  if (url.includes("#")) {
852
944
  const hashIndex = url.indexOf("#");
853
945
  baseUrl = url.slice(0, hashIndex);
854
- const refAndPreset = url.slice(hashIndex + 1);
855
- if (refAndPreset.includes(":")) {
856
- const colonIndex = refAndPreset.indexOf(":");
857
- ref = refAndPreset.slice(0, colonIndex);
858
- preset = refAndPreset.slice(colonIndex + 1);
946
+ let refAndRest = url.slice(hashIndex + 1);
947
+ const gadgetResult = extractGadgetName(refAndRest);
948
+ refAndRest = gadgetResult.rest;
949
+ gadgetName = gadgetResult.gadgetName;
950
+ if (refAndRest.includes(":")) {
951
+ const colonIndex = refAndRest.indexOf(":");
952
+ ref = refAndRest.slice(0, colonIndex);
953
+ preset = refAndRest.slice(colonIndex + 1);
859
954
  } else {
860
- ref = refAndPreset;
955
+ ref = refAndRest;
861
956
  }
862
957
  } else {
863
958
  const gitExtIndex = url.indexOf(".git");
864
959
  if (gitExtIndex !== -1) {
865
- const afterGit = url.slice(gitExtIndex + 4);
960
+ let afterGit = url.slice(gitExtIndex + 4);
961
+ baseUrl = url.slice(0, gitExtIndex + 4);
962
+ const gadgetResult = extractGadgetName(afterGit);
963
+ afterGit = gadgetResult.rest;
964
+ gadgetName = gadgetResult.gadgetName;
866
965
  if (afterGit.startsWith(":")) {
867
- baseUrl = url.slice(0, gitExtIndex + 4);
868
966
  preset = afterGit.slice(1);
869
- } else {
870
- baseUrl = url;
871
967
  }
872
968
  } else {
873
- baseUrl = url;
969
+ const gadgetResult = extractGadgetName(url);
970
+ baseUrl = gadgetResult.rest;
971
+ gadgetName = gadgetResult.gadgetName;
874
972
  }
875
973
  }
876
974
  return {
877
975
  type: "git",
878
976
  package: baseUrl,
879
977
  version: ref,
880
- preset
978
+ preset,
979
+ gadgetName
881
980
  };
882
981
  }
883
- const npmMatch = specifier.match(
884
- /^(@?[a-z0-9][\w.-]*)(?:@([\w.-]+))?(?::([a-z]+))?(?:\/(\w+))?$/i
982
+ let npmSpecifier = specifier;
983
+ let npmGadgetName;
984
+ const gadgetMatch = specifier.match(/^(.+)\/([A-Z][a-zA-Z0-9]*)$/);
985
+ if (gadgetMatch) {
986
+ npmSpecifier = gadgetMatch[1];
987
+ npmGadgetName = gadgetMatch[2];
988
+ }
989
+ const npmMatch = npmSpecifier.match(
990
+ /^(@[a-z0-9][\w.-]*\/[a-z0-9][\w.-]*|[a-z0-9][\w.-]*)(?:@([\w.-]+))?(?::([a-z]+))?$/i
885
991
  );
886
992
  if (npmMatch) {
887
- const [, pkg, version, preset, gadgetName] = npmMatch;
993
+ const [, pkg, version, preset] = npmMatch;
888
994
  return {
889
995
  type: "npm",
890
996
  package: pkg,
891
997
  version,
892
998
  preset,
893
- gadgetName
999
+ gadgetName: npmGadgetName
894
1000
  };
895
1001
  }
896
1002
  return null;
@@ -1144,6 +1250,14 @@ function expandHomePath(input) {
1144
1250
  }
1145
1251
  return path5.join(home, input.slice(1));
1146
1252
  }
1253
+ function parseLocalSpecifier(specifier) {
1254
+ const match = specifier.match(/^(.+):([A-Z][a-zA-Z0-9]*)$/);
1255
+ if (match) {
1256
+ const [, basePath, gadgetName] = match;
1257
+ return { path: basePath, gadgetName };
1258
+ }
1259
+ return { path: specifier };
1260
+ }
1147
1261
  function isFileLikeSpecifier(specifier) {
1148
1262
  return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(path5.sep);
1149
1263
  }
@@ -1227,7 +1341,10 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
1227
1341
  throw new Error(`Failed to load external package '${specifier}': ${message}`);
1228
1342
  }
1229
1343
  }
1230
- const resolved = resolveGadgetSpecifier(specifier, cwd);
1344
+ const localSpec = isFileLikeSpecifier(specifier) ? parseLocalSpecifier(specifier) : null;
1345
+ const pathToResolve = localSpec?.path ?? specifier;
1346
+ const gadgetNameFilter = localSpec?.gadgetName;
1347
+ const resolved = resolveGadgetSpecifier(pathToResolve, cwd);
1231
1348
  let exports;
1232
1349
  try {
1233
1350
  exports = await importer(resolved);
@@ -1242,6 +1359,17 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
1242
1359
  const message = error instanceof Error ? error.message : String(error);
1243
1360
  throw new Error(`Failed to initialize gadgets from module '${specifier}': ${message}`);
1244
1361
  }
1362
+ if (gadgetNameFilter) {
1363
+ const filtered = extracted.filter(
1364
+ (g) => g.name?.toLowerCase() === gadgetNameFilter.toLowerCase()
1365
+ );
1366
+ if (filtered.length === 0) {
1367
+ throw new Error(
1368
+ `Gadget '${gadgetNameFilter}' not found in module '${pathToResolve}'. Available gadgets: ${extracted.map((g) => g.name).join(", ")}`
1369
+ );
1370
+ }
1371
+ extracted = filtered;
1372
+ }
1245
1373
  if (extracted.length === 0) {
1246
1374
  throw new Error(`Module '${specifier}' does not export any Gadget instances.`);
1247
1375
  }