openswarmlayer 0.1.1-beta.2
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 +54 -0
- package/bin/openswarmlayer.cjs +7 -0
- package/lib/install.cjs +156 -0
- package/package.json +18 -0
- package/release.json +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stephen Phillips
|
|
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
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# OpenSwarmLayer npm installer
|
|
2
|
+
|
|
3
|
+
**Technical test beta — not ready for production.** This small, dependency-free
|
|
4
|
+
package installs the native Rust/Tauri model-sharing app. Node.js does not run
|
|
5
|
+
the model-sharing engine. Requires Node.js 20 or newer.
|
|
6
|
+
|
|
7
|
+
Once published to npm under the `beta` dist-tag:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npx openswarmlayer@beta install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Registry publication is pending authenticated maintainer access. Until then,
|
|
14
|
+
use the npm tarball on the GitHub beta release:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npx --package=https://github.com/HappyMonkeyAI/OpenSwarmLayer/releases/download/v0.1.1-beta.1/openswarmlayer-0.1.1-beta.2.tgz openswarmlayer install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Replace `install` with `download` to fetch without installing, or with `--help`
|
|
21
|
+
to inspect supported options. On Linux, add `--format appimage` after `download`
|
|
22
|
+
for the portable package. This URL points to a fixed wrapper version.
|
|
23
|
+
|
|
24
|
+
Windows x64 opens the normal unsigned installer. Debian/Ubuntu-family Linux x64
|
|
25
|
+
uses apt/sudo and requires glibc 2.39 or newer. Close the app before upgrading.
|
|
26
|
+
macOS, ARM and musl systems are unsupported. Full Linux desktop, tray and sharing
|
|
27
|
+
acceptance is incomplete.
|
|
28
|
+
|
|
29
|
+
Download without installing:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npx openswarmlayer@beta download
|
|
33
|
+
npx openswarmlayer@beta download --format appimage
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
AppImage download is Linux-only. Make the downloaded file executable with
|
|
37
|
+
`chmod +x <file>` and run it; use `--appimage-extract-and-run` if FUSE is unavailable.
|
|
38
|
+
No native application is installed by `npm install` or the help command. There
|
|
39
|
+
are no installation lifecycle hooks. Installation is an explicit CLI action.
|
|
40
|
+
|
|
41
|
+
Each npm version pins a native GitHub release, exact sizes and SHA-256 hashes.
|
|
42
|
+
Files are downloaded to a temporary directory and checked before an installer
|
|
43
|
+
can run. Hashes protect against mismatched release assets; trusting this npm
|
|
44
|
+
package and its publisher remains necessary. The wrapper does not configure
|
|
45
|
+
firewalls, model caches, signing keys, services or automatic application launch.
|
|
46
|
+
|
|
47
|
+
Current native release: `v0.1.1-beta.1`, source
|
|
48
|
+
`dec8e5099e782c9ddd6be75e8fecb3d65907989f`. The npm wrapper may be versioned
|
|
49
|
+
independently; it does not rebuild the native app. Publish beta builds with
|
|
50
|
+
`npm publish --tag beta`, keeping the stable `latest` channel unset.
|
|
51
|
+
|
|
52
|
+
See the [project README](https://github.com/HappyMonkeyAI/OpenSwarmLayer#readme)
|
|
53
|
+
for importing, downloading and re-sharing GGUF/Safetensors models. Software
|
|
54
|
+
license is MIT; model files retain their own licenses.
|
package/lib/install.cjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs/promises');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
const { spawn } = require('node:child_process');
|
|
7
|
+
const release = require('../release.json');
|
|
8
|
+
|
|
9
|
+
const HELP = `OpenSwarmLayer ${release.tag} - technical test beta; not ready for production.
|
|
10
|
+
|
|
11
|
+
Usage: openswarmlayer install [--format exe|deb]
|
|
12
|
+
openswarmlayer download [--format exe|deb|appimage]
|
|
13
|
+
openswarmlayer --help
|
|
14
|
+
|
|
15
|
+
Windows x64 defaults to the normal unsigned EXE installer.
|
|
16
|
+
Linux x64 defaults to DEB with apt/sudo; installation requires glibc 2.39+.
|
|
17
|
+
AppImage is download-only; make it executable and run it yourself.
|
|
18
|
+
No app is installed when you merely install this npm package or request help.
|
|
19
|
+
The wrapper requires Node.js 20+ and downloads pinned native GitHub release files.
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
function parseArgs(args) {
|
|
23
|
+
if (!args.length || (args.length === 1 && ['--help', '-h'].includes(args[0]))) return { action: 'help' };
|
|
24
|
+
if (!['install', 'download'].includes(args[0])) throw new Error('Choose install or download. Use --help for instructions.');
|
|
25
|
+
let format;
|
|
26
|
+
for (let i = 1; i < args.length; i++) {
|
|
27
|
+
if (args[i] !== '--format' || format || !['exe', 'deb', 'appimage'].includes(args[i + 1])) {
|
|
28
|
+
throw new Error('Unknown or incomplete argument. Use --help for instructions.');
|
|
29
|
+
}
|
|
30
|
+
format = args[++i];
|
|
31
|
+
}
|
|
32
|
+
if (args[0] === 'install' && format === 'appimage') throw new Error('Use download --format appimage, then run the portable app manually.');
|
|
33
|
+
return { action: args[0], format };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function selectAsset(format, platform = process.platform, arch = process.arch) {
|
|
37
|
+
if (arch !== 'x64' || !['win32', 'linux'].includes(platform)) throw new Error('Only Windows x64 and Linux x64 releases are available.');
|
|
38
|
+
format = format || (platform === 'win32' ? 'exe' : 'deb');
|
|
39
|
+
const asset = release.assets[format];
|
|
40
|
+
if (!asset || asset.platform !== platform) throw new Error('That package format is not available for this operating system.');
|
|
41
|
+
return asset;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function checkLinuxInstall(runtimeGlibc) {
|
|
45
|
+
if (!/^\d+\.\d+$/.test(runtimeGlibc || '')) throw new Error('Linux installation requires a glibc system (2.39+); musl is unsupported.');
|
|
46
|
+
const [major, minor] = runtimeGlibc.split('.').map(Number);
|
|
47
|
+
if (major < 2 || (major === 2 && minor < 39)) throw new Error(`This native beta requires glibc 2.39+, but this system has ${runtimeGlibc}.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function checkedUrl(value) {
|
|
51
|
+
const url = new URL(value);
|
|
52
|
+
const allowedHosts = ['github.com', 'release-assets.githubusercontent.com', 'objects.githubusercontent.com'];
|
|
53
|
+
if (url.protocol !== 'https:' || url.username || url.password || (url.port && url.port !== '443') || !allowedHosts.includes(url.hostname)) {
|
|
54
|
+
throw new Error('Release download redirected outside the allowed HTTPS hosts.');
|
|
55
|
+
}
|
|
56
|
+
return url;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function releaseResponse(url, fetchImpl = fetch) {
|
|
60
|
+
for (let redirects = 0; redirects <= 5; redirects++) {
|
|
61
|
+
const current = checkedUrl(url);
|
|
62
|
+
const response = await fetchImpl(current, {
|
|
63
|
+
redirect: 'manual', signal: AbortSignal.timeout(600000),
|
|
64
|
+
headers: { 'User-Agent': 'OpenSwarmLayer-beta-installer', 'Accept-Encoding': 'identity' },
|
|
65
|
+
});
|
|
66
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
67
|
+
const location = response.headers.get('location');
|
|
68
|
+
if (response.body) await response.body.cancel();
|
|
69
|
+
if (!location) throw new Error('Release redirect has no destination.');
|
|
70
|
+
url = new URL(location, current).href;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!response.ok) { if (response.body) await response.body.cancel(); throw new Error(`Release download returned HTTP ${response.status}.`); }
|
|
74
|
+
return response;
|
|
75
|
+
}
|
|
76
|
+
throw new Error('Too many release download redirects.');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function downloadAsset(asset, { fetchImpl = fetch, tempRoot = os.tmpdir() } = {}) {
|
|
80
|
+
if (!/^OpenSwarmLayer-[A-Za-z0-9._-]+$/.test(asset.name) || !/^[a-f0-9]{64}$/.test(asset.sha256) || !Number.isSafeInteger(asset.size) || asset.size <= 0 || asset.size > 256 * 1024 * 1024) {
|
|
81
|
+
throw new Error('Invalid pinned release metadata.');
|
|
82
|
+
}
|
|
83
|
+
const folder = await fs.mkdtemp(path.join(tempRoot, 'OpenSwarmLayer-npx-'));
|
|
84
|
+
const partial = path.join(folder, `${asset.name}.partial`);
|
|
85
|
+
const output = path.join(folder, asset.name);
|
|
86
|
+
let file;
|
|
87
|
+
try {
|
|
88
|
+
const url = `https://github.com/HappyMonkeyAI/OpenSwarmLayer/releases/download/${release.tag}/${asset.name}`;
|
|
89
|
+
const response = await releaseResponse(url, fetchImpl);
|
|
90
|
+
if (!response.body) throw new Error('Release download has no body.');
|
|
91
|
+
file = await fs.open(partial, 'wx', 0o600);
|
|
92
|
+
const digest = crypto.createHash('sha256');
|
|
93
|
+
let size = 0;
|
|
94
|
+
for await (const bytes of response.body) {
|
|
95
|
+
size += bytes.length;
|
|
96
|
+
if (size > asset.size) throw new Error('Release download exceeds its pinned size.');
|
|
97
|
+
digest.update(bytes);
|
|
98
|
+
// FileHandle.write can write fewer bytes than requested.
|
|
99
|
+
let offset = 0;
|
|
100
|
+
while (offset < bytes.length) {
|
|
101
|
+
const written = await file.write(bytes, offset, bytes.length - offset);
|
|
102
|
+
if (!written.bytesWritten) throw new Error('Could not write the downloaded package.');
|
|
103
|
+
offset += written.bytesWritten;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (size !== asset.size || digest.digest('hex') !== asset.sha256) throw new Error('Release size/checksum mismatch; installation refused.');
|
|
107
|
+
await file.close(); file = undefined;
|
|
108
|
+
await fs.rename(partial, output);
|
|
109
|
+
return { file: output, folder };
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (file) await file.close();
|
|
112
|
+
await fs.unlink(partial).catch(() => {});
|
|
113
|
+
await fs.rmdir(folder).catch(() => {});
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function runProcess(command, args) {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const child = spawn(command, args, { stdio: 'inherit', shell: false });
|
|
121
|
+
child.once('error', reject);
|
|
122
|
+
child.once('close', (code, signal) => code === 0 ? resolve() : reject(new Error(`Installer exited ${signal || code}.`)));
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function main(args, dependencies = {}) {
|
|
127
|
+
const options = parseArgs(args);
|
|
128
|
+
const log = dependencies.log || console.log;
|
|
129
|
+
if (options.action === 'help') { log(HELP); return; }
|
|
130
|
+
const platform = dependencies.platform || process.platform;
|
|
131
|
+
const asset = selectAsset(options.format, platform, dependencies.arch || process.arch);
|
|
132
|
+
if (options.action === 'install' && platform === 'linux') {
|
|
133
|
+
checkLinuxInstall(dependencies.glibc || process.report.getReport().header.glibcVersionRuntime);
|
|
134
|
+
}
|
|
135
|
+
log(`OpenSwarmLayer ${release.tag} - technical test beta; not ready for production.`);
|
|
136
|
+
log(platform === 'win32' ? 'Unsigned Windows package. Close the app before installing.' : 'Experimental Linux package; requires glibc 2.39+. Native desktop acceptance remains incomplete.');
|
|
137
|
+
const downloaded = await (dependencies.download || downloadAsset)(asset);
|
|
138
|
+
log(`SHA-256 verified: ${asset.name}`);
|
|
139
|
+
if (options.action === 'download') { log(`Verified package saved to ${downloaded.file}`); return downloaded.file; }
|
|
140
|
+
try {
|
|
141
|
+
const run = dependencies.run || runProcess;
|
|
142
|
+
if (platform === 'win32') await run(downloaded.file, []);
|
|
143
|
+
else {
|
|
144
|
+
await fs.chmod(downloaded.folder, 0o755);
|
|
145
|
+
await fs.chmod(downloaded.file, 0o644);
|
|
146
|
+
const root = process.getuid && process.getuid() === 0;
|
|
147
|
+
await run(root ? 'apt-get' : 'sudo', root ? ['install', downloaded.file] : ['apt-get', 'install', downloaded.file]);
|
|
148
|
+
}
|
|
149
|
+
log('Installer finished. Launch OpenSwarmLayer to begin your test.');
|
|
150
|
+
} finally {
|
|
151
|
+
await fs.unlink(downloaded.file);
|
|
152
|
+
await fs.rmdir(downloaded.folder);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { main, parseArgs, selectAsset, checkLinuxInstall, checkedUrl, releaseResponse, downloadAsset };
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openswarmlayer",
|
|
3
|
+
"version": "0.1.1-beta.2",
|
|
4
|
+
"description": "Install the OpenSwarmLayer native model-sharing app. Technical test beta; not ready for production.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": { "node": ">=20" },
|
|
7
|
+
"bin": { "openswarmlayer": "bin/openswarmlayer.cjs" },
|
|
8
|
+
"files": ["bin", "lib", "release.json", "README.md", "LICENSE"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test test/*.test.cjs",
|
|
11
|
+
"prepublishOnly": "npm test"
|
|
12
|
+
},
|
|
13
|
+
"repository": { "type": "git", "url": "git+https://github.com/HappyMonkeyAI/OpenSwarmLayer.git", "directory": "npm" },
|
|
14
|
+
"homepage": "https://github.com/HappyMonkeyAI/OpenSwarmLayer#readme",
|
|
15
|
+
"bugs": { "url": "https://github.com/HappyMonkeyAI/OpenSwarmLayer/issues" },
|
|
16
|
+
"keywords": ["llm", "gguf", "safetensors", "p2p", "tauri", "beta"],
|
|
17
|
+
"publishConfig": { "access": "public", "tag": "beta", "registry": "https://registry.npmjs.org/" }
|
|
18
|
+
}
|
package/release.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tag": "v0.1.1-beta.1",
|
|
3
|
+
"sourceCommit": "dec8e5099e782c9ddd6be75e8fecb3d65907989f",
|
|
4
|
+
"assets": {
|
|
5
|
+
"exe": {
|
|
6
|
+
"platform": "win32",
|
|
7
|
+
"name": "OpenSwarmLayer-v0.1.1-beta.1-windows-x64-setup.exe",
|
|
8
|
+
"size": 5678246,
|
|
9
|
+
"sha256": "880bf10491259d2568f43b203bd6611524d06239922cf7daab41e693c0f94e3d"
|
|
10
|
+
},
|
|
11
|
+
"deb": {
|
|
12
|
+
"platform": "linux",
|
|
13
|
+
"name": "OpenSwarmLayer-v0.1.1-beta.1-linux-amd64.deb",
|
|
14
|
+
"size": 11145498,
|
|
15
|
+
"sha256": "1cc92bd27bd285581633cf5a60be31aa692bcf9e10a24b2848662e7c2fa999c9"
|
|
16
|
+
},
|
|
17
|
+
"appimage": {
|
|
18
|
+
"platform": "linux",
|
|
19
|
+
"name": "OpenSwarmLayer-v0.1.1-beta.1-linux-x86_64.AppImage",
|
|
20
|
+
"size": 85273080,
|
|
21
|
+
"sha256": "a6be8295b1b11f17d8f0f750a45eebc1e384ba26623cb92727bb72650cf62ac6"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|