agentlab 0.1.5 → 0.2.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/LICENSE +21 -0
- package/README.md +11 -35
- package/dist/agentlab.js +10 -0
- package/dist/cache.js +141 -0
- package/dist/cli.js +131 -386
- package/dist/manifest.js +84 -0
- package/dist/prepack.js +20 -0
- package/package.json +19 -18
- package/release-manifest.json +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentLab contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,43 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AgentLab
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`agentlab` is a terminal AI coding agent for real project work. It chats, runs tools, edits files, and keeps multi-step coding sessions in one CLI flow.
|
|
6
|
-
|
|
7
|
-
## Supports
|
|
8
|
-
|
|
9
|
-
- Providers:
|
|
10
|
-
- OpenAI Codex
|
|
11
|
-
- GitHub Copilot
|
|
12
|
-
- Claude (subscription login via Claude Code, not API key mode)
|
|
13
|
-
- Core capabilities:
|
|
14
|
-
- Provider/model switching from inside the CLI
|
|
15
|
-
- Interactive login and auth status checks per provider
|
|
16
|
-
- Session history save/resume
|
|
17
|
-
|
|
18
|
-
## Install
|
|
19
|
-
|
|
20
|
-
Install globally from npm:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm install -g agentlab
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Then run:
|
|
3
|
+
One captain for all your coding agents in a fast local terminal UI.
|
|
27
4
|
|
|
28
5
|
```bash
|
|
6
|
+
npm install --global agentlab
|
|
29
7
|
agentlab
|
|
30
8
|
```
|
|
31
9
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
bun run build
|
|
37
|
-
bun run index.tsx
|
|
38
|
-
```
|
|
10
|
+
The npm launcher downloads the matching AgentLab executable on first use, verifies its
|
|
11
|
+
package-pinned size and SHA-256 digest, and caches it by version. It rechecks the cached digest
|
|
12
|
+
before execution. Linux x64 with glibc and Apple-silicon macOS are supported. AgentLab requires
|
|
13
|
+
`tmux` and at least one authenticated provider CLI: Codex, Claude Code, or OpenCode.
|
|
39
14
|
|
|
40
|
-
|
|
15
|
+
Use `agentlab update` for an explicit update or `agentlab update --check` to check without changing
|
|
16
|
+
the installation. Normal cached startup performs no update request.
|
|
41
17
|
|
|
42
|
-
|
|
43
|
-
|
|
18
|
+
See [the AgentLab repository](https://github.com/RiadMefti/agentlab) for usage, release checksums,
|
|
19
|
+
SBOMs, and build provenance.
|
package/dist/agentlab.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runLauncher } from "./cli.js";
|
|
3
|
+
try {
|
|
4
|
+
process.exitCode = await runLauncher(process.argv.slice(2));
|
|
5
|
+
}
|
|
6
|
+
catch (error) {
|
|
7
|
+
const message = error instanceof Error ? error.message : "Unexpected launcher failure.";
|
|
8
|
+
process.stderr.write(`agentlab: ${message}\n`);
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { chmod, lstat, mkdir, open, rename, rm } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
|
+
import { releaseDownloadUrl } from "./manifest.js";
|
|
7
|
+
const DOWNLOAD_TIMEOUT_MILLISECONDS = 120_000;
|
|
8
|
+
const MAXIMUM_PATH_LENGTH = 4096;
|
|
9
|
+
const TRUSTED_DOWNLOAD_HOSTS = new Set(["github.com"]);
|
|
10
|
+
export function resolveCacheRoot(environment = process.env, homeDirectory = homedir()) {
|
|
11
|
+
const override = environment.AGENTLAB_CACHE_PATH;
|
|
12
|
+
if (override !== undefined)
|
|
13
|
+
return validateAbsolutePath(override, "AGENTLAB_CACHE_PATH");
|
|
14
|
+
const xdgCache = environment.XDG_CACHE_HOME;
|
|
15
|
+
if (xdgCache !== undefined) {
|
|
16
|
+
return resolve(validateAbsolutePath(xdgCache, "XDG_CACHE_HOME"), "agentlab");
|
|
17
|
+
}
|
|
18
|
+
return resolve(homeDirectory, ".cache", "agentlab");
|
|
19
|
+
}
|
|
20
|
+
export function cachedBinaryPath(cacheRoot, manifest, key) {
|
|
21
|
+
return join(cacheRoot, "releases", manifest.version, key, "agentlab");
|
|
22
|
+
}
|
|
23
|
+
export async function ensureCachedBinary(manifest, key, options = {}) {
|
|
24
|
+
const cacheRoot = options.cacheRoot ?? resolveCacheRoot();
|
|
25
|
+
const binaryPath = cachedBinaryPath(cacheRoot, manifest, key);
|
|
26
|
+
const target = manifest.targets[key];
|
|
27
|
+
if (await isUsableCachedBinary(binaryPath, target.size, target.sha256))
|
|
28
|
+
return binaryPath;
|
|
29
|
+
await mkdir(dirname(binaryPath), { mode: 0o700, recursive: true });
|
|
30
|
+
await rm(binaryPath, { force: true });
|
|
31
|
+
const temporaryPath = `${binaryPath}.${String(process.pid)}.${randomUUID()}.tmp`;
|
|
32
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
33
|
+
const downloadUrl = releaseDownloadUrl(manifest, key);
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetchImplementation(downloadUrl, {
|
|
36
|
+
redirect: "follow",
|
|
37
|
+
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MILLISECONDS)
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`AgentLab download failed with HTTP ${String(response.status)}.`);
|
|
41
|
+
}
|
|
42
|
+
assertTrustedDownloadUrl(response.url || downloadUrl);
|
|
43
|
+
if (response.body === null)
|
|
44
|
+
throw new Error("AgentLab download returned an empty body.");
|
|
45
|
+
const declaredLength = response.headers.get("content-length");
|
|
46
|
+
if (declaredLength !== null && Number(declaredLength) !== target.size) {
|
|
47
|
+
throw new Error("AgentLab download size does not match the signed release manifest.");
|
|
48
|
+
}
|
|
49
|
+
const file = await open(temporaryPath, "wx", 0o600);
|
|
50
|
+
const hash = createHash("sha256");
|
|
51
|
+
let size = 0;
|
|
52
|
+
try {
|
|
53
|
+
const reader = response.body.getReader();
|
|
54
|
+
for (;;) {
|
|
55
|
+
const readResult = await reader.read();
|
|
56
|
+
const chunk = readDownloadChunk(readResult);
|
|
57
|
+
if (chunk === undefined)
|
|
58
|
+
break;
|
|
59
|
+
size += chunk.byteLength;
|
|
60
|
+
if (size > target.size) {
|
|
61
|
+
throw new Error("AgentLab download exceeded the signed release size.");
|
|
62
|
+
}
|
|
63
|
+
hash.update(chunk);
|
|
64
|
+
await writeAll(file, chunk);
|
|
65
|
+
}
|
|
66
|
+
await file.sync();
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await file.close();
|
|
70
|
+
}
|
|
71
|
+
if (size !== target.size || hash.digest("hex") !== target.sha256) {
|
|
72
|
+
throw new Error("AgentLab download failed checksum verification.");
|
|
73
|
+
}
|
|
74
|
+
await chmod(temporaryPath, 0o755);
|
|
75
|
+
await rename(temporaryPath, binaryPath);
|
|
76
|
+
return binaryPath;
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
await rm(temporaryPath, { force: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function isUsableCachedBinary(path, expectedSize, expectedChecksum) {
|
|
83
|
+
try {
|
|
84
|
+
const details = await lstat(path);
|
|
85
|
+
if (!details.isFile() || details.size !== expectedSize || (details.mode & 0o111) === 0) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const hash = createHash("sha256");
|
|
89
|
+
for await (const rawChunk of createReadStream(path)) {
|
|
90
|
+
const chunk = rawChunk;
|
|
91
|
+
if (!(chunk instanceof Uint8Array)) {
|
|
92
|
+
throw new Error("AgentLab cached binary returned an invalid file chunk.");
|
|
93
|
+
}
|
|
94
|
+
hash.update(chunk);
|
|
95
|
+
}
|
|
96
|
+
return hash.digest("hex") === expectedChecksum;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const code = error.code;
|
|
100
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
101
|
+
return false;
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function assertTrustedDownloadUrl(value) {
|
|
106
|
+
const url = new URL(value);
|
|
107
|
+
const trusted = url.protocol === "https:" &&
|
|
108
|
+
(TRUSTED_DOWNLOAD_HOSTS.has(url.hostname) || url.hostname.endsWith(".githubusercontent.com"));
|
|
109
|
+
if (!trusted)
|
|
110
|
+
throw new Error("AgentLab download redirected to an untrusted host.");
|
|
111
|
+
}
|
|
112
|
+
function validateAbsolutePath(value, label) {
|
|
113
|
+
if (value.length === 0 ||
|
|
114
|
+
value.length > MAXIMUM_PATH_LENGTH ||
|
|
115
|
+
!isAbsolute(value) ||
|
|
116
|
+
dirname(resolve(value)) === resolve(value)) {
|
|
117
|
+
throw new Error(`${label} must be a non-empty absolute path.`);
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
async function writeAll(file, chunk) {
|
|
122
|
+
let offset = 0;
|
|
123
|
+
while (offset < chunk.byteLength) {
|
|
124
|
+
const { bytesWritten } = await file.write(chunk, offset, chunk.byteLength - offset);
|
|
125
|
+
if (bytesWritten <= 0)
|
|
126
|
+
throw new Error("AgentLab download could not be written to disk.");
|
|
127
|
+
offset += bytesWritten;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function readDownloadChunk(value) {
|
|
131
|
+
if (typeof value !== "object" || value === null || !("done" in value)) {
|
|
132
|
+
throw new Error("AgentLab download returned an invalid stream result.");
|
|
133
|
+
}
|
|
134
|
+
const result = value;
|
|
135
|
+
if (result.done === true)
|
|
136
|
+
return undefined;
|
|
137
|
+
if (result.done !== false || !(result.value instanceof Uint8Array)) {
|
|
138
|
+
throw new Error("AgentLab download returned an invalid stream chunk.");
|
|
139
|
+
}
|
|
140
|
+
return result.value;
|
|
141
|
+
}
|