anveesa 0.2.2 → 0.2.3
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/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +5 -3
- package/bin/anveesa.js +5 -5
- package/package.json +4 -5
- package/scripts/install.js +86 -33
- package/src/lib.rs +14 -0
- package/src/provider/openai_compatible.rs +5 -2
- package/.github/workflows/release.yml +0 -68
- package/bin/anveesa +0 -0
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -10,17 +10,19 @@ Anveesa can be published as an npm package via a Node.js wrapper that invokes th
|
|
|
10
10
|
### Install from npm
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install -g anveesa
|
|
13
|
+
npm install -g anveesa
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
### Build and publish
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
|
|
19
|
+
git tag v$(node -p "require('./package.json').version")
|
|
20
|
+
git push origin main --tags
|
|
20
21
|
npm publish
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
Wait for the GitHub release binary workflow to finish before publishing to npm.
|
|
25
|
+
See `npm-publish.md` for the full release checklist.
|
|
24
26
|
|
|
25
27
|
- `openai-compatible`: HTTP chat completions providers such as OpenRouter and other compatible gateways.
|
|
26
28
|
- `command`: local CLIs such as Codex, Copilot, and Claude Code, where Anveesa spawns a command and passes the prompt.
|
package/bin/anveesa.js
CHANGED
|
@@ -8,14 +8,14 @@ function findBinary() {
|
|
|
8
8
|
const platform = process.platform;
|
|
9
9
|
const ext = platform === 'win32' ? '.exe' : '';
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
const bundled = path.join(__dirname, 'anveesa' + ext);
|
|
13
|
-
if (fs.existsSync(bundled)) return bundled;
|
|
14
|
-
|
|
15
|
-
// Also check for target/release (dev mode)
|
|
11
|
+
// Prefer the latest local release build in a development checkout.
|
|
16
12
|
const devPath = path.join(__dirname, '..', 'target', 'release', 'anveesa' + ext);
|
|
17
13
|
if (fs.existsSync(devPath)) return devPath;
|
|
18
14
|
|
|
15
|
+
// Installed npm packages keep the downloaded binary in the package bin/ directory.
|
|
16
|
+
const bundled = path.join(__dirname, 'anveesa' + ext);
|
|
17
|
+
if (fs.existsSync(bundled)) return bundled;
|
|
18
|
+
|
|
19
19
|
// Check if there's a sibling directory with the binary
|
|
20
20
|
const sibling = path.join(__dirname, 'target', 'release', 'anveesa' + ext);
|
|
21
21
|
if (fs.existsSync(sibling)) return sibling;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anveesa",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "A terminal CLI that wraps AI providers (OpenAI-compatible APIs and local CLIs) into a single unified command",
|
|
5
5
|
"main": "bin/anveesa.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,13 +12,12 @@
|
|
|
12
12
|
"prepublishOnly": "npm run build"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"bin/",
|
|
16
|
-
"scripts/",
|
|
15
|
+
"bin/anveesa.js",
|
|
16
|
+
"scripts/install.js",
|
|
17
17
|
"Cargo.toml",
|
|
18
18
|
"Cargo.lock",
|
|
19
19
|
"src/",
|
|
20
|
-
"README.md"
|
|
21
|
-
".github/"
|
|
20
|
+
"README.md"
|
|
22
21
|
],
|
|
23
22
|
"keywords": [
|
|
24
23
|
"ai",
|
package/scripts/install.js
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const https = require('https');
|
|
11
|
-
const {
|
|
11
|
+
const { execFileSync } = require('child_process');
|
|
12
12
|
|
|
13
13
|
const PACKAGE = require(path.join(__dirname, '..', 'package.json'));
|
|
14
14
|
const REPO = 'pandhuwibowo/anveesa-cli';
|
|
15
15
|
const BIN_DIR = path.join(__dirname, '..', 'bin');
|
|
16
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
16
17
|
|
|
17
18
|
function getPlatformInfo() {
|
|
18
19
|
const platform = process.platform;
|
|
@@ -32,6 +33,14 @@ function getPlatformInfo() {
|
|
|
32
33
|
return null;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function getExecutableName() {
|
|
37
|
+
return process.platform === 'win32' ? 'anveesa.exe' : 'anveesa';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getBinaryPath() {
|
|
41
|
+
return path.join(BIN_DIR, getExecutableName());
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
function getBinaryUrl() {
|
|
36
45
|
const info = getPlatformInfo();
|
|
37
46
|
if (!info) return null;
|
|
@@ -39,31 +48,47 @@ function getBinaryUrl() {
|
|
|
39
48
|
return `https://github.com/${REPO}/releases/download/v${version}/anveesa-${version}-${info.target}.tar.gz`;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
function download(url, dest) {
|
|
51
|
+
function download(url, dest, redirects = 0) {
|
|
43
52
|
return new Promise((resolve, reject) => {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
53
|
+
const request = https.get(url, {
|
|
54
|
+
headers: { 'User-Agent': `anveesa-install/${PACKAGE.version}` },
|
|
55
|
+
}, (res) => {
|
|
56
|
+
if (REDIRECT_STATUSES.has(res.statusCode)) {
|
|
57
|
+
res.resume();
|
|
58
|
+
if (!res.headers.location) {
|
|
59
|
+
reject(new Error(`HTTP ${res.statusCode} without Location header`));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (redirects >= 5) {
|
|
63
|
+
reject(new Error('too many redirects'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const nextUrl = new URL(res.headers.location, url).toString();
|
|
68
|
+
resolve(download(nextUrl, dest, redirects + 1));
|
|
69
|
+
return;
|
|
62
70
|
}
|
|
71
|
+
|
|
72
|
+
if (res.statusCode !== 200) {
|
|
73
|
+
res.resume();
|
|
74
|
+
reject(new Error(res.statusCode === 404 ? '404' : `HTTP ${res.statusCode}`));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const file = fs.createWriteStream(dest);
|
|
79
|
+
file.on('finish', () => { file.close(resolve); });
|
|
80
|
+
file.on('error', (err) => {
|
|
81
|
+
fs.unlink(dest, () => {});
|
|
82
|
+
reject(err);
|
|
83
|
+
});
|
|
84
|
+
res.pipe(file);
|
|
63
85
|
}).on('error', (err) => {
|
|
64
86
|
fs.unlink(dest, () => {});
|
|
65
87
|
reject(err);
|
|
66
88
|
});
|
|
89
|
+
request.setTimeout(30000, () => {
|
|
90
|
+
request.destroy(new Error('download timed out'));
|
|
91
|
+
});
|
|
67
92
|
});
|
|
68
93
|
}
|
|
69
94
|
|
|
@@ -76,18 +101,23 @@ async function tryDownloadBinary() {
|
|
|
76
101
|
|
|
77
102
|
console.log('⬇ Downloading prebuilt binary for', process.platform, process.arch);
|
|
78
103
|
|
|
104
|
+
const tarPath = path.join(__dirname, 'anveesa-bin.tar.gz');
|
|
79
105
|
try {
|
|
80
|
-
|
|
106
|
+
if (!fs.existsSync(BIN_DIR)) fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
107
|
+
|
|
81
108
|
await download(url, tarPath);
|
|
82
109
|
|
|
83
110
|
// Extract
|
|
84
|
-
|
|
111
|
+
execFileSync('tar', ['xzf', tarPath, '-C', BIN_DIR], { stdio: 'inherit' });
|
|
85
112
|
fs.unlinkSync(tarPath);
|
|
86
113
|
|
|
87
114
|
// Make executable
|
|
88
|
-
const binary =
|
|
115
|
+
const binary = getBinaryPath();
|
|
89
116
|
if (fs.existsSync(binary)) {
|
|
90
|
-
fs.chmodSync(binary, 0o755);
|
|
117
|
+
if (process.platform !== 'win32') fs.chmodSync(binary, 0o755);
|
|
118
|
+
} else {
|
|
119
|
+
console.log('⚠ Downloaded archive did not contain', getExecutableName());
|
|
120
|
+
return false;
|
|
91
121
|
}
|
|
92
122
|
|
|
93
123
|
console.log('✓ Binary downloaded successfully');
|
|
@@ -96,6 +126,7 @@ async function tryDownloadBinary() {
|
|
|
96
126
|
if (e.message === '404') {
|
|
97
127
|
console.log('ℹ No prebuilt binary for this version yet');
|
|
98
128
|
}
|
|
129
|
+
fs.unlink(tarPath, () => {});
|
|
99
130
|
return false;
|
|
100
131
|
}
|
|
101
132
|
}
|
|
@@ -103,29 +134,45 @@ async function tryDownloadBinary() {
|
|
|
103
134
|
function tryBuildFromSource() {
|
|
104
135
|
try {
|
|
105
136
|
console.log('⚙ Building from source (requires Rust)...');
|
|
106
|
-
|
|
137
|
+
execFileSync('cargo', ['build', '--release'], { cwd: path.join(__dirname, '..'), stdio: 'inherit' });
|
|
107
138
|
|
|
108
|
-
const src = path.join(__dirname, '..', 'target', 'release',
|
|
109
|
-
const dest =
|
|
139
|
+
const src = path.join(__dirname, '..', 'target', 'release', getExecutableName());
|
|
140
|
+
const dest = getBinaryPath();
|
|
110
141
|
|
|
111
142
|
if (fs.existsSync(src)) {
|
|
112
143
|
if (!fs.existsSync(BIN_DIR)) fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
113
144
|
fs.copyFileSync(src, dest);
|
|
114
|
-
fs.chmodSync(dest, 0o755);
|
|
145
|
+
if (process.platform !== 'win32') fs.chmodSync(dest, 0o755);
|
|
115
146
|
console.log('✓ Built from source successfully');
|
|
116
147
|
return true;
|
|
117
148
|
}
|
|
118
149
|
} catch (e) {
|
|
119
|
-
|
|
150
|
+
if (e.code === 'ENOENT') {
|
|
151
|
+
console.log('⚠ Build from source failed: cargo was not found');
|
|
152
|
+
} else {
|
|
153
|
+
console.log('⚠ Build from source failed');
|
|
154
|
+
}
|
|
120
155
|
}
|
|
121
156
|
return false;
|
|
122
157
|
}
|
|
123
158
|
|
|
159
|
+
function hasUsableExistingBinary() {
|
|
160
|
+
const existing = getBinaryPath();
|
|
161
|
+
if (!fs.existsSync(existing)) return false;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
if (process.platform !== 'win32') fs.chmodSync(existing, 0o755);
|
|
165
|
+
execFileSync(existing, ['--help'], { stdio: 'ignore', timeout: 5000 });
|
|
166
|
+
return true;
|
|
167
|
+
} catch (e) {
|
|
168
|
+
console.log('ℹ Existing anveesa binary is not usable on this platform; replacing it');
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
124
173
|
async function install() {
|
|
125
174
|
// Check if binary already exists
|
|
126
|
-
|
|
127
|
-
const existingExe = path.join(BIN_DIR, 'anveesa.exe');
|
|
128
|
-
if (fs.existsSync(existing) || fs.existsSync(existingExe)) {
|
|
175
|
+
if (hasUsableExistingBinary()) {
|
|
129
176
|
console.log('✓ anveesa binary already installed');
|
|
130
177
|
return;
|
|
131
178
|
}
|
|
@@ -139,6 +186,12 @@ async function install() {
|
|
|
139
186
|
console.error('');
|
|
140
187
|
console.error('✗ Could not install anveesa binary.');
|
|
141
188
|
console.error('');
|
|
189
|
+
const url = getBinaryUrl();
|
|
190
|
+
if (url) {
|
|
191
|
+
console.error(`No prebuilt binary was available for anveesa v${PACKAGE.version}:`);
|
|
192
|
+
console.error(` ${url}`);
|
|
193
|
+
console.error('');
|
|
194
|
+
}
|
|
142
195
|
console.error('Install Rust: https://rustup.rs/');
|
|
143
196
|
console.error('Then run: npm install -g anveesa');
|
|
144
197
|
console.error('');
|
|
@@ -147,4 +200,4 @@ async function install() {
|
|
|
147
200
|
process.exit(1);
|
|
148
201
|
}
|
|
149
202
|
|
|
150
|
-
install();
|
|
203
|
+
install();
|
package/src/lib.rs
CHANGED
|
@@ -16,6 +16,7 @@ use clap::{CommandFactory, Parser};
|
|
|
16
16
|
use serde::{Deserialize, Serialize};
|
|
17
17
|
use tokio::sync::mpsc;
|
|
18
18
|
|
|
19
|
+
#[cfg(target_os = "macos")]
|
|
19
20
|
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
|
20
21
|
|
|
21
22
|
use crate::{
|
|
@@ -968,11 +969,13 @@ impl PromptBuffer {
|
|
|
968
969
|
}
|
|
969
970
|
}
|
|
970
971
|
|
|
972
|
+
#[cfg(unix)]
|
|
971
973
|
struct RawPromptMode {
|
|
972
974
|
fd: i32,
|
|
973
975
|
saved: libc::termios,
|
|
974
976
|
}
|
|
975
977
|
|
|
978
|
+
#[cfg(unix)]
|
|
976
979
|
impl RawPromptMode {
|
|
977
980
|
fn enter() -> Result<Self> {
|
|
978
981
|
let fd = libc::STDIN_FILENO;
|
|
@@ -1001,6 +1004,7 @@ impl RawPromptMode {
|
|
|
1001
1004
|
}
|
|
1002
1005
|
}
|
|
1003
1006
|
|
|
1007
|
+
#[cfg(unix)]
|
|
1004
1008
|
impl Drop for RawPromptMode {
|
|
1005
1009
|
fn drop(&mut self) {
|
|
1006
1010
|
print!("\x1b[?2004l");
|
|
@@ -1012,6 +1016,16 @@ impl Drop for RawPromptMode {
|
|
|
1012
1016
|
}
|
|
1013
1017
|
}
|
|
1014
1018
|
|
|
1019
|
+
#[cfg(not(unix))]
|
|
1020
|
+
struct RawPromptMode;
|
|
1021
|
+
|
|
1022
|
+
#[cfg(not(unix))]
|
|
1023
|
+
impl RawPromptMode {
|
|
1024
|
+
fn enter() -> Result<Self> {
|
|
1025
|
+
Ok(Self)
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1015
1029
|
fn read_prompt_line(label: &str, width: usize, paste_count: &mut usize) -> Result<PromptRead> {
|
|
1016
1030
|
let _raw_mode = RawPromptMode::enter()?;
|
|
1017
1031
|
let mut input = io::stdin().lock();
|
|
@@ -668,7 +668,7 @@ async fn stream_response(
|
|
|
668
668
|
|
|
669
669
|
loop {
|
|
670
670
|
let chunk_result = response.chunk().await;
|
|
671
|
-
|
|
671
|
+
|
|
672
672
|
match chunk_result {
|
|
673
673
|
Ok(Some(chunk)) => {
|
|
674
674
|
consecutive_errors = 0; // Reset error counter on successful read
|
|
@@ -689,7 +689,10 @@ async fn stream_response(
|
|
|
689
689
|
consecutive_errors += 1;
|
|
690
690
|
if consecutive_errors >= MAX_CONSECUTIVE_ERRORS {
|
|
691
691
|
// Log the error but don't fail the whole request
|
|
692
|
-
eprintln!(
|
|
692
|
+
eprintln!(
|
|
693
|
+
"\n[warning: stream interrupted after {} consecutive errors]",
|
|
694
|
+
consecutive_errors
|
|
695
|
+
);
|
|
693
696
|
break;
|
|
694
697
|
}
|
|
695
698
|
// Try to continue reading - transient network hiccups happen
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
name: Release Binaries
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*'
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: write
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
build:
|
|
13
|
-
strategy:
|
|
14
|
-
matrix:
|
|
15
|
-
include:
|
|
16
|
-
- os: ubuntu-latest
|
|
17
|
-
target: x86_64-unknown-linux-gnu
|
|
18
|
-
- os: ubuntu-latest
|
|
19
|
-
target: aarch64-unknown-linux-gnu
|
|
20
|
-
- os: macos-latest
|
|
21
|
-
target: x86_64-apple-darwin
|
|
22
|
-
- os: macos-latest
|
|
23
|
-
target: aarch64-apple-darwin
|
|
24
|
-
- os: windows-latest
|
|
25
|
-
target: x86_64-pc-windows-msvc
|
|
26
|
-
|
|
27
|
-
runs-on: ${{ matrix.os }}
|
|
28
|
-
|
|
29
|
-
steps:
|
|
30
|
-
- uses: actions/checkout@v4
|
|
31
|
-
|
|
32
|
-
- name: Install Rust
|
|
33
|
-
uses: dtolnay/rust-action@stable
|
|
34
|
-
with:
|
|
35
|
-
targets: ${{ matrix.target }}
|
|
36
|
-
|
|
37
|
-
- name: Install cross-compilation tools (Linux ARM64)
|
|
38
|
-
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
|
39
|
-
run: |
|
|
40
|
-
sudo apt-get update
|
|
41
|
-
sudo apt-get install -y gcc-aarch64-linux-gnu
|
|
42
|
-
echo 'CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc' >> $GITHUB_ENV
|
|
43
|
-
|
|
44
|
-
- name: Build
|
|
45
|
-
run: cargo build --release --target ${{ matrix.target }}
|
|
46
|
-
|
|
47
|
-
- name: Package binary
|
|
48
|
-
shell: bash
|
|
49
|
-
run: |
|
|
50
|
-
VERSION=${GITHUB_REF_NAME#v}
|
|
51
|
-
BIN_DIR=release-artifacts
|
|
52
|
-
mkdir -p $BIN_DIR
|
|
53
|
-
|
|
54
|
-
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
|
|
55
|
-
cp target/${{ matrix.target }}/release/anveesa.exe $BIN_DIR/anveesa.exe
|
|
56
|
-
cd $BIN_DIR && tar czf ../anveesa-$VERSION-${{ matrix.target }}.tar.gz anveesa.exe
|
|
57
|
-
else
|
|
58
|
-
cp target/${{ matrix.target }}/release/anveesa $BIN_DIR/anveesa
|
|
59
|
-
chmod +x $BIN_DIR/anveesa
|
|
60
|
-
cd $BIN_DIR && tar czf ../anveesa-$VERSION-${{ matrix.target }}.tar.gz anveesa
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
- name: Upload to Release
|
|
64
|
-
uses: softprops/action-gh-release@v2
|
|
65
|
-
with:
|
|
66
|
-
files: anveesa-*.tar.gz
|
|
67
|
-
env:
|
|
68
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/bin/anveesa
DELETED
|
Binary file
|