@hemansubedi/aether-ai 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.
Files changed (90) hide show
  1. package/.gitattributes +3 -0
  2. package/.github/workflows/live-stats.yml +42 -0
  3. package/.github/workflows/publish.yml +34 -0
  4. package/.github/workflows/update-preview.yml +41 -0
  5. package/INSTALL.md +59 -0
  6. package/LICENSE +21 -0
  7. package/README.md +397 -0
  8. package/assets/aether-arena.svg +72 -0
  9. package/assets/aether-banner.svg +62 -0
  10. package/assets/aether-router.svg +129 -0
  11. package/dist/agent.js +125 -0
  12. package/dist/arena.js +486 -0
  13. package/dist/checkpoint.js +105 -0
  14. package/dist/client.js +95 -0
  15. package/dist/combos.js +176 -0
  16. package/dist/commands.js +483 -0
  17. package/dist/config.js +104 -0
  18. package/dist/cost.js +176 -0
  19. package/dist/git.js +52 -0
  20. package/dist/health.js +81 -0
  21. package/dist/index.js +272 -0
  22. package/dist/keys.js +128 -0
  23. package/dist/memory.js +98 -0
  24. package/dist/modes.js +68 -0
  25. package/dist/providers/index.js +32 -0
  26. package/dist/providers/ollama.js +206 -0
  27. package/dist/providers/openai-compat.js +181 -0
  28. package/dist/providers/openrouter.js +189 -0
  29. package/dist/providers/registry.js +211 -0
  30. package/dist/router-engine.js +200 -0
  31. package/dist/router.js +171 -0
  32. package/dist/server.js +210 -0
  33. package/dist/session.js +97 -0
  34. package/dist/settings.js +97 -0
  35. package/dist/skills.js +100 -0
  36. package/dist/tokensaver.js +50 -0
  37. package/dist/tools/filesystem.js +243 -0
  38. package/dist/tools/git.js +53 -0
  39. package/dist/tools/glob.js +175 -0
  40. package/dist/tools/grep.js +193 -0
  41. package/dist/tools/registry.js +39 -0
  42. package/dist/tools/vision.js +140 -0
  43. package/dist/tools/websearch.js +118 -0
  44. package/dist/tui.js +562 -0
  45. package/dist/types.js +8 -0
  46. package/docs/preview.txt +51 -0
  47. package/docs/screenshots.md +110 -0
  48. package/docs/stats.md +5 -0
  49. package/install.ps1 +170 -0
  50. package/install.sh +196 -0
  51. package/package.json +34 -0
  52. package/scripts/generate-stats-card.ts +62 -0
  53. package/scripts/patch_index.ps1 +17 -0
  54. package/scripts/release.sh +7 -0
  55. package/src/agent.ts +146 -0
  56. package/src/arena.ts +584 -0
  57. package/src/checkpoint.ts +111 -0
  58. package/src/client.ts +172 -0
  59. package/src/combos.ts +199 -0
  60. package/src/commands.ts +973 -0
  61. package/src/config.ts +122 -0
  62. package/src/cost.ts +206 -0
  63. package/src/git.ts +68 -0
  64. package/src/health.ts +90 -0
  65. package/src/index.ts +281 -0
  66. package/src/keys.ts +135 -0
  67. package/src/memory.ts +101 -0
  68. package/src/modes.ts +84 -0
  69. package/src/providers/index.ts +59 -0
  70. package/src/providers/ollama.ts +222 -0
  71. package/src/providers/openai-compat.ts +188 -0
  72. package/src/providers/openrouter.ts +198 -0
  73. package/src/providers/registry.ts +223 -0
  74. package/src/router-engine.ts +214 -0
  75. package/src/router.ts +195 -0
  76. package/src/server.ts +242 -0
  77. package/src/session.ts +111 -0
  78. package/src/settings.ts +125 -0
  79. package/src/skills.ts +106 -0
  80. package/src/tokensaver.ts +57 -0
  81. package/src/tools/filesystem.ts +258 -0
  82. package/src/tools/git.ts +53 -0
  83. package/src/tools/glob.ts +180 -0
  84. package/src/tools/grep.ts +192 -0
  85. package/src/tools/registry.ts +54 -0
  86. package/src/tools/vision.ts +152 -0
  87. package/src/tools/websearch.ts +130 -0
  88. package/src/tui.ts +664 -0
  89. package/src/types.ts +77 -0
  90. package/tsconfig.json +16 -0
@@ -0,0 +1,110 @@
1
+ # Aether Screenshots
2
+
3
+ This document describes the Aether TUI layout and provides ASCII representations of each view, so the project always looks great even without real PNG captures.
4
+
5
+ > Note: Aether runs in a real terminal. The ASCII art below is a faithful textual rendering of each view. Real screenshots can be dropped into `docs/assets/` (gitignored) at any time.
6
+
7
+ ## TUI Layout
8
+
9
+ The interactive TUI is a ChatGPT-style pane:
10
+
11
+ ```
12
+ +- Aether -----------------------------------------------+
13
+ � provider � model �
14
+ � �
15
+ � ? user prompt �
16
+ � �
17
+ � ? [tool: read_file] src/index.ts �
18
+ � ? [tool: edit_file] src/index.ts �
19
+ � �
20
+ � assistant response (streamed token by token) �
21
+ � �
22
+ � ? Created src/routes/users.ts �
23
+ � ? Updated src/routes/posts.ts �
24
+ � �
25
+ � ? _ �
26
+ +----------------------------------------------------------+
27
+ ```
28
+
29
+ The bottom bar shows the active provider and model, separated by a dot. Tool calls are indented with a `?` prefix. Completed file operations are marked with a green check.
30
+
31
+ ## Banner
32
+
33
+ ```
34
+ _ _ _ __ __ ___
35
+ | \ | | / \ | \/ |/ _ \
36
+ | \| | / _ \ | |\/| | | | |
37
+ | |\ | / ___ \ | | | | |_| |
38
+ |_| \_| /_/ \_\ |_| |_|\___/
39
+
40
+ Aether
41
+ ```
42
+
43
+ ## Arena
44
+
45
+ The arena pits two models against each other on the same prompt. Responses render side by side and a blind vote is recorded.
46
+
47
+ ```
48
+ +- Arena -------------------------------------------------+
49
+ � �
50
+ � Prompt: Write a haiku about the moon �
51
+ � �
52
+ � +-----------------------------+ +---------------------+ �
53
+ � � Model A � � Model B � �
54
+ � � � � � �
55
+ � � Silver blade on quiet lake, � � Cold coin in the � �
56
+ � � a thousand reflections... � � sky, � �
57
+ � � � � � �
58
+ � � � � � �
59
+ � +-----------------------------+ +---------------------+ �
60
+ � �
61
+ � [A] [B] [Tie] �
62
+ � �
63
+ +----------------------------------------------------------+
64
+ ```
65
+
66
+ Each vote updates the Elo leaderboard:
67
+
68
+ ```
69
+ Rank Model Elo Wins Losses Votes
70
+ ---- ----------------------------- ----- ---- ----- -----
71
+ 1 qwen2.5-coder:7b 1512 24 3 27
72
+ 2 mistral:7b 1488 19 8 27
73
+ 3 llama3.2:3b 1395 11 16 27
74
+ ```
75
+
76
+ ## Stats
77
+
78
+ `/stats` renders a session summary, cost table, and per-provider health:
79
+
80
+ ```
81
+ Session:
82
+ messages: 42 (user: 14, assistant: 14, tool: 14)
83
+
84
+ Cost:
85
+ Provider Model Requests Input Tok Output Tok Cost (USD)
86
+ ----------------- ----------------------------------- -------- --------- ---------- ----------
87
+ ollama-local qwen2.5-coder:7b 14 17623 7574 0.000000
88
+ openrouter-free mistral:7b 2 150 300 0.000000
89
+ openai-compatible gpt-4o-mini 1 1000 500 0.005000
90
+ Total 0.005000
91
+
92
+ Provider health:
93
+ ollama-local: healthy
94
+ openrouter-free: healthy
95
+ openai-compatible: unhealthy (HTTP 401)
96
+ ```
97
+
98
+ ## How to Take a Real Screenshot
99
+
100
+ 1. Run the interactive TUI: `npx tsx src/index.ts`
101
+ 2. Interact with the agent as normal.
102
+ 3. Capture the terminal with your tool of choice:
103
+ - macOS: `Cmd+Shift+4` (selection) or `Cmd+Shift+3` (full)
104
+ - Linux: `gnome-screenshot -i` or `scrot -s`
105
+ - Windows: `Snipping Tool` (Win+Shift+S)
106
+ 4. Save the PNG into `docs/assets/` and reference it here.
107
+
108
+ ## Preview File
109
+
110
+ `docs/preview.txt` is auto-generated by the `update-preview` GitHub Actions workflow (every 6 hours and on push). It captures the live output of `npx tsx src/index.ts /help` and `npx tsx src/index.ts /stats`, so the preview always reflects the current code.
package/docs/stats.md ADDED
@@ -0,0 +1,5 @@
1
+ # Aether Live Stats
2
+ _Last updated: <timestamp>_
3
+
4
+ <!-- stats-start -->
5
+ <!-- stats-end -->
package/install.ps1 ADDED
@@ -0,0 +1,170 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Install the Aether CLI on Windows.
4
+
5
+ .DESCRIPTION
6
+ Detects Node.js (>= 20), offers to install via winget if missing,
7
+ clones or updates the repo, installs dependencies, and links the
8
+ CLI onto PATH.
9
+
10
+ .PARAMETER Yes
11
+ Run non-interactively (assume "yes" to all prompts).
12
+ #>
13
+ [CmdletBinding()]
14
+ param(
15
+ [switch]$Yes
16
+ )
17
+
18
+ $ErrorActionPreference = "Stop"
19
+ $ProgressPreference = "SilentlyContinue"
20
+
21
+ $RepoUrl = "https://github.com/hemansubedi10/aether.git"
22
+ $MinNode = 20
23
+ $InstallRoot = if ($env:AETHER_INSTALL_DIR) { $env:AETHER_INSTALL_DIR } else { "$env:USERPROFILE\.aether" }
24
+ $BinDir = if ($env:AETHER_BIN_DIR) { $env:AETHER_BIN_DIR } else { "$env:USERPROFILE\.local\bin" }
25
+
26
+ function Say($msg) { Write-Host "==> $msg" -ForegroundColor Cyan }
27
+ function Warn($msg) { Write-Warning $msg }
28
+ function Err($msg) { Write-Error $msg }
29
+ function Die($msg) { Write-Error "error: $msg"; exit 1 }
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # OS check
33
+ # ---------------------------------------------------------------------------
34
+ if ($IsWindows -ne $true) {
35
+ if ($IsLinux -eq $true -or $IsMacOS -eq $true) {
36
+ Die "This installer is for Windows. Use install.sh instead: bash install.sh"
37
+ }
38
+ }
39
+ Say "Detected platform: Windows"
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Node.js check
43
+ # ---------------------------------------------------------------------------
44
+ function Get-NodeMajorVersion {
45
+ $p = Get-Command node -ErrorAction SilentlyContinue
46
+ if (-not $p) { return $null }
47
+ try {
48
+ $v = (& node -p "process.version" 2>$null)
49
+ if ($v -match '\d+') { return [int]$Matches[0] }
50
+ } catch {}
51
+ return $null
52
+ }
53
+
54
+ $nodeMajor = Get-NodeMajorVersion
55
+ if (-not $nodeMajor) {
56
+ Say "Node.js >= v$MinNode not found."
57
+ $install = $false
58
+ if ($Yes) {
59
+ $install = $true
60
+ } else {
61
+ $ans = Read-Host "Install Node.js now via winget? [Y/n]"
62
+ if ([string]::IsNullOrWhiteSpace($ans)) { $ans = "y" }
63
+ $install = ($ans -match '^(y|yes)$')
64
+ }
65
+ if (-not $install) {
66
+ Die "Node.js >= v$MinNode is required. Install it manually from https://nodejs.org and re-run this script."
67
+ }
68
+ if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
69
+ Die "winget not found. Install Node.js manually from https://nodejs.org and re-run this script."
70
+ }
71
+ Say "Installing Node.js via winget..."
72
+ winget install --id OpenJS.NodeJS --source winget --silent --accept-source-agreements
73
+ $env:PATH = "$env:ProgramFiles\nodejs;$env:PATH"
74
+ $nodeMajor = Get-NodeMajorVersion
75
+ if (-not $nodeMajor) { Die "Node.js install failed. Install it manually from https://nodejs.org." }
76
+ }
77
+
78
+ if ($nodeMajor -lt $MinNode) {
79
+ Die "Node.js v$nodeMajor detected, but >= v$MinNode is required. Install Node.js >= v$MinNode from https://nodejs.org."
80
+ }
81
+ Say "Node.js $(node -v) detected."
82
+
83
+ if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
84
+ Die "npm not found. Reinstall Node.js from https://nodejs.org (include npm)."
85
+ }
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Acquire the source
89
+ # ---------------------------------------------------------------------------
90
+ $repoDir = Join-Path $InstallRoot "repo"
91
+ if (Test-Path $repoDir) {
92
+ if (Test-Path (Join-Path $repoDir ".git")) {
93
+ Say "Updating existing checkout at $repoDir ..."
94
+ & git -C $repoDir fetch --depth=1 origin
95
+ & git -C $repoDir reset --hard origin/main
96
+ & git -C $repoDir clean -fd
97
+ } else {
98
+ Say "Existing directory at $repoDir is not a git repo. Removing it..."
99
+ Remove-Item -Recurse -Force $repoDir
100
+ Say "Cloning $RepoUrl ..."
101
+ & git clone --depth 1 $RepoUrl $repoDir
102
+ }
103
+ } else {
104
+ Say "Cloning $RepoUrl ..."
105
+ & git clone --depth 1 $RepoUrl $repoDir
106
+ }
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Install dependencies
110
+ # ---------------------------------------------------------------------------
111
+ Say "Installing npm dependencies..."
112
+ & npm install --no-audit --no-fund --prefix $repoDir
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Build
116
+ # ---------------------------------------------------------------------------
117
+ if (Test-Path (Join-Path $repoDir "package.json")) {
118
+ $pkg = Get-Content (Join-Path $repoDir "package.json") -Raw
119
+ if ($pkg -match '"build"') {
120
+ Say "Building TypeScript..."
121
+ try {
122
+ & npm run build --prefix $repoDir
123
+ } catch {
124
+ Warn "Build failed; the CLI will run via tsx instead."
125
+ }
126
+ }
127
+ }
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # Link the CLI onto PATH
131
+ # ---------------------------------------------------------------------------
132
+ New-Item -ItemType Directory -Force -Path $BinDir | Out-Null
133
+ $linkAether = Join-Path $BinDir "aether.cmd"
134
+ $linkAetherShim = Join-Path $BinDir "aether"
135
+ $linkServer = Join-Path $BinDir "aether-server.cmd"
136
+ $linkServerShim = Join-Path $BinDir "aether-server"
137
+
138
+ # .cmd wrappers so cmd.exe finds them; shim scripts for PowerShell/WSL.
139
+ Set-Content -LiteralPath $linkAether -Value "@echo off`n`"%USERPROFILE%\nodejs\npx.exe`" tsx `"%USERPROFILE%\.aether\repo\src\index.ts`" %*" -Encoding UTF8
140
+ Set-Content -LiteralPath $linkServer -Value "@echo off`n`"%USERPROFILE%\nodejs\npx.exe`" tsx `"%USERPROFILE%\.aether\repo\src\server.ts`" %*" -Encoding UTF8
141
+ Set-Content -LiteralPath $linkAetherShim -Value "#!/usr/bin/env bash`nexec npx tsx `"$InstallRoot/repo/src/index.ts`" `"$@`" -Encoding UTF8
142
+ Set-Content -LiteralPath $linkServerShim -Value "#!/usr/bin/env bash`nexec npx tsx `"$InstallRoot/repo/src/server.ts`" `"$@`" -Encoding UTF8
143
+
144
+ # Ensure bin dir is on PATH for the current user
145
+ $machinePath = [Environment]::GetEnvironmentVariable("PATH", [EnvironmentVariableTarget]::Machine)
146
+ $userPath = [Environment]::GetEnvironmentVariable("PATH", [EnvironmentVariableTarget]::User)
147
+ if ($machinePath -notlike "*$BinDir*") {
148
+ Say "Adding $BinDir to machine PATH..."
149
+ [Environment]::SetEnvironmentVariable("PATH", "$machinePath;$BinDir", [EnvironmentVariableTarget]::Machine)
150
+ }
151
+ if ($userPath -notlike "*$BinDir*") {
152
+ Say "Adding $BinDir to user PATH..."
153
+ [Environment]::SetEnvironmentVariable("PATH", "$userPath;$BinDir", [EnvironmentVariableTarget]::User)
154
+ }
155
+ $env:PATH = "$BinDir;$env:PATH"
156
+
157
+ # ---------------------------------------------------------------------------
158
+ # Verify
159
+ # ---------------------------------------------------------------------------
160
+ Say "Aether installed successfully."
161
+ Write-Host ""
162
+ Write-Host "Run it with:"
163
+ Write-Host " aether ""your prompt here"""
164
+ Write-Host " aether # interactive TUI"
165
+ Write-Host " aether-server # start HTTP server"
166
+ Write-Host ""
167
+ Write-Host "If 'aether' is not found, start a new terminal or run:"
168
+ Write-Host " [Environment]::SetEnvironmentVariable(""PATH"", `"$env:PATH`", ""User"")"
169
+ Write-Host ""
170
+ Write-Host "Verify: aether --version"
package/install.sh ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # install.sh - Cross-platform installer for the Aether CLI (macOS / Linux)
4
+ #
5
+ # Usage:
6
+ # bash install.sh # interactive
7
+ # bash install.sh --yes # non-interactive
8
+ #
9
+ set -euo pipefail
10
+
11
+ REPO_URL="https://github.com/hemansubedi10/aether.git"
12
+ MIN_NODE_MAJOR=20
13
+ INSTALL_DIR="${AETHER_INSTALL_DIR:-$HOME/.aether}"
14
+ BIN_DIR="${AETHER_BIN_DIR:-$HOME/.local/bin}"
15
+
16
+ # ---------------------------------------------------------------------------
17
+ # Parse arguments
18
+ # ---------------------------------------------------------------------------
19
+ YES=0
20
+ for arg in "$@"; do
21
+ case "$arg" in
22
+ --yes|-y) YES=1 ;;
23
+ -h|--help)
24
+ echo "Usage: bash install.sh [--yes]"
25
+ echo "Install the Aether CLI on macOS or Linux."
26
+ exit 0
27
+ ;;
28
+ *)
29
+ echo "Unknown option: $arg" >&2
30
+ echo "Usage: bash install.sh [--yes]" >&2
31
+ exit 2
32
+ ;;
33
+ esac
34
+ done
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Helpers
38
+ # ---------------------------------------------------------------------------
39
+ say() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
40
+ warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$*" >&2; }
41
+ err() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; }
42
+ die() { err "$*"; exit 1; }
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # OS detection
46
+ # ---------------------------------------------------------------------------
47
+ OS="$(uname -s)"
48
+ case "$OS" in
49
+ Darwin) PLATFORM="macOS" ;;
50
+ Linux) PLATFORM="Linux" ;;
51
+ *) die "Unsupported OS: $OS. install.sh supports macOS and Linux. Use install.ps1 on Windows." ;;
52
+ esac
53
+ say "Detected platform: $PLATFORM"
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Node.js check
57
+ # ---------------------------------------------------------------------------
58
+ need_node=0
59
+ if ! command -v node >/dev/null 2>&1; then
60
+ need_node=1
61
+ else
62
+ NODE_VERSION="$(node -v 2>/dev/null | sed 's/^v//; s/\..*//')"
63
+ if [ -z "$NODE_VERSION" ]; then
64
+ need_node=1
65
+ elif [ "$NODE_VERSION" -lt "$MIN_NODE_MAJOR" ]; then
66
+ warn "Node.js v$NODE_VERSION detected, but >= v$MIN_NODE_MAJOR is required."
67
+ need_node=1
68
+ fi
69
+ fi
70
+
71
+ if [ "$need_node" -eq 1 ]; then
72
+ say "Node.js >= v$MIN_NODE_MAJOR not found."
73
+ if [ "$YES" -eq 1 ]; then
74
+ PROMPT="y"
75
+ else
76
+ printf 'Install Node.js now via your package manager? [Y/n] '
77
+ read -r PROMPT
78
+ PROMPT="${PROMPT:-y}"
79
+ fi
80
+ case "${PROMPT,,}" in
81
+ y|yes)
82
+ if command -v brew >/dev/null 2>&1; then
83
+ say "Installing Node.js via Homebrew..."
84
+ brew install node
85
+ elif command -v apt-get >/dev/null 2>&1; then
86
+ say "Installing Node.js via apt..."
87
+ sudo apt-get update -y && sudo apt-get install -y nodejs npm
88
+ elif command -v dnf >/dev/null 2>&1; then
89
+ say "Installing Node.js via dnf..."
90
+ sudo dnf install -y nodejs npm
91
+ elif command -v pacman >/dev/null 2>&1; then
92
+ say "Installing Node.js via pacman..."
93
+ sudo pacman -S --noconfirm nodejs npm
94
+ elif command -v zypper >/dev/null 2>&1; then
95
+ say "Installing Node.js via zypper..."
96
+ sudo zypper install -y nodejs npm
97
+ else
98
+ die "No supported package manager found. Install Node.js >= v$MIN_NODE_MAJOR manually from https://nodejs.org"
99
+ fi
100
+ ;;
101
+ n|no)
102
+ die "Node.js >= v$MIN_NODE_MAJOR is required. Install it manually from https://nodejs.org and re-run this script."
103
+ ;;
104
+ *)
105
+ die "Invalid response. Aborting."
106
+ ;;
107
+ esac
108
+
109
+ # Re-check after attempted install
110
+ if ! command -v node >/dev/null 2>&1; then
111
+ die "Node.js still not found after install attempt. Please install it manually."
112
+ fi
113
+ NODE_VERSION="$(node -v 2>/dev/null | sed 's/^v//; s/\..*//')"
114
+ [ "$NODE_VERSION" -ge "$MIN_NODE_MAJOR" ] || die "Node.js version still too old (v$NODE_VERSION). Need >= v$MIN_NODE_MAJOR."
115
+ fi
116
+ say "Node.js $(node -v) detected."
117
+
118
+ # npm is required
119
+ command -v npm >/dev/null 2>&1 || die "npm not found. Node.js installation should include npm."
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # Acquire the source
123
+ # ---------------------------------------------------------------------------
124
+ REPO_DIR="$INSTALL_DIR/repo"
125
+ if [ -d "$REPO_DIR" ] && [ -d "$REPO_DIR/.git" ]; then
126
+ say "Updating existing checkout at $REPO_DIR ..."
127
+ git -C "$REPO_DIR" fetch --depth=1 origin
128
+ git -C "$REPO_DIR" reset --hard origin/main
129
+ git -C "$REPO_DIR" clean -fd
130
+ elif [ -d "$REPO_DIR" ]; then
131
+ say "Existing directory at $REPO_DIR is not a git repo. Removing it..."
132
+ rm -rf "$REPO_DIR"
133
+ say "Cloning $REPO_URL ..."
134
+ git clone --depth 1 "$REPO_URL" "$REPO_DIR"
135
+ else
136
+ say "Cloning $REPO_URL ..."
137
+ git clone --depth 1 "$REPO_URL" "$REPO_DIR"
138
+ fi
139
+
140
+ # ---------------------------------------------------------------------------
141
+ # Install dependencies
142
+ # ---------------------------------------------------------------------------
143
+ say "Installing npm dependencies..."
144
+ (cd "$REPO_DIR" && npm install --no-audit --no-fund)
145
+
146
+ # ---------------------------------------------------------------------------
147
+ # Build (TypeScript -> dist/)
148
+ # ---------------------------------------------------------------------------
149
+ if [ -f "$REPO_DIR/package.json" ] && grep -q '"build"' "$REPO_DIR/package.json"; then
150
+ say "Building TypeScript..."
151
+ (cd "$REPO_DIR" && npm run build) || warn "Build failed; the CLI will run via tsx instead."
152
+ fi
153
+
154
+ # ---------------------------------------------------------------------------
155
+ # Link the CLI onto PATH
156
+ # ---------------------------------------------------------------------------
157
+ mkdir -p "$BIN_DIR"
158
+ ln -sf "$REPO_DIR/src/index.ts" "$BIN_DIR/aether"
159
+ ln -sf "$REPO_DIR/src/server.ts" "$BIN_DIR/aether-server"
160
+
161
+ # Ensure bin dir is on PATH for this session and future shells
162
+ export PATH="$BIN_DIR:$PATH"
163
+ SHELL_RC=""
164
+ case "$SHELL" in
165
+ */zsh) SHELL_RC="$HOME/.zshrc" ;;
166
+ */bash) SHELL_RC="$HOME/.bashrc" ;;
167
+ *) SHELL_RC="$HOME/.profile" ;;
168
+ esac
169
+ for rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do
170
+ if [ -f "$rc" ] && ! grep -q "AETHER_BIN_DIR\|$BIN_DIR" "$rc" 2>/dev/null; then
171
+ printf '\n# Aether CLI\nexport PATH="%s:$PATH"\n' "$BIN_DIR" >> "$rc"
172
+ say "Added $BIN_DIR to PATH in $rc"
173
+ fi
174
+ done
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # Verify
178
+ # ---------------------------------------------------------------------------
179
+ if command -v aether >/dev/null 2>&1; then
180
+ say "Aether installed successfully."
181
+ aether --version 2>/dev/null || true
182
+ echo
183
+ echo "Run it with:"
184
+ echo " aether \"your prompt here\""
185
+ echo " aether # interactive TUI"
186
+ echo " aether-server # start HTTP server"
187
+ echo
188
+ echo "If 'aether' is not found, open a new terminal or run:"
189
+ echo " source ${SHELL_RC:-$HOME/.profile}"
190
+ else
191
+ warn "Installation finished but 'aether' is not on PATH yet."
192
+ warn "Open a new terminal, or run: source ${SHELL_RC:-$HOME/.profile}"
193
+ echo
194
+ echo "Then run:"
195
+ echo " aether --version"
196
+ fi
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@hemansubedi/aether-ai",
3
+ "version": "1.0.0",
4
+ "description": "The Aether CLI: the free, unlimited, multi-provider LLM CLI - install like KiloCode, run like Claude Code.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "aether-ai": "dist/index.js",
9
+ "aether-ai-server": "dist/server.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "start": "node dist/index.js",
14
+ "dev": "tsx src/index.ts"
15
+ },
16
+ "engines": { "node": ">=20" },
17
+ "keywords": ["llm", "cli", "ai", "router", "ollama", "openrouter", "free"],
18
+ "author": "Heman Subedi <hemansubedi66@gmail.com>",
19
+ "license": "MIT",
20
+ "homepage": "https://github.com/hemansubedi10/aether",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/hemansubedi10/aether.git"
24
+ },
25
+ "dependencies": {},
26
+ "devDependencies": {
27
+ "typescript": "^7.0.0",
28
+ "tsx": "^4.0.0",
29
+ "@types/node": "^22.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }
@@ -0,0 +1,62 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { PROVIDER_REGISTRY } from "../src/providers/registry.js";
4
+
5
+ function parseHealth(statsText: string): { total: number; healthy: number } {
6
+ const lines = statsText.split("\n");
7
+ let inHealth = false;
8
+ let healthy = 0;
9
+ let total = 0;
10
+ for (const raw of lines) {
11
+ const line = raw.trim();
12
+ if (line.startsWith("Provider health:")) { inHealth = true; continue; }
13
+ if (!inHealth) continue;
14
+ if (!line) continue;
15
+ total++;
16
+ if (/^\s*[\w-]+:\s+healthy\b/.test(line)) healthy++;
17
+ }
18
+ return { total, healthy };
19
+ }
20
+
21
+ function modelCount(): number {
22
+ let n = 0;
23
+ for (const p of PROVIDER_REGISTRY) if (p.enabled) n += p.models.length;
24
+ return n;
25
+ }
26
+
27
+ function main() {
28
+ const statsPath = path.join(process.cwd(), "docs", "stats.md");
29
+ const statsTxtPath = path.join(process.cwd(), "docs", "stats.txt");
30
+ const statsText = fs.existsSync(statsTxtPath) ? fs.readFileSync(statsTxtPath, "utf8") : "";
31
+ const { total, healthy } = parseHealth(statsText);
32
+ const models = modelCount();
33
+
34
+ let md = fs.existsSync(statsPath) ? fs.readFileSync(statsPath, "utf8") : "";
35
+
36
+ const stamp = new Date().toISOString().replace("T", " ").slice(0, 19) + " UTC";
37
+ md = md.replace(/_Last updated:.*_/, "_Last updated: " + stamp + "_");
38
+
39
+ const card = [
40
+ "<!-- stats-start -->",
41
+ "| Metric | Value |",
42
+ "|--------|-------|",
43
+ "| Providers | " + total + " |",
44
+ "| Healthy | " + healthy + " |",
45
+ "| Unhealthy / Open | " + (total - healthy) + " |",
46
+ "| Models (enabled) | " + models + " |",
47
+ "<!-- stats-end -->",
48
+ ].join("\n");
49
+
50
+ const start = md.indexOf("<!-- stats-start -->");
51
+ const end = md.indexOf("<!-- stats-end -->");
52
+ if (start !== -1 && end !== -1) {
53
+ md = md.slice(0, start) + card + md.slice(end + "<!-- stats-end -->".length);
54
+ } else {
55
+ md = md.trimEnd() + "\n\n" + card + "\n";
56
+ }
57
+
58
+ fs.writeFileSync(statsPath, md, "utf8");
59
+ console.log("Updated docs/stats.md");
60
+ }
61
+
62
+ main();
@@ -0,0 +1,17 @@
1
+ $ErrorActionPreference = "Stop"
2
+ $src = Get-Content -LiteralPath "C:\Users\heman\aether\src\index.ts" -Raw
3
+
4
+ $src = $src.Replace("function makeRouterAdapter(engine: RouterEngine) {", "function makeRouterAdapter(engine: RouterEngine, combos: any) {")
5
+
6
+ $src = $src.Replace("`n configs: engine.configs_,", "`n combos: combos,")
7
+
8
+ $src = $src.Replace("`n listAllModels: () => engine.listFreeModels(),", "`n combos: combos,`n applyCombo: (name: string) => {`n const c = combos.select(name);`n if (!c) return 'Combo ' + name + ' not found.';`n const summary = engine.applyCombo(c);`n adapter.activeProvider = c.providers[0];`n return summary;`n },`n listAllModels: () => engine.listFreeModels(),")
9
+
10
+ $src = $src.Replace("import { KeyManager } from './keys.js';", "import { KeyManager } from './keys.js';`nimport { ComboManager } from './combos.js';")
11
+
12
+ $src = $src.Replace(" const engine = new RouterEngine(undefined, undefined, KeyManager.instance());`n const router = makeRouterAdapter(engine);`n const registry = new ToolRegistry();", " const engine = new RouterEngine(undefined, undefined, KeyManager.instance());`n const combos = ComboManager.instance();`n const router = makeRouterAdapter(engine, combos);`n const registry = new ToolRegistry();")
13
+
14
+ $src = $src.Replace(" const engine = new RouterEngine(undefined, undefined, KeyManager.instance());`n const router = makeRouterAdapter(engine);`n const registry = new ToolRegistry();`n for (const make of [makeReadFileTool", " const engine = new RouterEngine(undefined, undefined, KeyManager.instance());`n const combos = ComboManager.instance();`n const router = makeRouterAdapter(engine, combos);`n const registry = new ToolRegistry();`n for (const make of [makeReadFileTool")
15
+
16
+ Set-Content -LiteralPath "C:\Users\heman\aether\src\index.ts" -Value $src -NoNewline
17
+ Write-Host "edited index.ts"
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # Bump version, commit, tag, and push � GitHub Action publishes automatically.
3
+ set -euo pipefail
4
+ VERSION="${1:-patch}"
5
+ npm version $VERSION
6
+ git push --follow-tags
7
+ echo "Pushed tag. GitHub Action will publish to npm."