@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 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
+ };