@agent-sh/agent-workspace-linux 0.1.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 +48 -0
- package/bin/agent-workspace-linux.js +60 -0
- package/package.json +47 -0
- package/scripts/postinstall.js +163 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @agent-sh/agent-workspace-linux
|
|
2
|
+
|
|
3
|
+
npm distribution wrapper for the [`agent-workspace-linux`](https://github.com/agent-sh/agent-workspace-linux) MCP server — isolated Linux desktop workspaces for AI agents.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g @agent-sh/agent-workspace-linux
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The installer automatically downloads the prebuilt binary for your architecture from the matching [GitHub Release](https://github.com/agent-sh/agent-workspace-linux/releases).
|
|
12
|
+
|
|
13
|
+
**Linux only.** `x64` (x86_64) and `arm64` (aarch64) are supported.
|
|
14
|
+
|
|
15
|
+
> **Note — `--ignore-scripts`:** package managers that skip lifecycle scripts (e.g. `pnpm` with `ignore-scripts=true`, some CI setups) will not download the binary automatically. Run the postinstall manually to recover:
|
|
16
|
+
> ```sh
|
|
17
|
+
> node $(npm root -g)/@agent-sh/agent-workspace-linux/scripts/postinstall.js
|
|
18
|
+
> ```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Once installed, the server is on your PATH (the command stays unscoped):
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
agent-workspace-linux
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
It is an [MCP](https://modelcontextprotocol.io/) server that speaks JSON-RPC over stdio. Wire it into your MCP client config, e.g. for Claude Code:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"agent-workspace-linux": {
|
|
34
|
+
"command": "agent-workspace-linux",
|
|
35
|
+
"args": []
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Source and full documentation
|
|
42
|
+
|
|
43
|
+
All source code, tool documentation, and issue tracking are at:
|
|
44
|
+
**<https://github.com/agent-sh/agent-workspace-linux>**
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// agent-workspace-linux.js — thin launcher for the native MCP server binary.
|
|
3
|
+
//
|
|
4
|
+
// Why a JS launcher instead of pointing `bin` directly at the native binary?
|
|
5
|
+
// - npm resolves `bin` entries from the tarball at install time; the downloaded
|
|
6
|
+
// binary is placed there by postinstall, which runs AFTER npm creates shims.
|
|
7
|
+
// Pointing `bin` at the native binary directly is therefore unreliable across
|
|
8
|
+
// npm versions and install modes.
|
|
9
|
+
// - The JS shim is always present in the tarball, so npm can always create the
|
|
10
|
+
// global shim. At runtime the shim locates the native binary relative to its
|
|
11
|
+
// own __dirname and execs it.
|
|
12
|
+
//
|
|
13
|
+
// This pattern mirrors what esbuild, @swc/core, and rollup use for their own
|
|
14
|
+
// native binaries published via npm.
|
|
15
|
+
//
|
|
16
|
+
// Signal handling: SIGINT is ignored in this parent process so the child MCP
|
|
17
|
+
// server handles Ctrl-C itself without the shell printing a double ^C.
|
|
18
|
+
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
const { spawn } = require("child_process");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
const fs = require("fs");
|
|
24
|
+
|
|
25
|
+
const binaryPath = path.join(__dirname, "agent-workspace-linux");
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(binaryPath)) {
|
|
28
|
+
console.error(
|
|
29
|
+
"agent-workspace-linux: native binary not found at " + binaryPath + "\n" +
|
|
30
|
+
"The binary is downloaded during installation (postinstall). " +
|
|
31
|
+
"If --ignore-scripts was used, run:\n" +
|
|
32
|
+
" node " + path.join(__dirname, "..", "scripts", "postinstall.js")
|
|
33
|
+
);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Ignore SIGINT in the launcher: let the child (MCP server) handle its own
|
|
38
|
+
// shutdown. Without this both processes would receive the signal and the child
|
|
39
|
+
// might not get a chance to flush stdio cleanly.
|
|
40
|
+
process.on("SIGINT", () => {});
|
|
41
|
+
|
|
42
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
43
|
+
stdio: "inherit",
|
|
44
|
+
// Detach false (default): child shares the session, gets signals from tty.
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.on("close", (code, signal) => {
|
|
48
|
+
if (signal) {
|
|
49
|
+
// Propagate signal so the parent's exit looks like a signalled death to
|
|
50
|
+
// any caller (e.g. npm run scripts that check $?).
|
|
51
|
+
process.kill(process.pid, signal);
|
|
52
|
+
} else {
|
|
53
|
+
process.exit(code ?? 0);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
child.on("error", (err) => {
|
|
58
|
+
console.error("agent-workspace-linux: failed to start binary —", err.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agent-sh/agent-workspace-linux",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Isolated Linux desktop workspaces for AI agents — MCP server binary wrapper.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/agent-sh/agent-workspace-linux.git",
|
|
12
|
+
"directory": "npm"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/agent-sh/agent-workspace-linux#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/agent-sh/agent-workspace-linux/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"linux",
|
|
21
|
+
"desktop",
|
|
22
|
+
"automation",
|
|
23
|
+
"workspace",
|
|
24
|
+
"agent"
|
|
25
|
+
],
|
|
26
|
+
"os": [
|
|
27
|
+
"linux"
|
|
28
|
+
],
|
|
29
|
+
"cpu": [
|
|
30
|
+
"x64",
|
|
31
|
+
"arm64"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"bin": {
|
|
37
|
+
"agent-workspace-linux": "bin/agent-workspace-linux.js"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"postinstall": "node scripts/postinstall.js"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"bin/agent-workspace-linux.js",
|
|
44
|
+
"scripts/postinstall.js",
|
|
45
|
+
"README.md"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// postinstall.js — downloads the matching prebuilt binary from GitHub Releases.
|
|
3
|
+
//
|
|
4
|
+
// Invoked automatically by npm after `npm install -g @agent-sh/agent-workspace-linux`.
|
|
5
|
+
// If --ignore-scripts was used (e.g. pnpm default, some CI setups) this script
|
|
6
|
+
// will NOT run; the binary won't be present and the launcher will print a clear
|
|
7
|
+
// error at runtime. To recover, re-run installation with scripts enabled or
|
|
8
|
+
// invoke this file directly: `node node_modules/@agent-sh/agent-workspace-linux/scripts/postinstall.js`
|
|
9
|
+
//
|
|
10
|
+
// Checksum verification is not wired in because the release workflow contract
|
|
11
|
+
// does not publish .sha256 sidecar files. If that changes, add a step here that
|
|
12
|
+
// downloads <asset>.sha256 and verifies it against the tmp file before rename.
|
|
13
|
+
|
|
14
|
+
"use strict";
|
|
15
|
+
|
|
16
|
+
const https = require("https");
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
const { execFileSync } = require("child_process");
|
|
20
|
+
|
|
21
|
+
// ── Platform / arch guard ────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
if (process.platform !== "linux") {
|
|
24
|
+
console.error(
|
|
25
|
+
`agent-workspace-linux: unsupported platform "${process.platform}". ` +
|
|
26
|
+
"This package only works on Linux."
|
|
27
|
+
);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ARCH_MAP = {
|
|
32
|
+
x64: "x86_64-unknown-linux-gnu",
|
|
33
|
+
arm64: "aarch64-unknown-linux-gnu",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const rustTarget = ARCH_MAP[process.arch];
|
|
37
|
+
if (!rustTarget) {
|
|
38
|
+
console.error(
|
|
39
|
+
`agent-workspace-linux: unsupported CPU architecture "${process.arch}". ` +
|
|
40
|
+
`Supported: ${Object.keys(ARCH_MAP).join(", ")}.`
|
|
41
|
+
);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Paths ────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const pkg = require("../package.json");
|
|
48
|
+
const version = pkg.version; // e.g. "0.1.0"
|
|
49
|
+
const assetName = `agent-workspace-linux-${rustTarget}`;
|
|
50
|
+
const downloadUrl =
|
|
51
|
+
`https://github.com/agent-sh/agent-workspace-linux/releases/download/` +
|
|
52
|
+
`v${version}/${assetName}`;
|
|
53
|
+
|
|
54
|
+
const binDir = path.join(__dirname, "..", "bin");
|
|
55
|
+
const destPath = path.join(binDir, "agent-workspace-linux");
|
|
56
|
+
const tmpPath = destPath + ".tmp";
|
|
57
|
+
|
|
58
|
+
// ── Download helper (follows redirects, max 5 hops) ─────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Download `url` to `tmpFile`, following up to `maxRedirects` 3xx responses.
|
|
62
|
+
* Resolves when the file is fully written, rejects on error or bad status.
|
|
63
|
+
*/
|
|
64
|
+
function download(url, tmpFile, maxRedirects = 5) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
function get(currentUrl, hopsLeft) {
|
|
67
|
+
https
|
|
68
|
+
.get(currentUrl, { headers: { "User-Agent": "node-fetch/postinstall" } }, (res) => {
|
|
69
|
+
const { statusCode, headers } = res;
|
|
70
|
+
|
|
71
|
+
// Follow redirects (GitHub releases always redirect to S3).
|
|
72
|
+
if (statusCode >= 300 && statusCode < 400 && headers.location) {
|
|
73
|
+
if (hopsLeft === 0) {
|
|
74
|
+
res.resume();
|
|
75
|
+
return reject(new Error(`Too many redirects downloading ${url}`));
|
|
76
|
+
}
|
|
77
|
+
res.resume(); // drain and ignore body
|
|
78
|
+
return get(headers.location, hopsLeft - 1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (statusCode !== 200) {
|
|
82
|
+
res.resume();
|
|
83
|
+
return reject(
|
|
84
|
+
new Error(
|
|
85
|
+
`Failed to download ${url}: HTTP ${statusCode}. ` +
|
|
86
|
+
"Check that the release exists and the version in package.json matches."
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const out = fs.createWriteStream(tmpFile);
|
|
92
|
+
res.pipe(out);
|
|
93
|
+
out.on("finish", () => out.close(resolve));
|
|
94
|
+
out.on("error", (err) => {
|
|
95
|
+
fs.unlink(tmpFile, () => {}); // best-effort cleanup
|
|
96
|
+
reject(err);
|
|
97
|
+
});
|
|
98
|
+
res.on("error", (err) => {
|
|
99
|
+
fs.unlink(tmpFile, () => {});
|
|
100
|
+
reject(err);
|
|
101
|
+
});
|
|
102
|
+
})
|
|
103
|
+
.on("error", (err) => {
|
|
104
|
+
fs.unlink(tmpFile, () => {});
|
|
105
|
+
reject(err);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get(url, maxRedirects);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
(async () => {
|
|
116
|
+
// Ensure bin/ directory exists (it should, tracked by git via .gitkeep, but
|
|
117
|
+
// be defensive for edge-case installs).
|
|
118
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
119
|
+
|
|
120
|
+
// Remove stale tmp file from a previous interrupted install.
|
|
121
|
+
try {
|
|
122
|
+
fs.unlinkSync(tmpPath);
|
|
123
|
+
} catch (_) {}
|
|
124
|
+
|
|
125
|
+
console.log(
|
|
126
|
+
`agent-workspace-linux: downloading ${assetName} v${version}…`
|
|
127
|
+
);
|
|
128
|
+
console.log(` URL: ${downloadUrl}`);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await download(downloadUrl, tmpPath);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
// Clean up in case the file was partially created.
|
|
134
|
+
try {
|
|
135
|
+
fs.unlinkSync(tmpPath);
|
|
136
|
+
} catch (_) {}
|
|
137
|
+
console.error(`\nagent-workspace-linux: download failed — ${err.message}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Atomic rename: avoids leaving a half-written binary.
|
|
142
|
+
try {
|
|
143
|
+
fs.renameSync(tmpPath, destPath);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error(
|
|
146
|
+
`agent-workspace-linux: could not move binary into place — ${err.message}`
|
|
147
|
+
);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Mark executable.
|
|
152
|
+
try {
|
|
153
|
+
fs.chmodSync(destPath, 0o755);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error(
|
|
156
|
+
`agent-workspace-linux: chmod failed — ${err.message}. ` +
|
|
157
|
+
`Try: chmod 755 ${destPath}`
|
|
158
|
+
);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log(`agent-workspace-linux: binary installed at ${destPath}`);
|
|
163
|
+
})();
|