claude-finops 0.13.0 → 0.14.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-finops",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Local FinOps dashboard for Claude Code: what you used, what it cost, why it cost that much, and what to change. Reads your own transcripts, no API key, no data leaves the machine.",
5
5
  "bin": {
6
6
  "claude-finops": "bin/claude-finops.js"
package/run.py CHANGED
@@ -154,6 +154,8 @@ HELP = """Claude FinOps Command Center
154
154
  claude-finops --no-update-check skip the once-a-day npm version check
155
155
  claude-finops --install-hook retired no-op (prints a message, changes nothing)
156
156
  claude-finops --install-statusline show model, context % and 5-hour limit in your statusline
157
+ claude-finops --install-guard warn near, and ask at, your per-session token budget
158
+ claude-finops --uninstall-guard remove the session guard hook
157
159
  claude-finops --help this message
158
160
 
159
161
  Environment:
@@ -241,7 +243,6 @@ def keys():
241
243
  def set_key(pid=None):
242
244
  """Prompt for a key and store it 0600. The value is never echoed or logged."""
243
245
  import getpass
244
- import json
245
246
  from finops.paths import SECRETS_PATH
246
247
  provs = _providers()
247
248
  if pid not in provs:
@@ -256,23 +257,21 @@ def set_key(pid=None):
256
257
  p = provs[pid]
257
258
  print(f"{p['name']}: paste the key (input stays hidden), or press Enter to remove it.")
258
259
  value = getpass.getpass("key: ").strip()
260
+ from finops.cloud import save_key
259
261
  try:
260
- data = json.load(open(SECRETS_PATH))
261
- except (OSError, ValueError):
262
- data = {}
263
- if value:
264
- data[p["field"]] = value
265
- else:
266
- data.pop(p["field"], None)
267
- fd = os.open(SECRETS_PATH, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
268
- with os.fdopen(fd, "w") as fh:
269
- json.dump(data, fh, indent=2)
262
+ save_key(pid, value)
263
+ except ValueError as e:
264
+ sys.exit(str(e))
270
265
  print(f"{'Stored' if value else 'Removed'} — {SECRETS_PATH}")
271
- print("Restart the dashboard for it to take effect: claude-finops --stop && claude-finops")
266
+ print("The dashboard picks it up on the next Refresh; no restart needed.")
272
267
 
273
268
 
274
269
  def main():
275
270
  args = sys.argv[1:]
271
+ # Runs before every tool call in Claude Code: skip setup, print only its JSON answer.
272
+ if "--guard" in args:
273
+ from finops.guard import main as guard
274
+ return sys.exit(guard())
276
275
  os.chdir(ROOT)
277
276
  ensure_dirs()
278
277
  migrate()
@@ -294,6 +293,11 @@ def main():
294
293
  if "--install-hook" in args or "--uninstall-hook" in args:
295
294
  from finops.integrate import install_hook
296
295
  return install_hook(remove="--uninstall-hook" in args)
296
+ if "--install-guard" in args or "--uninstall-guard" in args:
297
+ from finops.integrate import install_guard
298
+ r = install_guard(remove="--uninstall-guard" in args)
299
+ print(r["message"])
300
+ return sys.exit(0 if r["ok"] else 1)
297
301
  if "--install-statusline" in args or "--uninstall-statusline" in args:
298
302
  from finops.integrate import install_statusline
299
303
  r = install_statusline(remove="--uninstall-statusline" in args)