@helmoragent/helmor 0.1.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 +21 -0
- package/bin/helmor.js +51 -0
- package/package.json +49 -0
- package/scripts/install.js +167 -0
- package/scripts/lib.js +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# HELMOR Agent OS
|
|
2
|
+
|
|
3
|
+
Npm installer for the native `helmor` CLI.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i -g @helmoragent/helmor
|
|
7
|
+
helmor install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
One-off usage without a global install:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx @helmoragent/helmor@latest install
|
|
14
|
+
pnpm dlx @helmoragent/helmor install
|
|
15
|
+
yarn dlx @helmoragent/helmor install
|
|
16
|
+
bunx @helmoragent/helmor install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This package downloads the matching HELMOR release binary from GitHub, verifies it against the published SHA-256 checksum file, and exposes the `helmor` command.
|
|
20
|
+
|
|
21
|
+
Set `HELMOR_VERSION`, `HELMOR_REPO`, or `HELMOR_NPM_QUIET=1` to override the release tag, source repository, or install logging.
|
package/bin/helmor.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawn, spawnSync } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
9
|
+
const executable = process.platform === 'win32' ? 'helmor.exe' : 'helmor';
|
|
10
|
+
const binaryPath = path.join(packageRoot, 'vendor', executable);
|
|
11
|
+
|
|
12
|
+
function installIfMissing() {
|
|
13
|
+
if (fs.existsSync(binaryPath)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const installer = path.join(packageRoot, 'scripts', 'install.js');
|
|
18
|
+
const result = spawnSync(process.execPath, [installer], {
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
env: process.env
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (result.error) {
|
|
24
|
+
console.error(`helmor: failed to run installer: ${result.error.message}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
process.exit(result.status || 1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
installIfMissing();
|
|
34
|
+
|
|
35
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
36
|
+
stdio: 'inherit'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
child.on('error', (err) => {
|
|
40
|
+
console.error(`helmor: failed to run native binary: ${err.message}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on('exit', (code, signal) => {
|
|
45
|
+
if (signal) {
|
|
46
|
+
process.kill(process.pid, signal);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
process.exit(code == null ? 1 : code);
|
|
51
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@helmoragent/helmor",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Agent watcher for AI-assisted product development",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://github.com/helmorx/agent-os",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/helmorx/agent-os.git",
|
|
10
|
+
"directory": "npm/helmor"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/helmorx/agent-os/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai",
|
|
17
|
+
"agent",
|
|
18
|
+
"cli",
|
|
19
|
+
"codex",
|
|
20
|
+
"claude",
|
|
21
|
+
"cursor",
|
|
22
|
+
"windsurf"
|
|
23
|
+
],
|
|
24
|
+
"bin": {
|
|
25
|
+
"helmor": "bin/helmor.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"bin",
|
|
29
|
+
"scripts",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"os": [
|
|
36
|
+
"darwin",
|
|
37
|
+
"linux",
|
|
38
|
+
"win32"
|
|
39
|
+
],
|
|
40
|
+
"cpu": [
|
|
41
|
+
"x64",
|
|
42
|
+
"arm64"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"postinstall": "node scripts/install.js",
|
|
46
|
+
"test": "node --test test/*.test.js",
|
|
47
|
+
"pack:dry": "npm pack --dry-run --json"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const crypto = require('node:crypto');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const https = require('node:https');
|
|
7
|
+
const os = require('node:os');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const { spawnSync } = require('node:child_process');
|
|
10
|
+
|
|
11
|
+
const pkg = require('../package.json');
|
|
12
|
+
const {
|
|
13
|
+
checksumFor,
|
|
14
|
+
platformTarget,
|
|
15
|
+
releaseBaseUrl,
|
|
16
|
+
tagFromVersion
|
|
17
|
+
} = require('./lib');
|
|
18
|
+
|
|
19
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
20
|
+
const vendorDir = path.join(packageRoot, 'vendor');
|
|
21
|
+
|
|
22
|
+
function log(message) {
|
|
23
|
+
if (!process.env.HELMOR_NPM_QUIET) {
|
|
24
|
+
console.log(message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function downloadFile(url, destination, redirects = 0) {
|
|
29
|
+
if (redirects > 5) {
|
|
30
|
+
return Promise.reject(new Error(`too many redirects for ${url}`));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const request = https.get(url, {
|
|
35
|
+
headers: {
|
|
36
|
+
'user-agent': `helmor-npm/${pkg.version}`
|
|
37
|
+
}
|
|
38
|
+
}, (response) => {
|
|
39
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
40
|
+
response.resume();
|
|
41
|
+
const redirectUrl = new URL(response.headers.location, url).toString();
|
|
42
|
+
downloadFile(redirectUrl, destination, redirects + 1).then(resolve, reject);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (response.statusCode !== 200) {
|
|
47
|
+
response.resume();
|
|
48
|
+
reject(new Error(`download failed with HTTP ${response.statusCode}: ${url}`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const file = fs.createWriteStream(destination, { mode: 0o600 });
|
|
53
|
+
response.pipe(file);
|
|
54
|
+
|
|
55
|
+
file.on('finish', () => {
|
|
56
|
+
file.close(resolve);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
file.on('error', (err) => {
|
|
60
|
+
fs.rm(destination, { force: true }, () => reject(err));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
request.on('error', reject);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sha256(filePath) {
|
|
69
|
+
const hash = crypto.createHash('sha256');
|
|
70
|
+
hash.update(fs.readFileSync(filePath));
|
|
71
|
+
return hash.digest('hex');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function run(command, args) {
|
|
75
|
+
const result = spawnSync(command, args, {
|
|
76
|
+
stdio: 'inherit'
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (result.error) {
|
|
80
|
+
throw result.error;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (result.status !== 0) {
|
|
84
|
+
throw new Error(`${command} exited with status ${result.status}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function quotePowerShell(value) {
|
|
89
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function extractArchive(archivePath, destination, target) {
|
|
93
|
+
if (target.archiveExt === 'zip') {
|
|
94
|
+
const command = `Expand-Archive -LiteralPath ${quotePowerShell(archivePath)} -DestinationPath ${quotePowerShell(destination)} -Force`;
|
|
95
|
+
run('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command]);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
run('tar', ['-xzf', archivePath, '-C', destination]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function verifyInstalledVersion(binaryPath, expectedVersion) {
|
|
103
|
+
const result = spawnSync(binaryPath, ['version'], {
|
|
104
|
+
encoding: 'utf8'
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (result.error) {
|
|
108
|
+
throw result.error;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (result.status !== 0) {
|
|
112
|
+
throw new Error(`helmor version exited with status ${result.status}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const actualVersion = result.stdout.trim();
|
|
116
|
+
if (actualVersion !== expectedVersion) {
|
|
117
|
+
throw new Error(`expected helmor ${expectedVersion}, got ${actualVersion}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function main() {
|
|
122
|
+
const repo = process.env.HELMOR_REPO || 'helmorx/agent-os';
|
|
123
|
+
const tag = tagFromVersion(process.env.HELMOR_VERSION || pkg.version);
|
|
124
|
+
const target = platformTarget();
|
|
125
|
+
const baseUrl = releaseBaseUrl(repo, tag);
|
|
126
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'helmor-npm-'));
|
|
127
|
+
const archivePath = path.join(tmpDir, target.asset);
|
|
128
|
+
const checksumsPath = path.join(tmpDir, 'checksums.txt');
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
log(`Downloading ${target.asset} from ${repo}@${tag}`);
|
|
132
|
+
await downloadFile(`${baseUrl}/${target.asset}`, archivePath);
|
|
133
|
+
await downloadFile(`${baseUrl}/checksums.txt`, checksumsPath);
|
|
134
|
+
|
|
135
|
+
const expectedChecksum = checksumFor(fs.readFileSync(checksumsPath, 'utf8'), target.asset);
|
|
136
|
+
const actualChecksum = sha256(archivePath);
|
|
137
|
+
if (expectedChecksum !== actualChecksum) {
|
|
138
|
+
throw new Error(`checksum mismatch for ${target.asset}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
extractArchive(archivePath, tmpDir, target);
|
|
142
|
+
|
|
143
|
+
fs.rmSync(vendorDir, { recursive: true, force: true });
|
|
144
|
+
fs.mkdirSync(vendorDir, { recursive: true });
|
|
145
|
+
|
|
146
|
+
const sourceBinary = path.join(tmpDir, target.executable);
|
|
147
|
+
const installedBinary = path.join(vendorDir, target.executable);
|
|
148
|
+
fs.copyFileSync(sourceBinary, installedBinary);
|
|
149
|
+
|
|
150
|
+
if (process.platform !== 'win32') {
|
|
151
|
+
fs.chmodSync(installedBinary, 0o755);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (tag !== 'latest') {
|
|
155
|
+
verifyInstalledVersion(installedBinary, tag.replace(/^v/, ''));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
log(`Installed helmor to ${installedBinary}`);
|
|
159
|
+
} finally {
|
|
160
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
main().catch((err) => {
|
|
165
|
+
console.error(`helmor: ${err.message}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
});
|
package/scripts/lib.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SUPPORTED_PLATFORMS = {
|
|
4
|
+
darwin: 'darwin',
|
|
5
|
+
linux: 'linux',
|
|
6
|
+
win32: 'windows'
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function normalizeArch(arch) {
|
|
10
|
+
switch (arch) {
|
|
11
|
+
case 'arm64':
|
|
12
|
+
return 'arm64';
|
|
13
|
+
case 'x64':
|
|
14
|
+
return 'amd64';
|
|
15
|
+
default:
|
|
16
|
+
throw new Error(`unsupported architecture: ${arch}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function platformTarget(platform = process.platform, arch = process.arch) {
|
|
21
|
+
const goos = SUPPORTED_PLATFORMS[platform];
|
|
22
|
+
if (!goos) {
|
|
23
|
+
throw new Error(`unsupported platform: ${platform}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const goarch = normalizeArch(arch);
|
|
27
|
+
const archiveExt = platform === 'win32' ? 'zip' : 'tar.gz';
|
|
28
|
+
const executable = platform === 'win32' ? 'helmor.exe' : 'helmor';
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
goos,
|
|
32
|
+
goarch,
|
|
33
|
+
archiveExt,
|
|
34
|
+
executable,
|
|
35
|
+
asset: `helmor_${goos}_${goarch}.${archiveExt}`
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function tagFromVersion(version) {
|
|
40
|
+
if (!version) {
|
|
41
|
+
throw new Error('version is required');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (version === 'latest' || version.startsWith('v')) {
|
|
45
|
+
return version;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `v${version}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function releaseBaseUrl(repo, tag) {
|
|
52
|
+
if (!repo) {
|
|
53
|
+
throw new Error('repo is required');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (tag === 'latest') {
|
|
57
|
+
return `https://github.com/${repo}/releases/latest/download`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return `https://github.com/${repo}/releases/download/${tag}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function checksumFor(checksums, asset) {
|
|
64
|
+
for (const line of checksums.split(/\r?\n/)) {
|
|
65
|
+
const parts = line.trim().split(/\s+/);
|
|
66
|
+
if (parts.length >= 2 && parts[parts.length - 1] === asset) {
|
|
67
|
+
return parts[0].toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
throw new Error(`missing checksum for ${asset}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
checksumFor,
|
|
76
|
+
platformTarget,
|
|
77
|
+
releaseBaseUrl,
|
|
78
|
+
tagFromVersion
|
|
79
|
+
};
|