@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 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 包,安装时会从公开的 `liangwei/necode-cli-releases` GitHub Releases 下载当前平台二进制;普通用户不需要安装 Bun,也不需要单独安装内部 `pi-*` 包。
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; install 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.
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 * as fs from "node:fs";
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 resolveBinary() {
19
- const key = `${process.platform}-${process.arch}`;
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`);
@@ -1,31 +1,31 @@
1
1
  {
2
- "version": "1.0.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": "210cfe0ca226da82e3fab887b93ff7173af88ca7e7310b509340ee4f1c61592c",
7
+ "sha256": "521c2e6d6d40faf86edd32009f42ea548831c7370382d25e0ca626b858ed82ae",
8
8
  "size": 201537632
9
9
  },
10
10
  "darwin-x64": {
11
11
  "filename": "necode-cli-darwin-x64",
12
- "sha256": "dbad795f958ab25880adffa69a2ef40aa10d4012b776a75374a5c6e963c14c9c",
12
+ "sha256": "162e1d67d961d920509f4ee323d581506b5bccdde418b130f1fb17f3f4f98bb6",
13
13
  "size": 206981312
14
14
  },
15
15
  "linux-arm64": {
16
16
  "filename": "necode-cli-linux-arm64",
17
- "sha256": "264eb62fd60083e7a7f1bbf5834acd90eab2728712c6629a9ab38404105df0ab",
17
+ "sha256": "42bbcc30a37a19a10c14c3e5ae4144e4eded32fa7553316f27a79c28312cc4dd",
18
18
  "size": 227387536
19
19
  },
20
20
  "linux-x64": {
21
21
  "filename": "necode-cli-linux-x64",
22
- "sha256": "aaa36b9f039077c39d771af3906df68cdef7f01ee06675861e778133ab4cb944",
23
- "size": 243677312
22
+ "sha256": "242581942463faafd74412b724ff2822e040ec23ec3666c1dc94ce6139e552f5",
23
+ "size": 243681408
24
24
  },
25
25
  "win32-x64": {
26
26
  "filename": "necode-cli-windows-x64.exe",
27
- "sha256": "4f09f4336ff11d019395d21a595540eb964dd0fb6f05e85539ab389169ba86c6",
28
- "size": 231872000
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.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 { Readable } from "node:stream";
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 binariesDir = path.join(packageRoot, "binaries");
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
- await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(outputPath, { mode: EXECUTABLE_MODE }));
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 = path.join(binariesDir, asset.filename);
108
+ const finalPath = resolveBinaryPath(manifest, asset);
60
109
  const tmpPath = `${finalPath}.tmp-${process.pid}`;
61
- await fsp.mkdir(binariesDir, { recursive: true });
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
- try {
77
- await installBinary();
78
- } catch (error) {
79
- const message = error instanceof Error ? error.message : String(error);
80
- process.stderr.write(`error: ${message}\n`);
81
- process.exit(FAILURE_EXIT_CODE);
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
  }