@aegean-org/necode-cli 1.0.0
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/CHANGELOG.md +9212 -0
- package/LICENSE +22 -0
- package/README.md +115 -0
- package/bin/necode.js +54 -0
- package/binary-manifest.json +31 -0
- package/package.json +45 -0
- package/scripts/install-integrated-binary.js +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mario Zechner
|
|
4
|
+
Copyright (c) 2025-2026 Can Bölük
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @aegean-org/necode-cli
|
|
2
|
+
|
|
3
|
+
NE-focused coding agent CLI.
|
|
4
|
+
|
|
5
|
+
[中文](#中文) | [English](#english)
|
|
6
|
+
|
|
7
|
+
## 中文
|
|
8
|
+
|
|
9
|
+
### 安装
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install -g @aegean-org/necode-cli
|
|
13
|
+
necode
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
包发布在 npm registry,包名是 `@aegean-org/necode-cli`,安装后的命令是 `necode`。这是一个集成 CLI 包,安装时会从公开的 `liangwei/necode-cli-releases` GitHub Releases 下载当前平台二进制;普通用户不需要安装 Bun,也不需要单独安装内部 `pi-*` 包。
|
|
17
|
+
|
|
18
|
+
### 登录
|
|
19
|
+
|
|
20
|
+
在 TUI 中执行 `/login` 登录 NE。正常对话前必须完成 NE 登录。
|
|
21
|
+
|
|
22
|
+
### 本地文献 RAG
|
|
23
|
+
|
|
24
|
+
索引本地文献:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
/rag-import <file-or-directory>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
在输入中引用已索引文献:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
@文献
|
|
34
|
+
@文献:keyword
|
|
35
|
+
@doc
|
|
36
|
+
@doc:keyword
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
选择文献后会插入可读标题,例如 `@文献:"Paper Title"` 或 `@doc:"Paper Title"`。
|
|
40
|
+
|
|
41
|
+
### 默认 MCP 服务
|
|
42
|
+
|
|
43
|
+
necode 默认引入这些外部 MCP 服务:
|
|
44
|
+
|
|
45
|
+
- `noteexpress`: NE / NoteExpress MCP 服务
|
|
46
|
+
- `qingtibase`: 青提 MCP 服务
|
|
47
|
+
|
|
48
|
+
在 TUI 中使用 `/mcp list` 查看连接状态和可用工具。
|
|
49
|
+
|
|
50
|
+
### 发布
|
|
51
|
+
|
|
52
|
+
只发布 `@aegean-org/necode-cli` 一个 npm 包。内部 workspace 包会编译进平台二进制,并作为 `liangwei/necode-cli-releases` 的 GitHub Release assets 分发,不单独发布到 npm。
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
npm run build:necode:binaries -- --targets win32-x64
|
|
56
|
+
npm run pack:necode -- --targets win32-x64
|
|
57
|
+
npm run publish:necode -- --targets win32-x64
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
npm 包会保持小体积,并包含二进制校验清单。跨平台包需要先把 `darwin-arm64,darwin-x64,linux-arm64,linux-x64,win32-x64` 的二进制都放到 `packages/coding-agent/binaries/`,发布时还要先把这些文件上传到 `liangwei/necode-cli-releases` 的匹配版本 GitHub Release。
|
|
61
|
+
|
|
62
|
+
## English
|
|
63
|
+
|
|
64
|
+
### Install
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npm install -g @aegean-org/necode-cli
|
|
68
|
+
necode
|
|
69
|
+
```
|
|
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.
|
|
72
|
+
|
|
73
|
+
### Login
|
|
74
|
+
|
|
75
|
+
Run `/login` in the TUI to sign in with NE. A valid NE login is required before normal chat usage.
|
|
76
|
+
|
|
77
|
+
### Local Literature RAG
|
|
78
|
+
|
|
79
|
+
Index local papers:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
/rag-import <file-or-directory>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use indexed literature explicitly in a prompt:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
@文献
|
|
89
|
+
@文献:keyword
|
|
90
|
+
@doc
|
|
91
|
+
@doc:keyword
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Selecting an indexed document inserts a readable title such as `@文献:"Paper Title"` or `@doc:"Paper Title"`.
|
|
95
|
+
|
|
96
|
+
### Default MCP Servers
|
|
97
|
+
|
|
98
|
+
necode includes these default external MCP servers:
|
|
99
|
+
|
|
100
|
+
- `noteexpress`: NE / NoteExpress MCP server
|
|
101
|
+
- `qingtibase`: Qingti MCP server
|
|
102
|
+
|
|
103
|
+
Use `/mcp list` inside the TUI to check whether they are connected and which tools are available.
|
|
104
|
+
|
|
105
|
+
### Publishing
|
|
106
|
+
|
|
107
|
+
Only `@aegean-org/necode-cli` is published to npm. Internal workspace packages are compiled into platform binaries distributed as `liangwei/necode-cli-releases` GitHub Release assets instead of being published separately.
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
npm run build:necode:binaries -- --targets win32-x64
|
|
111
|
+
npm run pack:necode -- --targets win32-x64
|
|
112
|
+
npm run publish:necode -- --targets win32-x64
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The npm package stays small and contains a checked binary manifest. A cross-platform package requires binaries for `darwin-arm64,darwin-x64,linux-arm64,linux-x64,win32-x64` to exist in `packages/coding-agent/binaries/` first, and publish runs must upload those files to the matching release in `liangwei/necode-cli-releases` before `npm publish`.
|
package/bin/necode.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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";
|
|
6
|
+
|
|
7
|
+
const FAILURE_EXIT_CODE = 1;
|
|
8
|
+
const SUCCESS_EXIT_CODE = 0;
|
|
9
|
+
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
|
+
|
|
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();
|
|
34
|
+
const result = spawnSync(binaryPath, process.argv.slice(USER_ARGV_OFFSET), {
|
|
35
|
+
stdio: "inherit",
|
|
36
|
+
windowsHide: false,
|
|
37
|
+
});
|
|
38
|
+
if (result.error) {
|
|
39
|
+
throw result.error;
|
|
40
|
+
}
|
|
41
|
+
if (result.signal) {
|
|
42
|
+
process.stderr.write(`necode terminated by signal ${result.signal}\n`);
|
|
43
|
+
process.exit(FAILURE_EXIT_CODE);
|
|
44
|
+
}
|
|
45
|
+
process.exit(result.status ?? SUCCESS_EXIT_CODE);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
run();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
process.stderr.write(`error: ${message}\n`);
|
|
53
|
+
process.exit(FAILURE_EXIT_CODE);
|
|
54
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"repository": "liangwei/necode-cli-releases",
|
|
4
|
+
"assets": {
|
|
5
|
+
"darwin-arm64": {
|
|
6
|
+
"filename": "necode-cli-darwin-arm64",
|
|
7
|
+
"sha256": "210cfe0ca226da82e3fab887b93ff7173af88ca7e7310b509340ee4f1c61592c",
|
|
8
|
+
"size": 201537632
|
|
9
|
+
},
|
|
10
|
+
"darwin-x64": {
|
|
11
|
+
"filename": "necode-cli-darwin-x64",
|
|
12
|
+
"sha256": "dbad795f958ab25880adffa69a2ef40aa10d4012b776a75374a5c6e963c14c9c",
|
|
13
|
+
"size": 206981312
|
|
14
|
+
},
|
|
15
|
+
"linux-arm64": {
|
|
16
|
+
"filename": "necode-cli-linux-arm64",
|
|
17
|
+
"sha256": "264eb62fd60083e7a7f1bbf5834acd90eab2728712c6629a9ab38404105df0ab",
|
|
18
|
+
"size": 227387536
|
|
19
|
+
},
|
|
20
|
+
"linux-x64": {
|
|
21
|
+
"filename": "necode-cli-linux-x64",
|
|
22
|
+
"sha256": "aaa36b9f039077c39d771af3906df68cdef7f01ee06675861e778133ab4cb944",
|
|
23
|
+
"size": 243677312
|
|
24
|
+
},
|
|
25
|
+
"win32-x64": {
|
|
26
|
+
"filename": "necode-cli-windows-x64.exe",
|
|
27
|
+
"sha256": "4f09f4336ff11d019395d21a595540eb964dd0fb6f05e85539ab389169ba86c6",
|
|
28
|
+
"size": 231872000
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aegean-org/necode-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "necode coding agent with read, bash, edit, write tools and session management",
|
|
5
|
+
"author": "Can Boluk",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Mario Zechner"
|
|
8
|
+
],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"coding-agent",
|
|
12
|
+
"ai",
|
|
13
|
+
"llm",
|
|
14
|
+
"cli",
|
|
15
|
+
"tui",
|
|
16
|
+
"agent"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"bin": {
|
|
20
|
+
"necode": "bin/necode.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin",
|
|
24
|
+
"scripts",
|
|
25
|
+
"binary-manifest.json",
|
|
26
|
+
"README.md",
|
|
27
|
+
"CHANGELOG.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"postinstall": "node scripts/install-integrated-binary.js"
|
|
35
|
+
},
|
|
36
|
+
"os": [
|
|
37
|
+
"darwin",
|
|
38
|
+
"linux",
|
|
39
|
+
"win32"
|
|
40
|
+
],
|
|
41
|
+
"cpu": [
|
|
42
|
+
"arm64",
|
|
43
|
+
"x64"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as fsp from "node:fs/promises";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
7
|
+
import { pipeline } from "node:stream/promises";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const EXECUTABLE_MODE = 0o755;
|
|
11
|
+
const FAILURE_EXIT_CODE = 1;
|
|
12
|
+
const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
13
|
+
const manifestPath = path.join(packageRoot, "binary-manifest.json");
|
|
14
|
+
const binariesDir = path.join(packageRoot, "binaries");
|
|
15
|
+
|
|
16
|
+
async function readManifest() {
|
|
17
|
+
const manifest = JSON.parse(await fsp.readFile(manifestPath, "utf8"));
|
|
18
|
+
if (typeof manifest.version !== "string") throw new Error("binary-manifest.json is missing version");
|
|
19
|
+
if (typeof manifest.repository !== "string") throw new Error("binary-manifest.json is missing repository");
|
|
20
|
+
if (!manifest.assets || typeof manifest.assets !== "object") throw new Error("binary-manifest.json is missing assets");
|
|
21
|
+
return manifest;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveAsset(manifest) {
|
|
25
|
+
const platformKey = `${process.platform}-${process.arch}`;
|
|
26
|
+
const asset = manifest.assets[platformKey];
|
|
27
|
+
if (!asset) {
|
|
28
|
+
const supported = Object.keys(manifest.assets).sort().join(", ");
|
|
29
|
+
throw new Error(`Unsupported platform ${platformKey}. Supported platforms: ${supported}`);
|
|
30
|
+
}
|
|
31
|
+
if (typeof asset.filename !== "string") throw new Error(`Asset ${platformKey} is missing filename`);
|
|
32
|
+
if (typeof asset.sha256 !== "string") throw new Error(`Asset ${platformKey} is missing sha256`);
|
|
33
|
+
return asset;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function releaseAssetUrl(manifest, asset) {
|
|
37
|
+
const tag = `v${manifest.version}`;
|
|
38
|
+
return `https://github.com/${manifest.repository}/releases/download/${tag}/${asset.filename}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function sha256File(filePath) {
|
|
42
|
+
const hash = createHash("sha256");
|
|
43
|
+
for await (const chunk of fs.createReadStream(filePath)) {
|
|
44
|
+
hash.update(chunk);
|
|
45
|
+
}
|
|
46
|
+
return hash.digest("hex");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function downloadFile(url, outputPath) {
|
|
50
|
+
const response = await fetch(url);
|
|
51
|
+
if (!response.ok) throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
52
|
+
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 }));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function installBinary() {
|
|
57
|
+
const manifest = await readManifest();
|
|
58
|
+
const asset = resolveAsset(manifest);
|
|
59
|
+
const finalPath = path.join(binariesDir, asset.filename);
|
|
60
|
+
const tmpPath = `${finalPath}.tmp-${process.pid}`;
|
|
61
|
+
await fsp.mkdir(binariesDir, { recursive: true });
|
|
62
|
+
await fsp.rm(tmpPath, { force: true });
|
|
63
|
+
try {
|
|
64
|
+
await downloadFile(releaseAssetUrl(manifest, asset), tmpPath);
|
|
65
|
+
const actualSha256 = await sha256File(tmpPath);
|
|
66
|
+
if (actualSha256 !== asset.sha256) {
|
|
67
|
+
throw new Error(`Checksum mismatch for ${asset.filename}: expected ${asset.sha256}, got ${actualSha256}`);
|
|
68
|
+
}
|
|
69
|
+
await fsp.rename(tmpPath, finalPath);
|
|
70
|
+
if (process.platform !== "win32") await fsp.chmod(finalPath, EXECUTABLE_MODE);
|
|
71
|
+
} finally {
|
|
72
|
+
await fsp.rm(tmpPath, { force: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
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);
|
|
82
|
+
}
|