@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 +107 -0
- package/dist/cli.js +148 -20
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
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.
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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 =
|
|
955
|
+
ref = refAndRest;
|
|
861
956
|
}
|
|
862
957
|
} else {
|
|
863
958
|
const gitExtIndex = url.indexOf(".git");
|
|
864
959
|
if (gitExtIndex !== -1) {
|
|
865
|
-
|
|
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
|
-
|
|
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
|
-
|
|
884
|
-
|
|
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
|
|
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
|
|
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
|
}
|