@sponsoredai/cli 0.1.2 → 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 CHANGED
@@ -1,43 +1,70 @@
1
- # SAI npm launcher
1
+ # SAI CLI
2
2
 
3
- This package installs the `sai` command by downloading the platform binary from
4
- `https://downloads.sponsoredai.dev/sai`.
3
+ Install SAI from npm:
5
4
 
6
- The npm package is intentionally small. It contains a Node.js launcher and the
7
- installer script, not the Python source tree.
5
+ ```bash
6
+ npm install -g @sponsoredai/cli
7
+ ```
8
8
 
9
- ## Install
9
+ Then run your agent through SAI:
10
10
 
11
11
  ```bash
12
- npm install -g @sponsoredai/cli
13
- sai login
12
+ sai codex
14
13
  sai claude
15
14
  ```
16
15
 
17
- ## Binary layout
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
- The installer expects release assets at:
20
+ ## Common Commands
20
21
 
21
- ```text
22
- https://downloads.sponsoredai.dev/sai/v0.1.0/sai-darwin-arm64
23
- https://downloads.sponsoredai.dev/sai/v0.1.0/sai-darwin-arm64.sha256
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
- Supported names are:
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
- sai-darwin-arm64
30
- sai-linux-x64
31
- sai-win32-x64.exe
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
- The `.sha256` file must contain the hex SHA-256 digest. Standard
35
- `sha256sum`-style files are supported.
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
- ## Overrides
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
- SAI_BINARY_BASE_URL=https://downloads.example.com/sai npm install -g @sponsoredai/cli
41
- SAI_BINARY_VERSION=v0.1.0 npm install -g @sponsoredai/cli
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
- if (!fs.existsSync(binaryPath)) {
12
- console.error("SAI binary is not installed.");
13
- console.error("Reinstall the package, or run: npm rebuild @sponsoredai/cli");
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.2",
4
- "description": "SAI CLI launcher that downloads the platform binary.",
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
  }
@@ -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
- });
@@ -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
- };