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 CHANGED
@@ -54,7 +54,7 @@ dependencies = [
54
54
 
55
55
  [[package]]
56
56
  name = "anveesa"
57
- version = "0.2.0"
57
+ version = "0.2.3"
58
58
  dependencies = [
59
59
  "anyhow",
60
60
  "base64",
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "anveesa"
3
- version = "0.2.0"
3
+ version = "0.2.3"
4
4
  edition = "2024"
5
5
  default-run = "anveesa"
6
6
 
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-cli
13
+ npm install -g anveesa
14
14
  ```
15
15
 
16
16
  ### Build and publish
17
17
 
18
18
  ```bash
19
- npm run build
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
- See `package.json` in the root directory for build scripts and npm configuration.
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
- // Binary is shipped in the package bin/ directory
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.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",
@@ -8,11 +8,12 @@
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
10
  const https = require('https');
11
- const { execSync } = require('child_process');
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 file = fs.createWriteStream(dest);
45
- https.get(url, { followAllRedirects: true }, (res) => {
46
- if (res.statusCode === 302 || res.statusCode === 301) {
47
- https.get(res.headers.location, (res2) => {
48
- res2.pipe(file);
49
- file.on('finish', () => { file.close(); resolve(); });
50
- }).on('error', reject);
51
- } else if (res.statusCode === 200) {
52
- res.pipe(file);
53
- file.on('finish', () => { file.close(); resolve(); });
54
- } else if (res.statusCode === 404) {
55
- file.close();
56
- fs.unlink(dest, () => {});
57
- reject(new Error('404'));
58
- } else {
59
- file.close();
60
- fs.unlink(dest, () => {});
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
- const tarPath = path.join(__dirname, 'anveesa-bin.tar.gz');
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
- execSync(`tar xzf "${tarPath}" -C "${BIN_DIR}"`, { stdio: 'inherit' });
111
+ execFileSync('tar', ['xzf', tarPath, '-C', BIN_DIR], { stdio: 'inherit' });
85
112
  fs.unlinkSync(tarPath);
86
113
 
87
114
  // Make executable
88
- const binary = path.join(BIN_DIR, 'anveesa');
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
- execSync('cargo build --release', { cwd: path.join(__dirname, '..'), stdio: 'inherit' });
137
+ execFileSync('cargo', ['build', '--release'], { cwd: path.join(__dirname, '..'), stdio: 'inherit' });
107
138
 
108
- const src = path.join(__dirname, '..', 'target', 'release', 'anveesa');
109
- const dest = path.join(BIN_DIR, 'anveesa');
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
- console.log('⚠ Build from source failed (Rust not installed?)');
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
- const existing = path.join(BIN_DIR, 'anveesa');
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!("\n[warning: stream interrupted after {} consecutive errors]", consecutive_errors);
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