@aegean-org/necode-cli 1.0.0 → 1.0.2
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 +2 -2
- package/bin/necode.js +4 -27
- package/binary-manifest.json +8 -8
- package/package.json +1 -4
- package/scripts/install-integrated-binary.js +69 -15
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install -g @aegean-org/necode-cli
|
|
|
13
13
|
necode
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
包发布在 npm registry,包名是 `@aegean-org/necode-cli`,安装后的命令是 `necode`。这是一个集成 CLI
|
|
16
|
+
包发布在 npm registry,包名是 `@aegean-org/necode-cli`,安装后的命令是 `necode`。这是一个集成 CLI 包,首次运行 `necode` 时会从公开的 `liangwei/necode-cli-releases` GitHub Releases 下载当前平台二进制;普通用户不需要安装 Bun,也不需要单独安装内部 `pi-*` 包。
|
|
17
17
|
|
|
18
18
|
### 登录
|
|
19
19
|
|
|
@@ -68,7 +68,7 @@ npm install -g @aegean-org/necode-cli
|
|
|
68
68
|
necode
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
The package is published to the npm registry as `@aegean-org/necode-cli`, and the installed command is `necode`. It is an integrated CLI package;
|
|
71
|
+
The package is published to the npm registry as `@aegean-org/necode-cli`, and the installed command is `necode`. It is an integrated CLI package; the first `necode` run downloads the matching platform binary from the public `liangwei/necode-cli-releases` GitHub Releases, so normal users do not need Bun or separate internal `pi-*` packages.
|
|
72
72
|
|
|
73
73
|
### Login
|
|
74
74
|
|
package/bin/necode.js
CHANGED
|
@@ -1,36 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { installBinary } from "../scripts/install-integrated-binary.js";
|
|
6
4
|
|
|
7
5
|
const FAILURE_EXIT_CODE = 1;
|
|
8
6
|
const SUCCESS_EXIT_CODE = 0;
|
|
9
7
|
const USER_ARGV_OFFSET = 2;
|
|
10
|
-
const BINARIES = Object.freeze({
|
|
11
|
-
"darwin-arm64": "necode-cli-darwin-arm64",
|
|
12
|
-
"darwin-x64": "necode-cli-darwin-x64",
|
|
13
|
-
"linux-arm64": "necode-cli-linux-arm64",
|
|
14
|
-
"linux-x64": "necode-cli-linux-x64",
|
|
15
|
-
"win32-x64": "necode-cli-windows-x64.exe",
|
|
16
|
-
});
|
|
17
8
|
|
|
18
|
-
function
|
|
19
|
-
const
|
|
20
|
-
const binaryName = BINARIES[key];
|
|
21
|
-
if (!binaryName) {
|
|
22
|
-
throw new Error(`Unsupported platform: ${key}`);
|
|
23
|
-
}
|
|
24
|
-
const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
25
|
-
const binaryPath = path.join(packageRoot, "binaries", binaryName);
|
|
26
|
-
if (!fs.existsSync(binaryPath)) {
|
|
27
|
-
throw new Error(`Installed necode binary is missing: ${binaryPath}. Reinstall without --ignore-scripts.`);
|
|
28
|
-
}
|
|
29
|
-
return binaryPath;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function run() {
|
|
33
|
-
const binaryPath = resolveBinary();
|
|
9
|
+
async function run() {
|
|
10
|
+
const binaryPath = await installBinary();
|
|
34
11
|
const result = spawnSync(binaryPath, process.argv.slice(USER_ARGV_OFFSET), {
|
|
35
12
|
stdio: "inherit",
|
|
36
13
|
windowsHide: false,
|
|
@@ -46,7 +23,7 @@ function run() {
|
|
|
46
23
|
}
|
|
47
24
|
|
|
48
25
|
try {
|
|
49
|
-
run();
|
|
26
|
+
await run();
|
|
50
27
|
} catch (error) {
|
|
51
28
|
const message = error instanceof Error ? error.message : String(error);
|
|
52
29
|
process.stderr.write(`error: ${message}\n`);
|
package/binary-manifest.json
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
2
|
+
"version": "1.0.2",
|
|
3
3
|
"repository": "liangwei/necode-cli-releases",
|
|
4
4
|
"assets": {
|
|
5
5
|
"darwin-arm64": {
|
|
6
6
|
"filename": "necode-cli-darwin-arm64",
|
|
7
|
-
"sha256": "
|
|
7
|
+
"sha256": "521c2e6d6d40faf86edd32009f42ea548831c7370382d25e0ca626b858ed82ae",
|
|
8
8
|
"size": 201537632
|
|
9
9
|
},
|
|
10
10
|
"darwin-x64": {
|
|
11
11
|
"filename": "necode-cli-darwin-x64",
|
|
12
|
-
"sha256": "
|
|
12
|
+
"sha256": "162e1d67d961d920509f4ee323d581506b5bccdde418b130f1fb17f3f4f98bb6",
|
|
13
13
|
"size": 206981312
|
|
14
14
|
},
|
|
15
15
|
"linux-arm64": {
|
|
16
16
|
"filename": "necode-cli-linux-arm64",
|
|
17
|
-
"sha256": "
|
|
17
|
+
"sha256": "42bbcc30a37a19a10c14c3e5ae4144e4eded32fa7553316f27a79c28312cc4dd",
|
|
18
18
|
"size": 227387536
|
|
19
19
|
},
|
|
20
20
|
"linux-x64": {
|
|
21
21
|
"filename": "necode-cli-linux-x64",
|
|
22
|
-
"sha256": "
|
|
23
|
-
"size":
|
|
22
|
+
"sha256": "242581942463faafd74412b724ff2822e040ec23ec3666c1dc94ce6139e552f5",
|
|
23
|
+
"size": 243681408
|
|
24
24
|
},
|
|
25
25
|
"win32-x64": {
|
|
26
26
|
"filename": "necode-cli-windows-x64.exe",
|
|
27
|
-
"sha256": "
|
|
28
|
-
"size":
|
|
27
|
+
"sha256": "ca892f5a0d47180e1cf49c3fdd8efbb47de8eaeb5139441753986f6ee213056b",
|
|
28
|
+
"size": 231874048
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aegean-org/necode-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "necode coding agent with read, bash, edit, write tools and session management",
|
|
5
5
|
"author": "Can Boluk",
|
|
6
6
|
"contributors": [
|
|
@@ -30,9 +30,6 @@
|
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=18"
|
|
32
32
|
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"postinstall": "node scripts/install-integrated-binary.js"
|
|
35
|
-
},
|
|
36
33
|
"os": [
|
|
37
34
|
"darwin",
|
|
38
35
|
"linux",
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
+
import { once } from "node:events";
|
|
3
4
|
import * as fs from "node:fs";
|
|
4
5
|
import * as fsp from "node:fs/promises";
|
|
6
|
+
import * as os from "node:os";
|
|
5
7
|
import * as path from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
import { pipeline } from "node:stream/promises";
|
|
8
|
+
import { finished } from "node:stream/promises";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
|
|
10
11
|
const EXECUTABLE_MODE = 0o755;
|
|
11
12
|
const FAILURE_EXIT_CODE = 1;
|
|
13
|
+
const BYTES_PER_MIB = 1024 * 1024;
|
|
14
|
+
const PERCENT_SCALE = 100;
|
|
12
15
|
const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
13
16
|
const manifestPath = path.join(packageRoot, "binary-manifest.json");
|
|
14
|
-
const
|
|
17
|
+
const CACHE_DIR_NAME = "necode-cli";
|
|
15
18
|
|
|
16
|
-
async function readManifest() {
|
|
19
|
+
export async function readManifest() {
|
|
17
20
|
const manifest = JSON.parse(await fsp.readFile(manifestPath, "utf8"));
|
|
18
21
|
if (typeof manifest.version !== "string") throw new Error("binary-manifest.json is missing version");
|
|
19
22
|
if (typeof manifest.repository !== "string") throw new Error("binary-manifest.json is missing repository");
|
|
@@ -21,7 +24,7 @@ async function readManifest() {
|
|
|
21
24
|
return manifest;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
function resolveAsset(manifest) {
|
|
27
|
+
export function resolveAsset(manifest) {
|
|
25
28
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
26
29
|
const asset = manifest.assets[platformKey];
|
|
27
30
|
if (!asset) {
|
|
@@ -33,6 +36,16 @@ function resolveAsset(manifest) {
|
|
|
33
36
|
return asset;
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
function resolveCacheRoot() {
|
|
40
|
+
if (process.platform === "win32") return process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
41
|
+
if (process.platform === "darwin") return path.join(os.homedir(), "Library", "Caches");
|
|
42
|
+
return process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function resolveBinaryPath(manifest, asset) {
|
|
46
|
+
return path.join(resolveCacheRoot(), CACHE_DIR_NAME, manifest.version, asset.filename);
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
function releaseAssetUrl(manifest, asset) {
|
|
37
50
|
const tag = `v${manifest.version}`;
|
|
38
51
|
return `https://github.com/${manifest.repository}/releases/download/${tag}/${asset.filename}`;
|
|
@@ -46,21 +59,59 @@ async function sha256File(filePath) {
|
|
|
46
59
|
return hash.digest("hex");
|
|
47
60
|
}
|
|
48
61
|
|
|
62
|
+
async function fileExists(filePath) {
|
|
63
|
+
try {
|
|
64
|
+
const stat = await fsp.stat(filePath);
|
|
65
|
+
return stat.isFile();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return false;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function formatBytes(bytes) {
|
|
73
|
+
return `${(bytes / BYTES_PER_MIB).toFixed(1)} MiB`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function reportProgress(downloaded, totalBytes) {
|
|
77
|
+
if (totalBytes > 0) {
|
|
78
|
+
const percent = ((downloaded / totalBytes) * PERCENT_SCALE).toFixed(1);
|
|
79
|
+
process.stderr.write(`\rDownloading necode binary: ${formatBytes(downloaded)} / ${formatBytes(totalBytes)} (${percent}%)`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
process.stderr.write(`\rDownloading necode binary: ${formatBytes(downloaded)}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
49
85
|
async function downloadFile(url, outputPath) {
|
|
50
86
|
const response = await fetch(url);
|
|
51
87
|
if (!response.ok) throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
52
88
|
if (!response.body) throw new Error(`Failed to download ${url}: response body is empty`);
|
|
53
|
-
|
|
89
|
+
const totalBytes = Number(response.headers.get("content-length")) || 0;
|
|
90
|
+
const output = fs.createWriteStream(outputPath, { mode: EXECUTABLE_MODE });
|
|
91
|
+
const reader = response.body.getReader();
|
|
92
|
+
let downloaded = 0;
|
|
93
|
+
for (;;) {
|
|
94
|
+
const { done, value } = await reader.read();
|
|
95
|
+
if (done) break;
|
|
96
|
+
downloaded += value.byteLength;
|
|
97
|
+
if (!output.write(value)) await once(output, "drain");
|
|
98
|
+
reportProgress(downloaded, totalBytes);
|
|
99
|
+
}
|
|
100
|
+
output.end();
|
|
101
|
+
await finished(output);
|
|
102
|
+
process.stderr.write("\n");
|
|
54
103
|
}
|
|
55
104
|
|
|
56
|
-
async function installBinary() {
|
|
105
|
+
export async function installBinary() {
|
|
57
106
|
const manifest = await readManifest();
|
|
58
107
|
const asset = resolveAsset(manifest);
|
|
59
|
-
const finalPath =
|
|
108
|
+
const finalPath = resolveBinaryPath(manifest, asset);
|
|
60
109
|
const tmpPath = `${finalPath}.tmp-${process.pid}`;
|
|
61
|
-
await
|
|
110
|
+
if (await fileExists(finalPath)) return finalPath;
|
|
111
|
+
await fsp.mkdir(path.dirname(finalPath), { recursive: true });
|
|
62
112
|
await fsp.rm(tmpPath, { force: true });
|
|
63
113
|
try {
|
|
114
|
+
process.stderr.write(`Installing necode ${manifest.version} binary for ${process.platform}-${process.arch}\n`);
|
|
64
115
|
await downloadFile(releaseAssetUrl(manifest, asset), tmpPath);
|
|
65
116
|
const actualSha256 = await sha256File(tmpPath);
|
|
66
117
|
if (actualSha256 !== asset.sha256) {
|
|
@@ -71,12 +122,15 @@ async function installBinary() {
|
|
|
71
122
|
} finally {
|
|
72
123
|
await fsp.rm(tmpPath, { force: true });
|
|
73
124
|
}
|
|
125
|
+
return finalPath;
|
|
74
126
|
}
|
|
75
127
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
128
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
129
|
+
try {
|
|
130
|
+
await installBinary();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
process.stderr.write(`error: ${message}\n`);
|
|
134
|
+
process.exit(FAILURE_EXIT_CODE);
|
|
135
|
+
}
|
|
82
136
|
}
|