agentsecrets-cli 3.2.1__tar.gz → 3.2.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentsecrets-cli
3
- Version: 3.2.1
3
+ Version: 3.2.3
4
4
  Summary: AI-native zero-knowledge secrets manager
5
5
  Author-email: The Seventeen <engineering@theseventeen.co>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentsecrets-cli"
7
- version = "3.2.1"
7
+ version = "3.2.3"
8
8
  description = "AI-native zero-knowledge secrets manager"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,36 +1,97 @@
1
1
  import os
2
2
  import platform
3
3
  import sys
4
+ import time
4
5
  import urllib.request
5
6
  import tarfile
6
7
  import zipfile
7
8
  import tempfile
8
9
  import shutil
9
10
  import stat
10
-
11
11
  import json
12
12
 
13
- def get_latest_github_version():
14
- """Fetches the latest version tag from GitHub API."""
15
- try:
16
- url = "https://api.github.com/repos/The-17/agentsecrets/releases/latest"
17
- req = urllib.request.Request(url, headers={'User-Agent': 'agentsecrets-pypi'})
18
- with urllib.request.urlopen(req) as response:
19
- data = json.loads(response.read().decode())
20
- return data['tag_name'].lstrip('v')
21
- except Exception:
22
- return None
13
+ GITHUB_REPO = "The-17/agentsecrets"
23
14
 
24
15
  def _get_version():
25
16
  try:
26
- from importlib.metadata import version, PackageNotFoundError
17
+ from importlib.metadata import version
27
18
  return version("agentsecrets-cli")
28
- except (ImportError, PackageNotFoundError):
29
- latest = get_latest_github_version()
30
- return latest if latest else "1.3.1"
19
+ except Exception:
20
+ pass
21
+ try:
22
+ import re
23
+ pyproject = os.path.join(os.path.dirname(__file__), "..", "..", "pyproject.toml")
24
+ if os.path.exists(pyproject):
25
+ with open(pyproject, "r", encoding="utf-8") as f:
26
+ m = re.search(r'version\s*=\s*["\']([^"\']+)["\']', f.read())
27
+ if m:
28
+ return m.group(1)
29
+ except Exception:
30
+ pass
31
+ return "dev"
31
32
 
32
33
  VERSION = _get_version()
33
- GITHUB_REPO = "The-17/agentsecrets"
34
+
35
+ def compare_versions(v1, v2):
36
+ p1 = [int(x) for x in (v1 or "").lstrip("v").split(".") if x.isdigit()]
37
+ p2 = [int(x) for x in (v2 or "").lstrip("v").split(".") if x.isdigit()]
38
+ while len(p1) < 3: p1.append(0)
39
+ while len(p2) < 3: p2.append(0)
40
+ for i in range(3):
41
+ if p1[i] < p2[i]: return -1
42
+ if p1[i] > p2[i]: return 1
43
+ return 0
44
+
45
+ def check_update_notice():
46
+ try:
47
+ cache_file = os.path.expanduser("~/.agentsecrets/pypi_update_cache.json")
48
+ cache = None
49
+ if os.path.exists(cache_file):
50
+ try:
51
+ with open(cache_file, "r") as f:
52
+ cache = json.load(f)
53
+ except Exception:
54
+ pass
55
+
56
+ now = int(time.time())
57
+ if cache and cache.get("latest_version") and compare_versions(cache["latest_version"], VERSION) > 0:
58
+ sys.stderr.write(
59
+ f"\n\033[33mUpdate available:\033[0m {VERSION} -> \033[32m{cache['latest_version']}\033[0m\n"
60
+ f"Run: \033[36mpip install --upgrade agentsecrets-cli\033[0m to update\n\n"
61
+ )
62
+
63
+ if not cache or not cache.get("last_checked") or now - cache["last_checked"] > 86400:
64
+ try:
65
+ req = urllib.request.Request(
66
+ f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest",
67
+ headers={"User-Agent": "agentsecrets-pypi"}
68
+ )
69
+ with urllib.request.urlopen(req, timeout=2.0) as response:
70
+ data = json.loads(response.read().decode())
71
+ latest = data.get("tag_name", "").lstrip("v")
72
+ if latest:
73
+ os.makedirs(os.path.dirname(cache_file), exist_ok=True)
74
+ with open(cache_file, "w") as f:
75
+ json.dump({"last_checked": now, "latest_version": latest}, f)
76
+ except Exception:
77
+ pass
78
+ except Exception:
79
+ pass
80
+
81
+ def cleanup_old_binaries(base_dir, current_binary_path):
82
+ try:
83
+ if not os.path.exists(base_dir):
84
+ return
85
+ prefix = "agentsecrets.exe_" if platform.system().lower() == "windows" else "agentsecrets_"
86
+ for fname in os.listdir(base_dir):
87
+ full_path = os.path.join(base_dir, fname)
88
+ if fname.startswith(prefix) and full_path != current_binary_path:
89
+ try:
90
+ os.remove(full_path)
91
+ except Exception:
92
+ pass
93
+ except Exception:
94
+ pass
34
95
 
35
96
  def get_platform_info():
36
97
  """Returns a tuple of (os_name, arch_name) compatible with GoReleaser naming."""
@@ -61,11 +122,9 @@ def ensure_binary():
61
122
  If not, download it from GitHub Releases.
62
123
  Returns the absolute path to the binary.
63
124
  """
125
+ check_update_notice()
64
126
  os_name, arch_name = get_platform_info()
65
127
 
66
- # Store binaries in a hidden directory in the user's home or package dir
67
- # For PyPI, it's easier to store in the package directory itself if we have permissions,
68
- # or ~/.agentsecrets/bin
69
128
  base_dir = os.path.expanduser("~/.agentsecrets/bin")
70
129
  os.makedirs(base_dir, exist_ok=True)
71
130
 
@@ -73,22 +132,14 @@ def ensure_binary():
73
132
  if os_name == "windows":
74
133
  binary_name += ".exe"
75
134
 
76
- # Versioned binary path
77
135
  binary_path = os.path.join(base_dir, f"{binary_name}_{VERSION}")
78
136
 
79
137
  if os.path.exists(binary_path):
80
138
  return binary_path
81
139
 
82
- # Need to download
83
- print(f"AgentSecrets binary not found. Downloading version {VERSION} for {os_name}/{arch_name}...", file=sys.stderr)
140
+ sys.stderr.write(f"AgentSecrets binary not found. Downloading version {VERSION} for {os_name}/{arch_name}...\n")
84
141
 
85
142
  ext = "tar.gz" if os_name != "windows" else "zip"
86
- # Match the naming template in .goreleaser.yaml: {{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}
87
- # Note: GoReleaser naming is case-sensitive and uses specific strings.
88
- # Looking at .goreleaser.yaml: name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
89
- # GoReleaser Os: linux, darwin, windows
90
- # GoReleaser Arch: amd64, arm64
91
-
92
143
  asset_name = f"agentsecrets_{VERSION}_{os_name}_{arch_name}.{ext}"
93
144
  url = f"https://github.com/{GITHUB_REPO}/releases/download/v{VERSION}/{asset_name}"
94
145
 
@@ -96,11 +147,9 @@ def ensure_binary():
96
147
  with tempfile.TemporaryDirectory() as tmpdir:
97
148
  archive_path = os.path.join(tmpdir, asset_name)
98
149
 
99
- # Download the archive
100
150
  with urllib.request.urlopen(url) as response, open(archive_path, 'wb') as out_file:
101
151
  shutil.copyfileobj(response, out_file)
102
152
 
103
- # Extract
104
153
  if ext == "tar.gz":
105
154
  with tarfile.open(archive_path, "r:gz") as tar:
106
155
  tar.extractall(path=tmpdir)
@@ -108,12 +157,8 @@ def ensure_binary():
108
157
  with zipfile.ZipFile(archive_path, 'r') as zip_ref:
109
158
  zip_ref.extractall(tmpdir)
110
159
 
111
- # Move the binary to the final location
112
- # The archive contains the binary in the root
113
160
  extracted_binary = os.path.join(tmpdir, binary_name)
114
161
  if not os.path.exists(extracted_binary):
115
- # Maybe it's inside a folder? goreleaser usually puts it in root by default unless configured otherwise
116
- # Let's check all files in tmpdir
117
162
  for root, dirs, files in os.walk(tmpdir):
118
163
  if binary_name in files:
119
164
  extracted_binary = os.path.join(root, binary_name)
@@ -121,12 +166,10 @@ def ensure_binary():
121
166
 
122
167
  shutil.move(extracted_binary, binary_path)
123
168
 
124
- # Ensure executable
125
169
  st = os.stat(binary_path)
126
170
  os.chmod(binary_path, st.st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
127
171
 
128
- # Pre-register with keychain-auth if available. This eliminates
129
- # the one-time setup spinner on the user's first command.
172
+ # Pre-register with keychain-auth if available
130
173
  import subprocess
131
174
  kc = shutil.which("keychain-auth")
132
175
  if kc:
@@ -136,21 +179,19 @@ def ensure_binary():
136
179
  timeout=5, capture_output=True,
137
180
  )
138
181
  except Exception:
139
- pass # AutoSetup handles registration at runtime if needed
182
+ pass
140
183
 
184
+ cleanup_old_binaries(base_dir, binary_path)
141
185
  return binary_path
142
186
 
143
187
  except Exception as e:
144
- # Fallback to checking if a real binary is already in the PATH
145
188
  system_binary = shutil.which("agentsecrets")
146
- # Ensure we don't pick up this Python wrapper itself (infinite loop)
147
189
  if system_binary and not system_binary.endswith(".py") and "site-packages" not in system_binary:
148
190
  return system_binary
149
191
 
150
192
  raise Exception(
151
193
  f"Failed to download AgentSecrets binary from {url}.\n"
152
194
  f"Error: {e}\n\n"
153
- "TIP: This usually happens if the version hasn't been released on GitHub yet.\n"
154
- "Please run: git tag v1.0.0 && git push origin v1.0.0\n"
155
- "This will trigger the build that makes this binary available."
195
+ "TIP: Please verify your internet connection or install directly via Homebrew:\n"
196
+ " brew install The-17/tap/agentsecrets"
156
197
  )