adanos-cli 1.20.2__tar.gz → 1.20.4__tar.gz
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.
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/.github/workflows/cli-binaries.yml +38 -11
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/CHANGELOG.md +11 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/PKG-INFO +33 -2
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/README.md +32 -1
- adanos_cli-1.20.4/install.ps1 +40 -0
- adanos_cli-1.20.4/install.sh +110 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/scripts/build_cli_binary.py +1 -1
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/scripts/generate_homebrew_formula.py +2 -2
- adanos_cli-1.20.4/scripts/pyinstaller_entrypoint.py +8 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/__init__.py +1 -1
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/main.py +63 -1
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_distribution.py +75 -1
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_onboarding.py +43 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/.github/workflows/ci.yml +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/.github/workflows/publish-pypi.yml +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/.gitignore +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/LICENSE +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/pyproject.toml +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/__main__.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/activity_log.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/commands/__init__.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/commands/auth.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/commands/config.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/commands/extensions.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/config.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/endpoints.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/nlp.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/shell_history.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/summaries.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/tty.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/update_notifier.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/utils.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/src/adanos_cli/watchlists.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/__init__.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/conftest.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_account.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_activity_log.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_agent_contract.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_auth.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_config_runtime.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_diagnostics.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_endpoint_coverage.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_extensions.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_logs.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_power_features.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_shell.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_tty.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_update_notifier.py +0 -0
- {adanos_cli-1.20.2 → adanos_cli-1.20.4}/tests/test_watchlists.py +0 -0
|
@@ -5,6 +5,9 @@ on:
|
|
|
5
5
|
types:
|
|
6
6
|
- published
|
|
7
7
|
|
|
8
|
+
env:
|
|
9
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
10
|
+
|
|
8
11
|
jobs:
|
|
9
12
|
build:
|
|
10
13
|
name: Build CLI Binary (${{ matrix.target }})
|
|
@@ -37,7 +40,7 @@ jobs:
|
|
|
37
40
|
run: python scripts/build_cli_binary.py --output-dir dist-binaries
|
|
38
41
|
|
|
39
42
|
- name: Upload binary artifacts
|
|
40
|
-
uses: actions/upload-artifact@
|
|
43
|
+
uses: actions/upload-artifact@v6
|
|
41
44
|
with:
|
|
42
45
|
name: cli-binary-${{ matrix.target }}
|
|
43
46
|
if-no-files-found: error
|
|
@@ -55,7 +58,7 @@ jobs:
|
|
|
55
58
|
ref: ${{ github.event.release.tag_name }}
|
|
56
59
|
|
|
57
60
|
- name: Download binary artifacts
|
|
58
|
-
uses: actions/download-artifact@
|
|
61
|
+
uses: actions/download-artifact@v7
|
|
59
62
|
with:
|
|
60
63
|
path: release-artifacts
|
|
61
64
|
|
|
@@ -79,10 +82,9 @@ jobs:
|
|
|
79
82
|
ls -la release-upload
|
|
80
83
|
|
|
81
84
|
- name: Publish release assets
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
files: release-upload/*
|
|
85
|
+
env:
|
|
86
|
+
GH_TOKEN: ${{ github.token }}
|
|
87
|
+
run: gh release upload "${{ github.event.release.tag_name }}" release-upload/* --clobber
|
|
86
88
|
|
|
87
89
|
homebrew-formula:
|
|
88
90
|
name: Generate Homebrew Formula
|
|
@@ -90,13 +92,15 @@ jobs:
|
|
|
90
92
|
runs-on: ubuntu-latest
|
|
91
93
|
permissions:
|
|
92
94
|
contents: write
|
|
95
|
+
env:
|
|
96
|
+
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
|
93
97
|
steps:
|
|
94
98
|
- uses: actions/checkout@v5
|
|
95
99
|
with:
|
|
96
100
|
ref: ${{ github.event.release.tag_name }}
|
|
97
101
|
|
|
98
102
|
- name: Download binary artifacts
|
|
99
|
-
uses: actions/download-artifact@
|
|
103
|
+
uses: actions/download-artifact@v7
|
|
100
104
|
with:
|
|
101
105
|
path: formula-artifacts
|
|
102
106
|
|
|
@@ -129,13 +133,36 @@ jobs:
|
|
|
129
133
|
--output dist/homebrew/adanos-cli.rb
|
|
130
134
|
|
|
131
135
|
- name: Upload Homebrew formula artifact
|
|
132
|
-
uses: actions/upload-artifact@
|
|
136
|
+
uses: actions/upload-artifact@v6
|
|
133
137
|
with:
|
|
134
138
|
name: homebrew-formula
|
|
135
139
|
path: dist/homebrew/adanos-cli.rb
|
|
136
140
|
|
|
137
141
|
- name: Attach Homebrew formula to release
|
|
138
|
-
|
|
142
|
+
env:
|
|
143
|
+
GH_TOKEN: ${{ github.token }}
|
|
144
|
+
run: gh release upload "${{ github.event.release.tag_name }}" dist/homebrew/adanos-cli.rb --clobber
|
|
145
|
+
|
|
146
|
+
- name: Checkout Homebrew tap repository
|
|
147
|
+
if: ${{ env.HOMEBREW_TAP_TOKEN != '' }}
|
|
148
|
+
uses: actions/checkout@v5
|
|
139
149
|
with:
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
repository: adanos-software/homebrew-tap
|
|
151
|
+
token: ${{ env.HOMEBREW_TAP_TOKEN }}
|
|
152
|
+
path: homebrew-tap
|
|
153
|
+
|
|
154
|
+
- name: Publish formula to Homebrew tap
|
|
155
|
+
if: ${{ env.HOMEBREW_TAP_TOKEN != '' }}
|
|
156
|
+
run: |
|
|
157
|
+
mkdir -p homebrew-tap/Formula
|
|
158
|
+
cp dist/homebrew/adanos-cli.rb homebrew-tap/Formula/adanos-cli.rb
|
|
159
|
+
cd homebrew-tap
|
|
160
|
+
git config user.name "github-actions[bot]"
|
|
161
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
162
|
+
git add Formula/adanos-cli.rb
|
|
163
|
+
if git diff --cached --quiet; then
|
|
164
|
+
echo "Homebrew tap already up to date."
|
|
165
|
+
exit 0
|
|
166
|
+
fi
|
|
167
|
+
git commit -m "Update adanos-cli formula for ${{ github.event.release.tag_name }}"
|
|
168
|
+
git push origin HEAD:main
|
|
@@ -5,6 +5,17 @@ All notable changes to `adanos-cli` will be documented in this file.
|
|
|
5
5
|
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
6
6
|
Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
|
7
7
|
|
|
8
|
+
## [1.20.4] - 2026-03-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Added `adanos onboard recover --email ...` so existing users can trigger the secure email-based recovery flow from the CLI without exposing recovery tokens in terminal output.
|
|
12
|
+
|
|
13
|
+
## [1.20.3] - 2026-03-16
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Standalone release binaries now boot through a package-safe PyInstaller entry point instead of failing on relative imports.
|
|
17
|
+
- Shell installer checksum verification now accepts the `release-upload/` paths emitted by the binary release workflow.
|
|
18
|
+
|
|
8
19
|
## [1.20.2] - 2026-03-15
|
|
9
20
|
|
|
10
21
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: adanos-cli
|
|
3
|
-
Version: 1.20.
|
|
3
|
+
Version: 1.20.4
|
|
4
4
|
Summary: CLI for the Adanos Finance Sentiment API
|
|
5
5
|
Project-URL: Homepage, https://adanos.org
|
|
6
6
|
Project-URL: API_Docs, https://api.adanos.org/docs
|
|
@@ -43,6 +43,31 @@ The CLI is versioned independently from the API backend. It targets the public A
|
|
|
43
43
|
pipx install adanos-cli
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
### cURL
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
curl -fsSL https://raw.githubusercontent.com/adanos-software/adanos-cli/main/install.sh | bash
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The shell installer downloads the latest standalone binary for:
|
|
53
|
+
- macOS arm64
|
|
54
|
+
- macOS x86_64
|
|
55
|
+
- Linux x86_64
|
|
56
|
+
|
|
57
|
+
By default it installs to `~/.local/bin`. Override with `ADANOS_INSTALL_DIR=/your/path`.
|
|
58
|
+
|
|
59
|
+
### Homebrew (macOS / Linux)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
brew install adanos-software/tap/adanos-cli
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### PowerShell (Windows)
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
irm https://raw.githubusercontent.com/adanos-software/adanos-cli/main/install.ps1 | iex
|
|
69
|
+
```
|
|
70
|
+
|
|
46
71
|
### Plain pip
|
|
47
72
|
|
|
48
73
|
```bash
|
|
@@ -110,6 +135,12 @@ Persist a key locally:
|
|
|
110
135
|
adanos login --api-key sk_live_xxx
|
|
111
136
|
```
|
|
112
137
|
|
|
138
|
+
Request a recovery email for an existing account:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
adanos onboard recover --email you@example.com
|
|
142
|
+
```
|
|
143
|
+
|
|
113
144
|
Use profiles:
|
|
114
145
|
|
|
115
146
|
```bash
|
|
@@ -198,7 +229,7 @@ Tagged releases build standalone archives for:
|
|
|
198
229
|
- macOS x86_64
|
|
199
230
|
- Linux x86_64
|
|
200
231
|
|
|
201
|
-
The repo also generates a Homebrew formula artifact for each tagged binary release.
|
|
232
|
+
The repo also generates a Homebrew formula artifact for each tagged binary release and can publish it to `adanos-software/homebrew-tap` when `HOMEBREW_TAP_TOKEN` is configured.
|
|
202
233
|
|
|
203
234
|
PyPI publishing also happens from this repo, not from the API monorepo.
|
|
204
235
|
|
|
@@ -17,6 +17,31 @@ The CLI is versioned independently from the API backend. It targets the public A
|
|
|
17
17
|
pipx install adanos-cli
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
### cURL
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
curl -fsSL https://raw.githubusercontent.com/adanos-software/adanos-cli/main/install.sh | bash
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The shell installer downloads the latest standalone binary for:
|
|
27
|
+
- macOS arm64
|
|
28
|
+
- macOS x86_64
|
|
29
|
+
- Linux x86_64
|
|
30
|
+
|
|
31
|
+
By default it installs to `~/.local/bin`. Override with `ADANOS_INSTALL_DIR=/your/path`.
|
|
32
|
+
|
|
33
|
+
### Homebrew (macOS / Linux)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
brew install adanos-software/tap/adanos-cli
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### PowerShell (Windows)
|
|
40
|
+
|
|
41
|
+
```powershell
|
|
42
|
+
irm https://raw.githubusercontent.com/adanos-software/adanos-cli/main/install.ps1 | iex
|
|
43
|
+
```
|
|
44
|
+
|
|
20
45
|
### Plain pip
|
|
21
46
|
|
|
22
47
|
```bash
|
|
@@ -84,6 +109,12 @@ Persist a key locally:
|
|
|
84
109
|
adanos login --api-key sk_live_xxx
|
|
85
110
|
```
|
|
86
111
|
|
|
112
|
+
Request a recovery email for an existing account:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
adanos onboard recover --email you@example.com
|
|
116
|
+
```
|
|
117
|
+
|
|
87
118
|
Use profiles:
|
|
88
119
|
|
|
89
120
|
```bash
|
|
@@ -172,7 +203,7 @@ Tagged releases build standalone archives for:
|
|
|
172
203
|
- macOS x86_64
|
|
173
204
|
- Linux x86_64
|
|
174
205
|
|
|
175
|
-
The repo also generates a Homebrew formula artifact for each tagged binary release.
|
|
206
|
+
The repo also generates a Homebrew formula artifact for each tagged binary release and can publish it to `adanos-software/homebrew-tap` when `HOMEBREW_TAP_TOKEN` is configured.
|
|
176
207
|
|
|
177
208
|
PyPI publishing also happens from this repo, not from the API monorepo.
|
|
178
209
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
$ErrorActionPreference = "Stop"
|
|
2
|
+
|
|
3
|
+
$packageName = "adanos-cli"
|
|
4
|
+
|
|
5
|
+
function Write-Info($message) {
|
|
6
|
+
Write-Host $message
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function Get-PythonCommand {
|
|
10
|
+
foreach ($candidate in @("py", "python", "python3")) {
|
|
11
|
+
if (Get-Command $candidate -ErrorAction SilentlyContinue) {
|
|
12
|
+
return $candidate
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
throw "Python is required. Install Python 3.10+ and re-run this script."
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
$python = Get-PythonCommand
|
|
19
|
+
$pipx = Get-Command pipx -ErrorAction SilentlyContinue
|
|
20
|
+
|
|
21
|
+
if (-not $pipx) {
|
|
22
|
+
Write-Info "Installing pipx..."
|
|
23
|
+
& $python -m pip install --user --upgrade pip pipx
|
|
24
|
+
& $python -m pipx ensurepath | Out-Null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
$pipx = Get-Command pipx -ErrorAction SilentlyContinue
|
|
28
|
+
|
|
29
|
+
if ($pipx) {
|
|
30
|
+
Write-Info "Installing $packageName with pipx..."
|
|
31
|
+
& $pipx.Source install --force $packageName
|
|
32
|
+
} else {
|
|
33
|
+
Write-Info "pipx is not available in the current session. Falling back to pip --user."
|
|
34
|
+
& $python -m pip install --user --upgrade $packageName
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Write-Info ""
|
|
38
|
+
Write-Info "Installed $packageName."
|
|
39
|
+
Write-Info "Verify with:"
|
|
40
|
+
Write-Info " adanos --version"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
REPO="adanos-software/adanos-cli"
|
|
5
|
+
LATEST_BASE_URL="https://github.com/${REPO}/releases/latest/download"
|
|
6
|
+
DEFAULT_INSTALL_DIR="${HOME}/.local/bin"
|
|
7
|
+
INSTALL_DIR="${ADANOS_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
|
|
8
|
+
|
|
9
|
+
log() {
|
|
10
|
+
printf '%s\n' "$*"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fail() {
|
|
14
|
+
printf 'error: %s\n' "$*" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
require_cmd() {
|
|
19
|
+
command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
detect_asset() {
|
|
23
|
+
local os arch
|
|
24
|
+
os="$(uname -s)"
|
|
25
|
+
arch="$(uname -m)"
|
|
26
|
+
|
|
27
|
+
case "$os" in
|
|
28
|
+
Darwin)
|
|
29
|
+
case "$arch" in
|
|
30
|
+
arm64|aarch64) printf 'adanos-darwin-arm64.tar.gz' ;;
|
|
31
|
+
x86_64|amd64) printf 'adanos-darwin-x86_64.tar.gz' ;;
|
|
32
|
+
*) fail "unsupported macOS architecture: ${arch}" ;;
|
|
33
|
+
esac
|
|
34
|
+
;;
|
|
35
|
+
Linux)
|
|
36
|
+
case "$arch" in
|
|
37
|
+
x86_64|amd64) printf 'adanos-linux-x86_64.tar.gz' ;;
|
|
38
|
+
*) fail "unsupported Linux architecture: ${arch}" ;;
|
|
39
|
+
esac
|
|
40
|
+
;;
|
|
41
|
+
*)
|
|
42
|
+
fail "unsupported operating system: ${os}"
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
verify_checksum() {
|
|
48
|
+
local archive_path checksums_path asset_name expected actual
|
|
49
|
+
archive_path="$1"
|
|
50
|
+
checksums_path="$2"
|
|
51
|
+
asset_name="$3"
|
|
52
|
+
|
|
53
|
+
expected="$(awk -v asset="$asset_name" '$2 ~ ("(^|/)" asset "$") {print $1; exit}' "$checksums_path")"
|
|
54
|
+
[ -n "$expected" ] || fail "could not find checksum for ${asset_name}"
|
|
55
|
+
|
|
56
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
57
|
+
actual="$(shasum -a 256 "$archive_path" | awk '{print $1}')"
|
|
58
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
59
|
+
actual="$(sha256sum "$archive_path" | awk '{print $1}')"
|
|
60
|
+
else
|
|
61
|
+
fail "missing checksum tool (shasum or sha256sum)"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
[ "$actual" = "$expected" ] || fail "checksum mismatch for ${asset_name}"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
print_path_hint() {
|
|
68
|
+
case ":${PATH}:" in
|
|
69
|
+
*":${INSTALL_DIR}:"*) ;;
|
|
70
|
+
*)
|
|
71
|
+
log
|
|
72
|
+
log "Add this to your shell profile if needed:"
|
|
73
|
+
log " export PATH=\"${INSTALL_DIR}:\$PATH\""
|
|
74
|
+
;;
|
|
75
|
+
esac
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main() {
|
|
79
|
+
require_cmd curl
|
|
80
|
+
require_cmd tar
|
|
81
|
+
require_cmd mktemp
|
|
82
|
+
require_cmd install
|
|
83
|
+
|
|
84
|
+
local asset_name tmp_dir archive_path checksums_path
|
|
85
|
+
asset_name="$(detect_asset)"
|
|
86
|
+
tmp_dir="$(mktemp -d)"
|
|
87
|
+
archive_path="${tmp_dir}/${asset_name}"
|
|
88
|
+
checksums_path="${tmp_dir}/SHA256SUMS.txt"
|
|
89
|
+
|
|
90
|
+
log "Downloading ${asset_name}..."
|
|
91
|
+
curl -fsSL "${LATEST_BASE_URL}/${asset_name}" -o "$archive_path"
|
|
92
|
+
curl -fsSL "${LATEST_BASE_URL}/SHA256SUMS.txt" -o "$checksums_path"
|
|
93
|
+
|
|
94
|
+
verify_checksum "$archive_path" "$checksums_path" "$asset_name"
|
|
95
|
+
|
|
96
|
+
mkdir -p "${INSTALL_DIR}"
|
|
97
|
+
tar -xzf "$archive_path" -C "$tmp_dir"
|
|
98
|
+
install -m 0755 "${tmp_dir}/adanos" "${INSTALL_DIR}/adanos"
|
|
99
|
+
|
|
100
|
+
rm -rf "$tmp_dir"
|
|
101
|
+
|
|
102
|
+
log
|
|
103
|
+
log "Installed adanos to ${INSTALL_DIR}/adanos"
|
|
104
|
+
print_path_hint
|
|
105
|
+
log
|
|
106
|
+
log "Verify with:"
|
|
107
|
+
log " adanos --version"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main "$@"
|
|
@@ -15,7 +15,7 @@ from pathlib import Path
|
|
|
15
15
|
|
|
16
16
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
17
17
|
CLI_SRC = REPO_ROOT / "src"
|
|
18
|
-
CLI_ENTRYPOINT =
|
|
18
|
+
CLI_ENTRYPOINT = REPO_ROOT / "scripts" / "pyinstaller_entrypoint.py"
|
|
19
19
|
PACKAGE_INIT = CLI_SRC / "adanos_cli" / "__init__.py"
|
|
20
20
|
|
|
21
21
|
|
|
@@ -19,7 +19,7 @@ def render_formula(
|
|
|
19
19
|
) -> str:
|
|
20
20
|
return f"""class AdanosCli < Formula
|
|
21
21
|
desc "Comprehensive CLI for the Adanos Finance Sentiment API"
|
|
22
|
-
homepage "https://
|
|
22
|
+
homepage "https://adanos.org"
|
|
23
23
|
version "{version}"
|
|
24
24
|
|
|
25
25
|
on_macos do
|
|
@@ -42,7 +42,7 @@ def render_formula(
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
test do
|
|
45
|
-
assert_match "adanos", shell_output("#{bin}/adanos --help")
|
|
45
|
+
assert_match "adanos", shell_output("#{{bin}}/adanos --help")
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
"""
|
|
@@ -67,6 +67,7 @@ from .watchlists import (
|
|
|
67
67
|
|
|
68
68
|
SUPPORT_CONTACT_EMAIL = "support@adanos.org"
|
|
69
69
|
PAID_ACCOUNT_TYPES = {"hobby", "professional"}
|
|
70
|
+
DEFAULT_RECOVERY_REQUEST_URL = "https://adanos.org/api/recover"
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
def _load_sdk_client_class() -> Any:
|
|
@@ -95,7 +96,9 @@ def _print_onboarding_guide(base_url: str, *, has_api_key: bool = False) -> None
|
|
|
95
96
|
print("3) Manual alternative:")
|
|
96
97
|
print(' adanos onboard register --name "Your Name" --email "you@example.com" --purpose "CLI usage for stocks and crypto"')
|
|
97
98
|
print(" adanos onboard redeem --token <delivery_token> --save")
|
|
98
|
-
print("4)
|
|
99
|
+
print("4) If you lost access to an existing key:")
|
|
100
|
+
print(' adanos onboard recover --email "you@example.com"')
|
|
101
|
+
print("5) Start using the API:")
|
|
99
102
|
print(' adanos ask "How does TSLA look?"')
|
|
100
103
|
print(f"API base URL: {base_url}")
|
|
101
104
|
|
|
@@ -916,6 +919,14 @@ def _request_onboard_redeem(base_url: str, token: str) -> tuple[int, dict[str, A
|
|
|
916
919
|
return response.status_code, data, message
|
|
917
920
|
|
|
918
921
|
|
|
922
|
+
def _request_onboard_recover(recovery_url: str, payload: dict[str, str]) -> tuple[int, dict[str, Any] | None, str]:
|
|
923
|
+
with httpx.Client(timeout=30.0) as http:
|
|
924
|
+
response = http.post(recovery_url, json=payload)
|
|
925
|
+
data = _decode_json_dict(response)
|
|
926
|
+
message = _extract_error_message(response)
|
|
927
|
+
return response.status_code, data, message
|
|
928
|
+
|
|
929
|
+
|
|
919
930
|
def _request_account_status(
|
|
920
931
|
base_url: str, api_key: str, *, timeout_s: float = 30.0
|
|
921
932
|
) -> tuple[int, dict[str, Any] | None, str, dict[str, str]]:
|
|
@@ -1628,6 +1639,46 @@ def _run_onboard_redeem(args: Namespace, base_url: str, *, json_mode: bool) -> i
|
|
|
1628
1639
|
return 0
|
|
1629
1640
|
|
|
1630
1641
|
|
|
1642
|
+
def _run_onboard_recover(args: Namespace, *, json_mode: bool) -> int:
|
|
1643
|
+
recovery_url = str(args.recovery_url or DEFAULT_RECOVERY_REQUEST_URL).strip()
|
|
1644
|
+
status_code, data, message = _request_onboard_recover(recovery_url, {"email": args.email})
|
|
1645
|
+
if status_code not in {200, 202}:
|
|
1646
|
+
_emit_error(
|
|
1647
|
+
json_mode=json_mode,
|
|
1648
|
+
code="onboard_recover_failed",
|
|
1649
|
+
message=message if message else "Recovery request failed",
|
|
1650
|
+
hint="Retry later or open https://adanos.org/key to use the hosted recovery form.",
|
|
1651
|
+
status_code=status_code,
|
|
1652
|
+
)
|
|
1653
|
+
return 1
|
|
1654
|
+
|
|
1655
|
+
if not isinstance(data, dict) or data.get("success") is not True:
|
|
1656
|
+
_emit_error(
|
|
1657
|
+
json_mode=json_mode,
|
|
1658
|
+
code="onboard_recover_failed",
|
|
1659
|
+
message="Response did not include structured recovery data",
|
|
1660
|
+
status_code=status_code,
|
|
1661
|
+
)
|
|
1662
|
+
return 1
|
|
1663
|
+
|
|
1664
|
+
if json_mode:
|
|
1665
|
+
print_json(
|
|
1666
|
+
with_json_metadata(
|
|
1667
|
+
data,
|
|
1668
|
+
kind="onboard_recovery",
|
|
1669
|
+
command="onboard",
|
|
1670
|
+
subcommand="recover",
|
|
1671
|
+
)
|
|
1672
|
+
)
|
|
1673
|
+
else:
|
|
1674
|
+
print("Recovery request accepted.")
|
|
1675
|
+
print(str(data.get("message") or "If an account exists, further recovery instructions will be sent separately."))
|
|
1676
|
+
print("Next step:")
|
|
1677
|
+
print(" Check your email for the secure recovery link.")
|
|
1678
|
+
|
|
1679
|
+
return 0
|
|
1680
|
+
|
|
1681
|
+
|
|
1631
1682
|
def _run_onboard_wizard(args: Namespace, base_url: str, *, json_mode: bool, runtime_has_key: bool) -> int:
|
|
1632
1683
|
if json_mode:
|
|
1633
1684
|
_emit_error(
|
|
@@ -1789,6 +1840,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1789
1840
|
p_onboard_redeem.add_argument("--json", action="store_true")
|
|
1790
1841
|
p_onboard_redeem.set_defaults(_handler="onboard_redeem")
|
|
1791
1842
|
|
|
1843
|
+
p_onboard_recover = onboard_subs.add_parser("recover", help="Request a recovery email for an existing API key")
|
|
1844
|
+
p_onboard_recover.add_argument("--email", required=True, help="Registered email address for the API account")
|
|
1845
|
+
p_onboard_recover.add_argument("--recovery-url", help=argparse.SUPPRESS)
|
|
1846
|
+
p_onboard_recover.add_argument("--json", action="store_true")
|
|
1847
|
+
p_onboard_recover.set_defaults(_handler="onboard_recover")
|
|
1848
|
+
|
|
1792
1849
|
p_onboard.set_defaults(_handler="onboard_guide")
|
|
1793
1850
|
|
|
1794
1851
|
p_auth = subs.add_parser("auth", help="Manage local auth profiles and active credentials")
|
|
@@ -2111,6 +2168,7 @@ def _requires_api_key(args: Namespace) -> bool:
|
|
|
2111
2168
|
"onboard_wizard",
|
|
2112
2169
|
"onboard_register",
|
|
2113
2170
|
"onboard_redeem",
|
|
2171
|
+
"onboard_recover",
|
|
2114
2172
|
"auth_login",
|
|
2115
2173
|
"auth_logout",
|
|
2116
2174
|
"auth_list",
|
|
@@ -3354,6 +3412,7 @@ def _main_impl(raw_argv: list[str], *, argv_supplied: bool) -> int:
|
|
|
3354
3412
|
"adanos onboard wizard",
|
|
3355
3413
|
'adanos onboard register --name "Your Name" --email "you@example.com" --purpose "CLI usage for stocks and crypto"',
|
|
3356
3414
|
"adanos onboard redeem --token <delivery_token> --save",
|
|
3415
|
+
'adanos onboard recover --email "you@example.com"',
|
|
3357
3416
|
],
|
|
3358
3417
|
},
|
|
3359
3418
|
},
|
|
@@ -3380,6 +3439,9 @@ def _main_impl(raw_argv: list[str], *, argv_supplied: bool) -> int:
|
|
|
3380
3439
|
if handler == "onboard_redeem":
|
|
3381
3440
|
return _run_onboard_redeem(args, runtime_cfg.base_url, json_mode=args.json)
|
|
3382
3441
|
|
|
3442
|
+
if handler == "onboard_recover":
|
|
3443
|
+
return _run_onboard_recover(args, json_mode=args.json)
|
|
3444
|
+
|
|
3383
3445
|
if _requires_api_key(args) and not runtime_cfg.api_key:
|
|
3384
3446
|
if args.json:
|
|
3385
3447
|
_emit_error(
|
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import os
|
|
6
|
+
import stat
|
|
7
|
+
import subprocess
|
|
5
8
|
import sys
|
|
9
|
+
from pathlib import Path
|
|
6
10
|
|
|
7
11
|
from adanos_cli import __version__
|
|
8
12
|
import scripts.generate_homebrew_formula as homebrew_formula
|
|
9
13
|
from scripts.build_cli_binary import CLI_ENTRYPOINT, CLI_SRC, build_pyinstaller_command, detect_target, read_package_version
|
|
10
14
|
from scripts.generate_homebrew_formula import render_formula
|
|
11
15
|
|
|
16
|
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
17
|
+
|
|
12
18
|
|
|
13
19
|
def test_render_formula_includes_all_targets() -> None:
|
|
14
20
|
formula = render_formula(
|
|
@@ -62,6 +68,13 @@ def test_build_pyinstaller_command_uses_repo_local_paths(tmp_path) -> None:
|
|
|
62
68
|
assert "stocksentiment" in command
|
|
63
69
|
|
|
64
70
|
|
|
71
|
+
def test_pyinstaller_entrypoint_imports_main() -> None:
|
|
72
|
+
entrypoint = CLI_ENTRYPOINT.read_text(encoding="utf-8")
|
|
73
|
+
|
|
74
|
+
assert "from adanos_cli.main import main" in entrypoint
|
|
75
|
+
assert "raise SystemExit(main())" in entrypoint
|
|
76
|
+
|
|
77
|
+
|
|
65
78
|
def test_render_formula_includes_install_and_test_blocks() -> None:
|
|
66
79
|
formula = render_formula(
|
|
67
80
|
version="1.20.0",
|
|
@@ -74,8 +87,9 @@ def test_render_formula_includes_install_and_test_blocks() -> None:
|
|
|
74
87
|
)
|
|
75
88
|
|
|
76
89
|
assert "class AdanosCli < Formula" in formula
|
|
90
|
+
assert 'homepage "https://adanos.org"' in formula
|
|
77
91
|
assert "def install" in formula
|
|
78
|
-
assert 'assert_match "adanos"' in formula
|
|
92
|
+
assert 'assert_match "adanos", shell_output("#{bin}/adanos --help")' in formula
|
|
79
93
|
|
|
80
94
|
|
|
81
95
|
def test_homebrew_formula_main_writes_output_file(tmp_path, monkeypatch) -> None:
|
|
@@ -108,3 +122,63 @@ def test_homebrew_formula_main_writes_output_file(tmp_path, monkeypatch) -> None
|
|
|
108
122
|
|
|
109
123
|
assert output_path.exists()
|
|
110
124
|
assert 'version "1.20.0"' in output_path.read_text(encoding="utf-8")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_install_shell_script_uses_latest_release_assets() -> None:
|
|
128
|
+
script = (REPO_ROOT / "install.sh").read_text(encoding="utf-8")
|
|
129
|
+
|
|
130
|
+
assert "adanos-software/adanos-cli" in script
|
|
131
|
+
assert "releases/latest/download" in script
|
|
132
|
+
assert "SHA256SUMS.txt" in script
|
|
133
|
+
assert "adanos-darwin-arm64.tar.gz" in script
|
|
134
|
+
assert "adanos-darwin-x86_64.tar.gz" in script
|
|
135
|
+
assert "adanos-linux-x86_64.tar.gz" in script
|
|
136
|
+
assert "ADANOS_INSTALL_DIR" in script
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_install_powershell_script_bootstraps_pipx() -> None:
|
|
140
|
+
script = (REPO_ROOT / "install.ps1").read_text(encoding="utf-8")
|
|
141
|
+
|
|
142
|
+
assert "adanos-cli" in script
|
|
143
|
+
assert "pipx" in script
|
|
144
|
+
assert "install --force" in script
|
|
145
|
+
assert "Verify with:" in script
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_readme_documents_shell_homebrew_and_powershell_install() -> None:
|
|
149
|
+
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
|
|
150
|
+
|
|
151
|
+
assert "curl -fsSL https://raw.githubusercontent.com/adanos-software/adanos-cli/main/install.sh | bash" in readme
|
|
152
|
+
assert "brew install adanos-software/tap/adanos-cli" in readme
|
|
153
|
+
assert "irm https://raw.githubusercontent.com/adanos-software/adanos-cli/main/install.ps1 | iex" in readme
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_install_shell_script_accepts_release_upload_checksum_prefix(tmp_path) -> None:
|
|
157
|
+
archive_path = tmp_path / "adanos-darwin-arm64.tar.gz"
|
|
158
|
+
archive_path.write_bytes(b"fake-archive")
|
|
159
|
+
|
|
160
|
+
checksum = subprocess.check_output(
|
|
161
|
+
["shasum", "-a", "256", str(archive_path)],
|
|
162
|
+
text=True,
|
|
163
|
+
).split()[0]
|
|
164
|
+
checksums_path = tmp_path / "SHA256SUMS.txt"
|
|
165
|
+
checksums_path.write_text(
|
|
166
|
+
f"{checksum} release-upload/{archive_path.name}\n",
|
|
167
|
+
encoding="utf-8",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
script_text = (REPO_ROOT / "install.sh").read_text(encoding="utf-8")
|
|
171
|
+
script_without_main = script_text.rsplit('main "$@"', 1)[0]
|
|
172
|
+
harness_path = tmp_path / "install-harness.sh"
|
|
173
|
+
harness_path.write_text(
|
|
174
|
+
script_without_main
|
|
175
|
+
+ f'\nverify_checksum "{archive_path}" "{checksums_path}" "{archive_path.name}"\n',
|
|
176
|
+
encoding="utf-8",
|
|
177
|
+
)
|
|
178
|
+
harness_path.chmod(harness_path.stat().st_mode | stat.S_IXUSR)
|
|
179
|
+
|
|
180
|
+
subprocess.run(
|
|
181
|
+
["bash", str(harness_path)],
|
|
182
|
+
check=True,
|
|
183
|
+
env={**os.environ, "HOME": str(tmp_path)},
|
|
184
|
+
)
|
|
@@ -76,6 +76,7 @@ def test_onboard_guide_is_cli_first(capsys) -> None:
|
|
|
76
76
|
assert "adanos login --api-key sk_live_xxx" in out
|
|
77
77
|
assert "adanos onboard wizard" in out
|
|
78
78
|
assert "adanos onboard register" in out
|
|
79
|
+
assert "adanos onboard recover" in out
|
|
79
80
|
assert "curl -s" not in out
|
|
80
81
|
|
|
81
82
|
|
|
@@ -158,6 +159,48 @@ def test_onboard_register_returns_token_and_next_step(capsys) -> None:
|
|
|
158
159
|
assert f"adanos onboard redeem --token {token} --save" in out
|
|
159
160
|
|
|
160
161
|
|
|
162
|
+
@respx.mock
|
|
163
|
+
def test_onboard_recover_requests_email_confirmation(capsys) -> None:
|
|
164
|
+
respx.post("https://adanos.org/api/recover").mock(
|
|
165
|
+
return_value=httpx.Response(
|
|
166
|
+
200,
|
|
167
|
+
json={
|
|
168
|
+
"success": True,
|
|
169
|
+
"action": "accepted",
|
|
170
|
+
"message": "If an active account exists for this email, further recovery instructions will be sent separately.",
|
|
171
|
+
},
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
rc = cli_main.main(["onboard", "recover", "--email", "alex@example.com"])
|
|
176
|
+
out = capsys.readouterr().out
|
|
177
|
+
assert rc == 0
|
|
178
|
+
assert "Recovery request accepted." in out
|
|
179
|
+
assert "Check your email for the secure recovery link." in out
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@respx.mock
|
|
183
|
+
def test_onboard_recover_json_outputs_structured_response(capsys) -> None:
|
|
184
|
+
respx.post("https://adanos.org/api/recover").mock(
|
|
185
|
+
return_value=httpx.Response(
|
|
186
|
+
200,
|
|
187
|
+
json={
|
|
188
|
+
"success": True,
|
|
189
|
+
"action": "accepted",
|
|
190
|
+
"message": "If an active account exists for this email, further recovery instructions will be sent separately.",
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
rc = cli_main.main(["--output", "json", "onboard", "recover", "--email", "alex@example.com", "--json"])
|
|
196
|
+
captured = capsys.readouterr()
|
|
197
|
+
assert rc == 0
|
|
198
|
+
payload = json.loads(captured.out)
|
|
199
|
+
assert payload["kind"] == "onboard_recovery"
|
|
200
|
+
assert payload["success"] is True
|
|
201
|
+
assert payload["action"] == "accepted"
|
|
202
|
+
|
|
203
|
+
|
|
161
204
|
@respx.mock
|
|
162
205
|
def test_onboard_redeem_save_writes_config(tmp_path, monkeypatch) -> None:
|
|
163
206
|
token = "kt_a8Kj2mNpQrStUvWxYz1234567890AbCdEfGhIjKlMnO"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|