@sponsoredai/cli 0.1.1 → 0.1.5
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 +50 -23
- package/bin/sai.js +49 -4
- package/package.json +7 -9
- package/scripts/install.js +0 -108
- package/scripts/platform.js +0 -24
package/README.md
CHANGED
|
@@ -1,43 +1,70 @@
|
|
|
1
|
-
# SAI
|
|
1
|
+
# SAI CLI
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`https://downloads.sponsoredai.dev/sai`.
|
|
3
|
+
Install SAI from npm:
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g @sponsoredai/cli
|
|
7
|
+
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Then run your agent through SAI:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
|
|
13
|
-
sai login
|
|
12
|
+
sai codex
|
|
14
13
|
sai claude
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
You do not need to run `sai login` first. SAI creates its local install state
|
|
17
|
+
when it is needed. `sai login` is only useful if you explicitly want to print or
|
|
18
|
+
refresh the local SAI API key used by the local gateway/dashboard flow.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
## Common Commands
|
|
20
21
|
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
```bash
|
|
23
|
+
sai codex
|
|
24
|
+
sai claude
|
|
25
|
+
sai run -- npm test
|
|
26
|
+
sai wallet
|
|
27
|
+
sai config show
|
|
28
|
+
sai --help
|
|
24
29
|
```
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
## How The npm Package Works
|
|
32
|
+
|
|
33
|
+
`@sponsoredai/cli` is a small launcher. npm also installs one matching optional
|
|
34
|
+
binary package for your machine:
|
|
27
35
|
|
|
28
36
|
```text
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
macOS arm64 -> @sponsoredai/cli-darwin-arm64
|
|
38
|
+
Linux x64 -> @sponsoredai/cli-linux-x64
|
|
39
|
+
Windows x64 -> @sponsoredai/cli-win32-x64
|
|
32
40
|
```
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
When you run `sai`, the launcher finds that platform package and executes the
|
|
43
|
+
bundled binary. There is no separate binary download during install.
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
- Node.js 18 or newer.
|
|
48
|
+
- npm optional dependencies enabled.
|
|
49
|
+
- macOS arm64, Linux x64, or Windows x64.
|
|
50
|
+
|
|
51
|
+
Avoid installing with `--omit=optional` or `--no-optional`, because that skips
|
|
52
|
+
the platform binary package.
|
|
53
|
+
|
|
54
|
+
## Troubleshooting
|
|
55
|
+
|
|
56
|
+
If `sai` says `missing optional dependency`, reinstall normally:
|
|
38
57
|
|
|
39
58
|
```bash
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
SAI_SKIP_BINARY_DOWNLOAD=1 npm install -g @sponsoredai/cli
|
|
59
|
+
npm uninstall -g @sponsoredai/cli
|
|
60
|
+
npm install -g @sponsoredai/cli
|
|
43
61
|
```
|
|
62
|
+
|
|
63
|
+
If it still fails, check that npm is not omitting optional dependencies:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm config get optional
|
|
67
|
+
npm config get omit
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`optional` should not be `false`, and `omit` should not include `optional`.
|
package/bin/sai.js
CHANGED
|
@@ -5,12 +5,57 @@ const fs = require("fs");
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const { spawn } = require("child_process");
|
|
7
7
|
|
|
8
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
8
9
|
const binaryName = process.platform === "win32" ? "sai.exe" : "sai";
|
|
9
|
-
const binaryPath = path.join(__dirname, "..", "vendor", binaryName);
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const PLATFORM_PACKAGES = new Map([
|
|
12
|
+
["darwin-arm64", "@sponsoredai/cli-darwin-arm64"],
|
|
13
|
+
["linux-x64", "@sponsoredai/cli-linux-x64"],
|
|
14
|
+
["win32-x64", "@sponsoredai/cli-win32-x64"]
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function platformKey(platform = process.platform, arch = process.arch) {
|
|
18
|
+
return `${platform}-${arch}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolvePlatformBinary() {
|
|
22
|
+
const key = platformKey();
|
|
23
|
+
const packageName = PLATFORM_PACKAGES.get(key);
|
|
24
|
+
if (!packageName) {
|
|
25
|
+
throw new Error(`unsupported platform ${process.platform}/${process.arch}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const packageJson = require.resolve(`${packageName}/package.json`, {
|
|
30
|
+
paths: [packageRoot]
|
|
31
|
+
});
|
|
32
|
+
const packageDir = path.dirname(packageJson);
|
|
33
|
+
const candidate = path.join(packageDir, "bin", binaryName);
|
|
34
|
+
if (fs.existsSync(candidate)) {
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`${packageName} is installed but ${path.relative(packageDir, candidate)} is missing`);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error && error.code !== "MODULE_NOT_FOUND") {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const vendorFallback = path.join(packageRoot, "vendor", binaryName);
|
|
45
|
+
if (fs.existsSync(vendorFallback)) {
|
|
46
|
+
return vendorFallback;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new Error(
|
|
50
|
+
`missing optional dependency ${packageName}. Reinstall @sponsoredai/cli without --omit=optional.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let binaryPath;
|
|
55
|
+
try {
|
|
56
|
+
binaryPath = resolvePlatformBinary();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(`SAI binary is not installed: ${error.message}`);
|
|
14
59
|
process.exit(1);
|
|
15
60
|
}
|
|
16
61
|
|
package/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sponsoredai/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "SAI CLI launcher
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "SAI CLI launcher.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://sponsoredai.dev",
|
|
7
7
|
"bin": {
|
|
8
8
|
"sai": "bin/sai.js"
|
|
9
9
|
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"postinstall": "node scripts/install.js"
|
|
12
|
-
},
|
|
13
10
|
"files": [
|
|
14
11
|
"bin/",
|
|
15
|
-
"scripts/",
|
|
16
12
|
"README.md"
|
|
17
13
|
],
|
|
14
|
+
"optionalDependencies": {
|
|
15
|
+
"@sponsoredai/cli-darwin-arm64": "0.1.5",
|
|
16
|
+
"@sponsoredai/cli-linux-x64": "0.1.5",
|
|
17
|
+
"@sponsoredai/cli-win32-x64": "0.1.5"
|
|
18
|
+
},
|
|
18
19
|
"os": [
|
|
19
20
|
"darwin",
|
|
20
21
|
"linux",
|
|
@@ -29,8 +30,5 @@
|
|
|
29
30
|
},
|
|
30
31
|
"publishConfig": {
|
|
31
32
|
"access": "public"
|
|
32
|
-
},
|
|
33
|
-
"sai": {
|
|
34
|
-
"binaryBaseUrl": "https://downloads.sponsoredai.dev/sai"
|
|
35
33
|
}
|
|
36
34
|
}
|
package/scripts/install.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const crypto = require("crypto");
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
const http = require("http");
|
|
7
|
-
const https = require("https");
|
|
8
|
-
const path = require("path");
|
|
9
|
-
|
|
10
|
-
const { binaryFileName, targetTriple } = require("./platform");
|
|
11
|
-
|
|
12
|
-
const packageRoot = path.resolve(__dirname, "..");
|
|
13
|
-
const pkg = require(path.join(packageRoot, "package.json"));
|
|
14
|
-
const vendorDir = path.join(packageRoot, "vendor");
|
|
15
|
-
const installedName = process.platform === "win32" ? "sai.exe" : "sai";
|
|
16
|
-
const installedPath = path.join(vendorDir, installedName);
|
|
17
|
-
|
|
18
|
-
function info(message) {
|
|
19
|
-
console.log(`[sai] ${message}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function fail(message) {
|
|
23
|
-
console.error(`[sai] ${message}`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function request(url, redirectCount = 0) {
|
|
28
|
-
return new Promise((resolve, reject) => {
|
|
29
|
-
const client = url.startsWith("https:") ? https : http;
|
|
30
|
-
const req = client.get(url, (res) => {
|
|
31
|
-
const status = res.statusCode || 0;
|
|
32
|
-
if (status >= 300 && status < 400 && res.headers.location) {
|
|
33
|
-
if (redirectCount >= 5) {
|
|
34
|
-
reject(new Error(`too many redirects while downloading ${url}`));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const nextUrl = new URL(res.headers.location, url).toString();
|
|
38
|
-
res.resume();
|
|
39
|
-
resolve(request(nextUrl, redirectCount + 1));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (status !== 200) {
|
|
43
|
-
res.resume();
|
|
44
|
-
reject(new Error(`download failed with HTTP ${status}: ${url}`));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const chunks = [];
|
|
49
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
50
|
-
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
51
|
-
});
|
|
52
|
-
req.on("error", reject);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function parseChecksum(text, fileName) {
|
|
57
|
-
const lines = text.split(/\r?\n/).filter(Boolean);
|
|
58
|
-
for (const line of lines) {
|
|
59
|
-
const parts = line.trim().split(/\s+/);
|
|
60
|
-
if (parts.length >= 1 && /^[a-f0-9]{64}$/i.test(parts[0])) {
|
|
61
|
-
if (parts.length === 1 || parts.some((part) => part.endsWith(fileName))) {
|
|
62
|
-
return parts[0].toLowerCase();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
fail(`invalid checksum file for ${fileName}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function main() {
|
|
70
|
-
if (process.env.SAI_SKIP_BINARY_DOWNLOAD === "1") {
|
|
71
|
-
info("Skipping binary download because SAI_SKIP_BINARY_DOWNLOAD=1");
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const target = targetTriple();
|
|
76
|
-
if (!target) {
|
|
77
|
-
fail(`unsupported platform ${process.platform}/${process.arch}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const fileName = binaryFileName(target);
|
|
81
|
-
const baseUrl = (process.env.SAI_BINARY_BASE_URL || pkg.sai.binaryBaseUrl).replace(/\/+$/, "");
|
|
82
|
-
const version = process.env.SAI_BINARY_VERSION || `v${pkg.version}`;
|
|
83
|
-
const binaryUrl = `${baseUrl}/${version}/${fileName}`;
|
|
84
|
-
const checksumUrl = `${binaryUrl}.sha256`;
|
|
85
|
-
|
|
86
|
-
info(`Downloading ${fileName}`);
|
|
87
|
-
const [binary, checksumBuffer] = await Promise.all([
|
|
88
|
-
request(binaryUrl),
|
|
89
|
-
request(checksumUrl)
|
|
90
|
-
]);
|
|
91
|
-
|
|
92
|
-
const expected = parseChecksum(checksumBuffer.toString("utf8"), fileName);
|
|
93
|
-
const actual = crypto.createHash("sha256").update(binary).digest("hex");
|
|
94
|
-
if (actual !== expected) {
|
|
95
|
-
fail(`checksum mismatch for ${fileName}`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
fs.mkdirSync(vendorDir, { recursive: true });
|
|
99
|
-
fs.writeFileSync(installedPath, binary);
|
|
100
|
-
if (process.platform !== "win32") {
|
|
101
|
-
fs.chmodSync(installedPath, 0o755);
|
|
102
|
-
}
|
|
103
|
-
info(`Installed ${installedName}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
main().catch((error) => {
|
|
107
|
-
fail(error.message);
|
|
108
|
-
});
|
package/scripts/platform.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const TARGETS = new Set([
|
|
4
|
-
"darwin-arm64",
|
|
5
|
-
"linux-x64",
|
|
6
|
-
"win32-x64"
|
|
7
|
-
]);
|
|
8
|
-
|
|
9
|
-
function targetTriple(platform = process.platform, arch = process.arch) {
|
|
10
|
-
if (!TARGETS.has(`${platform}-${arch}`)) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
return { platform, arch };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function binaryFileName(target) {
|
|
17
|
-
const ext = target.platform === "win32" ? ".exe" : "";
|
|
18
|
-
return `sai-${target.platform}-${target.arch}${ext}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
module.exports = {
|
|
22
|
-
binaryFileName,
|
|
23
|
-
targetTriple
|
|
24
|
-
};
|