openaihub 1.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 +27 -0
- package/bin/OAH.js +3 -0
- package/bin/openaihub.js +3 -0
- package/lib/config.js +41 -0
- package/lib/install.js +216 -0
- package/lib/run.js +35 -0
- package/package.json +28 -0
- package/scripts/postinstall.js +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# OpenAI Hub npm package
|
|
2
|
+
|
|
3
|
+
Install globally:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g openaihub
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Current support: Windows x64.
|
|
10
|
+
|
|
11
|
+
Runtime location after install:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
%USERPROFILE%/.openaihub/npm-runtime
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then run:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
openaihub
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
OAH
|
|
27
|
+
```
|
package/bin/OAH.js
ADDED
package/bin/openaihub.js
ADDED
package/lib/config.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
7
|
+
const runtimeRoot = path.join(os.homedir(), '.openaihub', 'npm-runtime');
|
|
8
|
+
|
|
9
|
+
const packageInfo = require(path.join(packageRoot, 'package.json'));
|
|
10
|
+
|
|
11
|
+
function getPlatformConfig() {
|
|
12
|
+
const platform = process.platform;
|
|
13
|
+
const arch = process.arch;
|
|
14
|
+
|
|
15
|
+
if (platform === 'win32' && arch === 'x64') {
|
|
16
|
+
return {
|
|
17
|
+
assetName: 'openaihub-windows.zip',
|
|
18
|
+
assetDownloadUrl: `https://github.com/gugezhanghao132-eng/openaihub/releases/download/${getTagName()}/openaihub-windows.zip`,
|
|
19
|
+
executableRelativePath: path.join('openaihub.exe'),
|
|
20
|
+
extractKind: 'zip',
|
|
21
|
+
runtimeKey: 'win32-x64'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error(`OpenAI Hub npm package does not support ${platform}-${arch} yet.`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getTagName() {
|
|
29
|
+
return `v${packageInfo.version}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
owner: 'gugezhanghao132-eng',
|
|
34
|
+
repo: 'openaihub',
|
|
35
|
+
packageName: packageInfo.name,
|
|
36
|
+
packageVersion: packageInfo.version,
|
|
37
|
+
packageRoot,
|
|
38
|
+
runtimeRoot,
|
|
39
|
+
getTagName,
|
|
40
|
+
getPlatformConfig
|
|
41
|
+
};
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const fsp = require('node:fs/promises');
|
|
5
|
+
const https = require('node:https');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
packageVersion,
|
|
12
|
+
runtimeRoot,
|
|
13
|
+
getPlatformConfig,
|
|
14
|
+
} = require('./config');
|
|
15
|
+
|
|
16
|
+
function log(message) {
|
|
17
|
+
process.stdout.write(`[OpenAI Hub] ${message}\n`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function downloadFile(url, targetPath, redirectsRemaining = 5) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const requestUrl = (currentUrl, redirectsLeft) => {
|
|
23
|
+
const req = https.get(currentUrl, {
|
|
24
|
+
headers: {
|
|
25
|
+
'User-Agent': 'OpenAIHub-npm-installer'
|
|
26
|
+
}
|
|
27
|
+
}, (res) => {
|
|
28
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
29
|
+
if (redirectsLeft <= 0) {
|
|
30
|
+
reject(new Error(`Too many redirects while downloading ${url}`));
|
|
31
|
+
res.resume();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
res.resume();
|
|
35
|
+
requestUrl(res.headers.location, redirectsLeft - 1);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (res.statusCode !== 200) {
|
|
40
|
+
reject(new Error(`Download failed: ${res.statusCode} ${currentUrl}`));
|
|
41
|
+
res.resume();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const file = fs.createWriteStream(targetPath);
|
|
46
|
+
res.pipe(file);
|
|
47
|
+
file.on('finish', () => {
|
|
48
|
+
file.close(resolve);
|
|
49
|
+
});
|
|
50
|
+
file.on('error', (error) => {
|
|
51
|
+
file.close(() => fs.rmSync(targetPath, { force: true }));
|
|
52
|
+
reject(error);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
req.setTimeout(30000, () => {
|
|
57
|
+
req.destroy(new Error(`Download timed out: ${currentUrl}`));
|
|
58
|
+
});
|
|
59
|
+
req.on('error', reject);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
requestUrl(url, redirectsRemaining);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function runCommand(command, args) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const child = spawn(command, args, {
|
|
69
|
+
stdio: 'inherit',
|
|
70
|
+
windowsHide: true
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
child.on('error', reject);
|
|
74
|
+
child.on('exit', (code) => {
|
|
75
|
+
if (code === 0) {
|
|
76
|
+
resolve();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
reject(new Error(`${command} exited with code ${code}`));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function withRetry(action, attempts, label) {
|
|
85
|
+
let lastError = null;
|
|
86
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
87
|
+
try {
|
|
88
|
+
return await action();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
lastError = error;
|
|
91
|
+
if (attempt < attempts) {
|
|
92
|
+
log(`${label} failed, retrying (${attempt}/${attempts - 1})...`);
|
|
93
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
throw lastError;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function acquireLock(lockPath, timeoutMs = 60000) {
|
|
101
|
+
const startedAt = Date.now();
|
|
102
|
+
while (true) {
|
|
103
|
+
try {
|
|
104
|
+
return await fsp.open(lockPath, 'wx');
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error && error.code === 'EEXIST') {
|
|
107
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
108
|
+
throw new Error(`Timed out waiting for install lock: ${lockPath}`);
|
|
109
|
+
}
|
|
110
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function extractArchive(archivePath, extractRoot, extractKind) {
|
|
119
|
+
if (extractKind === 'zip' && process.platform === 'win32') {
|
|
120
|
+
await runCommand('powershell.exe', [
|
|
121
|
+
'-NoProfile',
|
|
122
|
+
'-ExecutionPolicy',
|
|
123
|
+
'Bypass',
|
|
124
|
+
'-Command',
|
|
125
|
+
`Expand-Archive -Path '${archivePath.replace(/'/g, "''")}' -DestinationPath '${extractRoot.replace(/'/g, "''")}' -Force`
|
|
126
|
+
]);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
throw new Error(`Unsupported archive extraction for ${process.platform}: ${extractKind}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function findExecutable(extractRoot, executableName) {
|
|
134
|
+
const stack = [extractRoot];
|
|
135
|
+
while (stack.length > 0) {
|
|
136
|
+
const current = stack.pop();
|
|
137
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const fullPath = path.join(current, entry.name);
|
|
140
|
+
if (entry.isDirectory()) {
|
|
141
|
+
stack.push(fullPath);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (entry.isFile() && entry.name.toLowerCase() === executableName.toLowerCase()) {
|
|
145
|
+
return path.dirname(fullPath);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function ensureInstalled(options = {}) {
|
|
153
|
+
const { quiet = false, force = false } = options;
|
|
154
|
+
const platformConfig = getPlatformConfig();
|
|
155
|
+
const runtimeDir = path.join(runtimeRoot, platformConfig.runtimeKey);
|
|
156
|
+
const executablePath = path.join(runtimeDir, platformConfig.executableRelativePath);
|
|
157
|
+
|
|
158
|
+
if (!force && fs.existsSync(executablePath)) {
|
|
159
|
+
return { executablePath, runtimeDir, downloaded: false };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'openaihub-npm-'));
|
|
163
|
+
const archivePath = path.join(tempRoot, platformConfig.assetName);
|
|
164
|
+
const extractRoot = path.join(tempRoot, 'extract');
|
|
165
|
+
const stageRoot = path.join(tempRoot, 'stage');
|
|
166
|
+
const lockPath = path.join(runtimeRoot, `${platformConfig.runtimeKey}.lock`);
|
|
167
|
+
let lockHandle = null;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await fsp.mkdir(runtimeRoot, { recursive: true });
|
|
171
|
+
lockHandle = await acquireLock(lockPath);
|
|
172
|
+
|
|
173
|
+
if (!quiet) {
|
|
174
|
+
log(`Preparing OpenAI Hub ${packageVersion} for ${platformConfig.runtimeKey}...`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await fsp.mkdir(extractRoot, { recursive: true });
|
|
178
|
+
await fsp.mkdir(stageRoot, { recursive: true });
|
|
179
|
+
|
|
180
|
+
if (!quiet) {
|
|
181
|
+
log(`Downloading ${platformConfig.assetName}...`);
|
|
182
|
+
}
|
|
183
|
+
await withRetry(() => downloadFile(platformConfig.assetDownloadUrl, archivePath), 3, 'Runtime download');
|
|
184
|
+
|
|
185
|
+
if (!quiet) {
|
|
186
|
+
log('Extracting runtime...');
|
|
187
|
+
}
|
|
188
|
+
await extractArchive(archivePath, extractRoot, platformConfig.extractKind);
|
|
189
|
+
|
|
190
|
+
const extractedRuntimeRoot = findExecutable(extractRoot, path.basename(platformConfig.executableRelativePath));
|
|
191
|
+
if (!extractedRuntimeRoot) {
|
|
192
|
+
throw new Error(`Executable ${platformConfig.executableRelativePath} was not found in downloaded asset.`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await fsp.cp(extractedRuntimeRoot, stageRoot, { recursive: true, force: true });
|
|
196
|
+
await fsp.rm(runtimeDir, { recursive: true, force: true });
|
|
197
|
+
await fsp.mkdir(runtimeRoot, { recursive: true });
|
|
198
|
+
await fsp.rename(stageRoot, runtimeDir);
|
|
199
|
+
|
|
200
|
+
if (!quiet) {
|
|
201
|
+
log('Runtime ready.');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { executablePath, runtimeDir, downloaded: true };
|
|
205
|
+
} finally {
|
|
206
|
+
if (lockHandle) {
|
|
207
|
+
await lockHandle.close();
|
|
208
|
+
}
|
|
209
|
+
await fsp.rm(lockPath, { force: true });
|
|
210
|
+
await fsp.rm(tempRoot, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
ensureInstalled
|
|
216
|
+
};
|
package/lib/run.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
|
|
5
|
+
const { ensureInstalled } = require('./install');
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
try {
|
|
9
|
+
const { executablePath } = await ensureInstalled({ quiet: true });
|
|
10
|
+
const child = spawn(executablePath, process.argv.slice(2), {
|
|
11
|
+
stdio: 'inherit',
|
|
12
|
+
windowsHide: false
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
child.on('exit', (code, signal) => {
|
|
16
|
+
if (signal) {
|
|
17
|
+
process.kill(process.pid, signal);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
process.exit(code === null ? 1 : code);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
child.on('error', (error) => {
|
|
24
|
+
process.stderr.write(`[OpenAI Hub] Failed to launch runtime: ${error.message}\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
process.stderr.write(`[OpenAI Hub] ${error.message}\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
main
|
|
35
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openaihub",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "OpenAI Hub CLI installer and launcher",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"os": [
|
|
7
|
+
"win32"
|
|
8
|
+
],
|
|
9
|
+
"cpu": [
|
|
10
|
+
"x64"
|
|
11
|
+
],
|
|
12
|
+
"bin": {
|
|
13
|
+
"openaihub": "bin/openaihub.js",
|
|
14
|
+
"OAH": "bin/OAH.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"postinstall": "node scripts/postinstall.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin",
|
|
21
|
+
"lib",
|
|
22
|
+
"scripts",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ensureInstalled } = require('../lib/install');
|
|
4
|
+
|
|
5
|
+
ensureInstalled()
|
|
6
|
+
.catch((error) => {
|
|
7
|
+
process.stderr.write(`[OpenAI Hub] postinstall warning: ${error.message}\n`);
|
|
8
|
+
process.stderr.write('[OpenAI Hub] The runtime will be downloaded automatically on first launch.\n');
|
|
9
|
+
});
|