sasacode 0.9.7
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 +12 -0
- package/bin/sasacode.js +80 -0
- package/package.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# sasacode
|
|
2
|
+
|
|
3
|
+
A small, pluggable coding agent for the terminal. See the [project README](https://github.com/sasanokusa/sasacode#readme) and [the docs](https://sasanokusa.com/sasacode/docs/).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g sasacode
|
|
7
|
+
sasacode
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
This package is a launcher. On its first run it downloads the single binary of the same version for your machine (macOS arm64 / x64, Linux x64 / arm64, glibc or musl) from [GitHub Releases](https://github.com/sasanokusa/sasacode/releases), checks it against the release's `SHA256SUMS`, and keeps it in `~/.sasacode/npm-bin`. Later runs start it directly. Update with `npm install -g sasacode@latest`. Windows is supported through WSL.
|
|
11
|
+
|
|
12
|
+
Without npm: `curl -fsSL https://sasanokusa.com/sasacode/install.sh | sh`.
|
package/bin/sasacode.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// sasacode from npm: a launcher, not the program. The first run downloads the single binary of
|
|
3
|
+
// this package's version for this machine from GitHub Releases, checks it against the release's
|
|
4
|
+
// SHA256SUMS, keeps it under ~/.sasacode/npm-bin, and every run then starts it. The platform rules
|
|
5
|
+
// are the ones scripts/install.sh uses.
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { constants, homedir } from "node:os";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const version = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8")).version;
|
|
15
|
+
const base = (process.env.SASACODE_DOWNLOAD_BASE ?? "https://github.com/sasanokusa/sasacode/releases").replace(/\/$/, "");
|
|
16
|
+
|
|
17
|
+
function die(message) {
|
|
18
|
+
process.stderr.write(`sasacode (npm): ${message}\n`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function target() {
|
|
23
|
+
const os = { darwin: "darwin", linux: "linux" }[process.platform];
|
|
24
|
+
if (!os) die(process.platform === "win32" ? "Windows is supported through WSL: install sasacode inside a WSL shell" : `unsupported OS: ${process.platform}`);
|
|
25
|
+
let arch = { x64: "x64", arm64: "arm64" }[process.arch];
|
|
26
|
+
if (!arch) die(`unsupported CPU: ${process.arch}`);
|
|
27
|
+
// An x64 Node under Rosetta on Apple Silicon should still get the native build.
|
|
28
|
+
if (os === "darwin" && arch === "x64" && spawnSync("sysctl", ["-n", "sysctl.proc_translated"], { encoding: "utf8" }).stdout?.trim() === "1") arch = "arm64";
|
|
29
|
+
if (os === "linux") {
|
|
30
|
+
const musl = existsSync("/etc/alpine-release") || spawnSync("sh", ["-c", "ls /lib/ld-musl-*"], { stdio: "ignore" }).status === 0;
|
|
31
|
+
if (musl) return `${os}-${arch}-musl`;
|
|
32
|
+
if (arch === "x64" && !/\bavx2\b/.test(readSafe("/proc/cpuinfo"))) return `${os}-${arch}-baseline`; // CPUs without AVX2
|
|
33
|
+
}
|
|
34
|
+
return `${os}-${arch}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readSafe(path) {
|
|
38
|
+
try {
|
|
39
|
+
return readFileSync(path, "utf8");
|
|
40
|
+
} catch {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function download(url) {
|
|
46
|
+
const res = await fetch(url);
|
|
47
|
+
if (!res.ok) die(`download failed: ${url} (${res.status})`);
|
|
48
|
+
return Buffer.from(await res.arrayBuffer());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function install(name, dest) {
|
|
52
|
+
const url = `${base}/download/v${version}`;
|
|
53
|
+
process.stderr.write(`sasacode (npm): downloading sasacode v${version} (${name.replace(/^sasacode-/, "")}) from ${url}\n`);
|
|
54
|
+
const [archive, sums] = await Promise.all([download(`${url}/${name}.tar.gz`), download(`${url}/SHA256SUMS`)]);
|
|
55
|
+
const expected = sums.toString("utf8").split("\n").find((l) => l.endsWith(` ${name}.tar.gz`))?.split(/\s+/)[0];
|
|
56
|
+
if (!expected) die(`${name}.tar.gz is not listed in SHA256SUMS`);
|
|
57
|
+
if (createHash("sha256").update(archive).digest("hex") !== expected) die(`checksum mismatch for ${name}.tar.gz`);
|
|
58
|
+
const tmp = mkdtempSync(join(dirname(dest), ".download-"));
|
|
59
|
+
try {
|
|
60
|
+
writeFileSync(join(tmp, `${name}.tar.gz`), archive);
|
|
61
|
+
if (spawnSync("tar", ["-xzf", `${name}.tar.gz`], { cwd: tmp }).status !== 0) die(`could not unpack ${name}.tar.gz`);
|
|
62
|
+
chmodSync(join(tmp, name), 0o755);
|
|
63
|
+
if (!spawnSync(join(tmp, name), ["--version"], { encoding: "utf8" }).stdout?.trim()) die("the downloaded binary does not run on this system");
|
|
64
|
+
renameSync(join(tmp, name), dest); // atomic: a second run at the same time finds a whole file or none
|
|
65
|
+
} finally {
|
|
66
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const name = `sasacode-${target()}`;
|
|
71
|
+
const dir = join(process.env.SASACODE_HOME ?? join(homedir(), ".sasacode"), "npm-bin", `v${version}`);
|
|
72
|
+
const bin = join(dir, name);
|
|
73
|
+
if (!existsSync(bin)) {
|
|
74
|
+
mkdirSync(dir, { recursive: true });
|
|
75
|
+
await install(name, bin);
|
|
76
|
+
}
|
|
77
|
+
// `sasacode update` would replace this cached binary; with npm, npm does the updating.
|
|
78
|
+
const run = spawnSync(bin, process.argv.slice(2), { stdio: "inherit", env: { ...process.env, SASACODE_INSTALLED_BY: "npm" } });
|
|
79
|
+
if (run.error) die(run.error.message);
|
|
80
|
+
process.exit(run.status ?? 128 + (constants.signals[run.signal] ?? 0));
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sasacode",
|
|
3
|
+
"version": "0.9.7",
|
|
4
|
+
"description": "A small, pluggable coding agent for the terminal. This package fetches the single binary for your machine on first run.",
|
|
5
|
+
"keywords": ["coding-agent", "cli", "llm", "agent", "terminal"],
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": { "sasacode": "bin/sasacode.js" },
|
|
8
|
+
"files": ["bin/sasacode.js", "README.md"],
|
|
9
|
+
"engines": { "node": ">=18" },
|
|
10
|
+
"os": ["darwin", "linux"],
|
|
11
|
+
"cpu": ["x64", "arm64"],
|
|
12
|
+
"homepage": "https://sasanokusa.com/sasacode/",
|
|
13
|
+
"repository": { "type": "git", "url": "git+https://github.com/sasanokusa/sasacode.git", "directory": "npm/sasacode" },
|
|
14
|
+
"license": "Apache-2.0"
|
|
15
|
+
}
|