agentlab 0.2.1 → 0.2.3
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 +64 -9
- package/dist/cache.js +2 -0
- package/dist/cli.js +22 -3
- package/dist/install-progress.js +82 -0
- package/package.json +16 -4
- package/release-manifest.json +5 -5
package/README.md
CHANGED
|
@@ -1,19 +1,74 @@
|
|
|
1
1
|
# AgentLab
|
|
2
2
|
|
|
3
|
-
One captain
|
|
3
|
+
One captain per project folder, with Codex, Claude Code, and OpenCode in one local terminal.
|
|
4
|
+
|
|
5
|
+
AgentLab lets you switch between projects and run multiple coding-agent sessions while keeping their
|
|
6
|
+
real CLIs accessible. Each project points to a folder on your machine. It has one captain and any
|
|
7
|
+
number of workers.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
4
10
|
|
|
5
11
|
```bash
|
|
6
12
|
npm install --global agentlab
|
|
7
13
|
agentlab
|
|
8
14
|
```
|
|
9
15
|
|
|
10
|
-
The
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
Run `agentlab` from any directory. The app opens to the project picker and waits for you to choose a
|
|
17
|
+
folder. On first launch, the npm package downloads the matching AgentLab executable from GitHub and
|
|
18
|
+
caches it. The installer shows the percentage, downloaded size, and download speed while it works.
|
|
19
|
+
Later launches use the cached executable.
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Linux x64 with glibc, or macOS on Apple silicon
|
|
24
|
+
- Node.js 20 or newer
|
|
25
|
+
- `tmux`
|
|
26
|
+
- At least one installed and authenticated provider CLI: `codex`, `claude`, or `opencode`
|
|
27
|
+
- A terminal at least 90 columns by 18 rows
|
|
28
|
+
|
|
29
|
+
Install tmux with `brew install tmux` on macOS or your Linux distribution's package manager.
|
|
30
|
+
|
|
31
|
+
## Start a project
|
|
32
|
+
|
|
33
|
+
1. Run `agentlab`.
|
|
34
|
+
2. Press `Alt+N` and enter the path to an existing folder. The folder can be anywhere on your
|
|
35
|
+
machine.
|
|
36
|
+
3. Name the project and choose its captain provider, model, and reasoning level.
|
|
37
|
+
4. Add workers with `Alt+W` when the captain needs more sessions.
|
|
38
|
+
5. Switch projects from the left sidebar. Each project keeps its captain and workers together.
|
|
39
|
+
|
|
40
|
+
Removing a project stops its managed sessions and forgets it in AgentLab. The project folder and its
|
|
41
|
+
files stay untouched.
|
|
42
|
+
|
|
43
|
+
## Keys
|
|
44
|
+
|
|
45
|
+
| Key | Action |
|
|
46
|
+
| ------------------------- | --------------------------------------------- |
|
|
47
|
+
| `Alt+1`, `Alt+2`, `Alt+3` | Focus projects, terminal, or agents |
|
|
48
|
+
| `Up`, `Down`, `Enter` | Navigate a focused sidebar and enter terminal |
|
|
49
|
+
| `Alt+N` | Add a project folder |
|
|
50
|
+
| `Alt+W` | Add a worker to the selected project |
|
|
51
|
+
| `Delete` | Remove the selected project or worker |
|
|
52
|
+
| `Alt+C` | Copy the terminal selection |
|
|
53
|
+
| `Alt+Q` | Quit AgentLab |
|
|
54
|
+
|
|
55
|
+
## Updates
|
|
56
|
+
|
|
57
|
+
AgentLab updates only when you ask it to:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
agentlab update --check
|
|
61
|
+
agentlab update
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Normal startup does not check for updates.
|
|
65
|
+
|
|
66
|
+
## Local data
|
|
14
67
|
|
|
15
|
-
|
|
16
|
-
|
|
68
|
+
AgentLab opens no network listener. It stores the project list in a local SQLite database, keeps
|
|
69
|
+
provider credentials in each CLI's existing authentication store, and runs agent sessions through
|
|
70
|
+
tmux.
|
|
17
71
|
|
|
18
|
-
|
|
19
|
-
|
|
72
|
+
Read the [documentation](https://github.com/RiadMefti/agentlab#readme), browse
|
|
73
|
+
[releases](https://github.com/RiadMefti/agentlab/releases), or
|
|
74
|
+
[report a problem](https://github.com/RiadMefti/agentlab/issues).
|
package/dist/cache.js
CHANGED
|
@@ -32,6 +32,7 @@ export async function ensureCachedBinary(manifest, key, options = {}) {
|
|
|
32
32
|
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
33
33
|
const downloadUrl = releaseDownloadUrl(manifest, key);
|
|
34
34
|
try {
|
|
35
|
+
options.onDownloadProgress?.({ downloadedBytes: 0, totalBytes: target.size });
|
|
35
36
|
const response = await fetchImplementation(downloadUrl, {
|
|
36
37
|
redirect: "follow",
|
|
37
38
|
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MILLISECONDS)
|
|
@@ -62,6 +63,7 @@ export async function ensureCachedBinary(manifest, key, options = {}) {
|
|
|
62
63
|
}
|
|
63
64
|
hash.update(chunk);
|
|
64
65
|
await writeAll(file, chunk);
|
|
66
|
+
options.onDownloadProgress?.({ downloadedBytes: size, totalBytes: target.size });
|
|
65
67
|
}
|
|
66
68
|
await file.sync();
|
|
67
69
|
}
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readFile } from "node:fs/promises";
|
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { ensureCachedBinary, resolveCacheRoot } from "./cache.js";
|
|
7
|
+
import { InstallProgressReporter } from "./install-progress.js";
|
|
7
8
|
import { currentRuntimePlatform, parseReleaseManifest, resolveRuntimeTarget } from "./manifest.js";
|
|
8
9
|
const REGISTRY_LATEST_URL = "https://registry.npmjs.org/agentlab/latest";
|
|
9
10
|
const STABLE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
|
|
@@ -32,10 +33,28 @@ export async function runLauncher(args, options = {}) {
|
|
|
32
33
|
}
|
|
33
34
|
const runtime = options.runtime ?? currentRuntimePlatform();
|
|
34
35
|
const target = resolveRuntimeTarget(runtime);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
const stderr = options.stderr ?? ((message) => process.stderr.write(message));
|
|
37
|
+
const progress = new InstallProgressReporter({
|
|
38
|
+
isTTY: options.stderrIsTTY ?? (options.stderr === undefined && process.stderr.isTTY),
|
|
39
|
+
target,
|
|
40
|
+
version: packageVersion,
|
|
41
|
+
write: stderr
|
|
38
42
|
});
|
|
43
|
+
let binary;
|
|
44
|
+
try {
|
|
45
|
+
binary = await ensureCachedBinary(manifest, target, {
|
|
46
|
+
cacheRoot: options.cacheRoot ?? resolveCacheRoot(environment),
|
|
47
|
+
fetch: options.fetch,
|
|
48
|
+
onDownloadProgress: (downloadProgress) => {
|
|
49
|
+
progress.report(downloadProgress);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
progress.clear();
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
progress.complete();
|
|
39
58
|
return execute(binary, args, {
|
|
40
59
|
...environment,
|
|
41
60
|
AGENTLAB_INSTALL_METHOD: "npm",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
const BAR_WIDTH = 16;
|
|
3
|
+
const BYTES_PER_MEGABYTE = 1_000_000;
|
|
4
|
+
const MINIMUM_RENDER_INTERVAL_MILLISECONDS = 100;
|
|
5
|
+
const CLEAR_LINE = "\r\u001B[2K";
|
|
6
|
+
const targetLabels = {
|
|
7
|
+
"linux-x64": "Linux x64",
|
|
8
|
+
"mac-arm64": "macOS arm64"
|
|
9
|
+
};
|
|
10
|
+
export class InstallProgressReporter {
|
|
11
|
+
isTTY;
|
|
12
|
+
now;
|
|
13
|
+
targetLabel;
|
|
14
|
+
version;
|
|
15
|
+
write;
|
|
16
|
+
active = false;
|
|
17
|
+
finished = false;
|
|
18
|
+
lastRenderAt;
|
|
19
|
+
startedAt;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.isTTY = options.isTTY;
|
|
22
|
+
this.now = options.now ?? (() => performance.now());
|
|
23
|
+
this.targetLabel = targetLabels[options.target];
|
|
24
|
+
this.version = options.version;
|
|
25
|
+
this.write = options.write;
|
|
26
|
+
}
|
|
27
|
+
report(progress) {
|
|
28
|
+
if (this.finished)
|
|
29
|
+
return;
|
|
30
|
+
const now = this.now();
|
|
31
|
+
if (!this.active) {
|
|
32
|
+
this.active = true;
|
|
33
|
+
this.startedAt = now;
|
|
34
|
+
if (!this.isTTY) {
|
|
35
|
+
this.write(`Downloading AgentLab ${this.version} for ${this.targetLabel} (${formatMegabytes(progress.totalBytes)} MB)...\n`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!this.isTTY)
|
|
40
|
+
return;
|
|
41
|
+
const complete = progress.downloadedBytes >= progress.totalBytes;
|
|
42
|
+
if (!complete &&
|
|
43
|
+
this.lastRenderAt !== undefined &&
|
|
44
|
+
now - this.lastRenderAt < MINIMUM_RENDER_INTERVAL_MILLISECONDS) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.lastRenderAt = now;
|
|
48
|
+
this.write(`${CLEAR_LINE}${renderProgress(progress, now - (this.startedAt ?? now), this.version)}`);
|
|
49
|
+
}
|
|
50
|
+
complete() {
|
|
51
|
+
if (!this.active || this.finished)
|
|
52
|
+
return;
|
|
53
|
+
this.finished = true;
|
|
54
|
+
if (this.isTTY)
|
|
55
|
+
this.write(CLEAR_LINE);
|
|
56
|
+
const marker = this.isTTY ? "✓ " : "";
|
|
57
|
+
this.write(`${marker}AgentLab ${this.version} installed for ${this.targetLabel}.\n`);
|
|
58
|
+
this.write("Starting AgentLab...\n");
|
|
59
|
+
}
|
|
60
|
+
clear() {
|
|
61
|
+
if (!this.active || this.finished)
|
|
62
|
+
return;
|
|
63
|
+
this.finished = true;
|
|
64
|
+
if (this.isTTY)
|
|
65
|
+
this.write(CLEAR_LINE);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function renderProgress(progress, elapsedMilliseconds, version) {
|
|
69
|
+
const ratio = clamp(progress.downloadedBytes / progress.totalBytes, 0, 1);
|
|
70
|
+
const filled = ratio === 1 ? BAR_WIDTH : Math.floor(ratio * BAR_WIDTH);
|
|
71
|
+
const bar = `${"█".repeat(filled)}${"░".repeat(BAR_WIDTH - filled)}`;
|
|
72
|
+
const percent = Math.floor(ratio * 100);
|
|
73
|
+
const elapsedSeconds = Math.max(elapsedMilliseconds / 1_000, 0.001);
|
|
74
|
+
const megabytesPerSecond = progress.downloadedBytes / BYTES_PER_MEGABYTE / elapsedSeconds;
|
|
75
|
+
return `Downloading AgentLab ${version} [${bar}] ${String(percent).padStart(3)}% ${formatMegabytes(progress.downloadedBytes)}/${formatMegabytes(progress.totalBytes)} MB ${megabytesPerSecond.toFixed(1)} MB/s`;
|
|
76
|
+
}
|
|
77
|
+
function formatMegabytes(bytes) {
|
|
78
|
+
return (bytes / BYTES_PER_MEGABYTE).toFixed(1);
|
|
79
|
+
}
|
|
80
|
+
function clamp(value, minimum, maximum) {
|
|
81
|
+
return Math.min(Math.max(value, minimum), maximum);
|
|
82
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlab",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Run Codex, Claude Code, and OpenCode in one local terminal, with one captain per project.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"coding-agents",
|
|
8
|
+
"codex",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"opencode",
|
|
11
|
+
"multi-agent",
|
|
12
|
+
"terminal",
|
|
13
|
+
"tui",
|
|
14
|
+
"tmux"
|
|
15
|
+
],
|
|
5
16
|
"type": "module",
|
|
6
17
|
"bin": {
|
|
7
18
|
"agentlab": "dist/agentlab.js"
|
|
@@ -21,12 +32,13 @@
|
|
|
21
32
|
"license": "MIT",
|
|
22
33
|
"repository": {
|
|
23
34
|
"type": "git",
|
|
24
|
-
"url": "git+https://github.com/RiadMefti/agentlab.git"
|
|
35
|
+
"url": "git+https://github.com/RiadMefti/agentlab.git",
|
|
36
|
+
"directory": "packages/launcher"
|
|
25
37
|
},
|
|
26
38
|
"bugs": {
|
|
27
39
|
"url": "https://github.com/RiadMefti/agentlab/issues"
|
|
28
40
|
},
|
|
29
|
-
"homepage": "https://github.com/RiadMefti/agentlab
|
|
41
|
+
"homepage": "https://github.com/RiadMefti/agentlab",
|
|
30
42
|
"publishConfig": {
|
|
31
43
|
"access": "public"
|
|
32
44
|
}
|
package/release-manifest.json
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
"repository": "RiadMefti/agentlab",
|
|
3
3
|
"targets": {
|
|
4
4
|
"linux-x64": {
|
|
5
|
-
"asset": "agentlab-v0.2.
|
|
6
|
-
"sha256": "
|
|
5
|
+
"asset": "agentlab-v0.2.3-linux-x64",
|
|
6
|
+
"sha256": "c4da3d5e1d5191a3316fc4c6db4d40b73dabb8eb882354fa67c918097944b5ec",
|
|
7
7
|
"size": 116298952
|
|
8
8
|
},
|
|
9
9
|
"mac-arm64": {
|
|
10
|
-
"asset": "agentlab-v0.2.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"asset": "agentlab-v0.2.3-mac-arm64",
|
|
11
|
+
"sha256": "b6ebe1fc40338c16a9fbfe7da7cc0b79c9a93d516387f61a0cdc4a3fae30ca7c",
|
|
12
12
|
"size": 77351282
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"version": "0.2.
|
|
15
|
+
"version": "0.2.3"
|
|
16
16
|
}
|