claude-finops 0.4.1 → 0.4.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/package.json +1 -1
- package/run.py +45 -0
- package/web/app.js +4 -1
- package/web/styles.css +7 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-finops",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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
|
@@ -93,6 +93,7 @@ HELP = """Claude FinOps Command Center
|
|
|
93
93
|
claude-finops --set-key store a provider API key (prompts, never echoes)
|
|
94
94
|
claude-finops --keys list which provider keys are configured
|
|
95
95
|
claude-finops --share write ../claude-finops.zip (code only, never your data)
|
|
96
|
+
claude-finops --version print the installed version, and whether a newer one is out
|
|
96
97
|
claude-finops --help this message
|
|
97
98
|
|
|
98
99
|
Environment:
|
|
@@ -104,6 +105,38 @@ Environment:
|
|
|
104
105
|
"""
|
|
105
106
|
|
|
106
107
|
|
|
108
|
+
def _version():
|
|
109
|
+
from finops.update import _installed
|
|
110
|
+
return _installed()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def version():
|
|
114
|
+
"""Which copy is this, and is it current?
|
|
115
|
+
|
|
116
|
+
The second half matters more than the first: people run a global install and
|
|
117
|
+
a repo checkout side by side, and the usual confusion is not "what version
|
|
118
|
+
am I on" but "why does the one I am looking at not have the feature".
|
|
119
|
+
"""
|
|
120
|
+
from finops.update import check, disabled, _key
|
|
121
|
+
# Asking outright is worth a fresh request: a day-old cached answer is the
|
|
122
|
+
# one thing this command must not give you.
|
|
123
|
+
u = check(force=True)
|
|
124
|
+
print(f" claude-finops {u['current'] or 'unknown'}")
|
|
125
|
+
print(f" installed at {ROOT}")
|
|
126
|
+
if u.get("update_available"):
|
|
127
|
+
print(f" update {u['latest']} is out - {u['command']}")
|
|
128
|
+
elif disabled():
|
|
129
|
+
print(" update check is off (NO_UPDATE_NOTIFIER)")
|
|
130
|
+
elif u.get("latest") and _key(u["current"]) > _key(u["latest"]):
|
|
131
|
+
# A checkout mid-release is ahead of what is published. Saying "up to
|
|
132
|
+
# date" there would hide exactly the gap you are looking for.
|
|
133
|
+
print(f" update ahead of npm (published latest is {u['latest']})")
|
|
134
|
+
elif u.get("latest"):
|
|
135
|
+
print(f" update up to date (npm latest is {u['latest']})")
|
|
136
|
+
else:
|
|
137
|
+
print(" update could not reach the npm registry")
|
|
138
|
+
|
|
139
|
+
|
|
107
140
|
def where():
|
|
108
141
|
from finops.paths import (HOME_DIR, DB_PATH, LOCAL_SETTINGS_PATH, SECRETS_PATH,
|
|
109
142
|
LOGFILE, CONFIG_DIR)
|
|
@@ -180,6 +213,8 @@ def main():
|
|
|
180
213
|
migrate()
|
|
181
214
|
if "--help" in args or "-h" in args:
|
|
182
215
|
return print(HELP)
|
|
216
|
+
if "--version" in args or "-v" in args or "-V" in args:
|
|
217
|
+
return version()
|
|
183
218
|
if "--where" in args:
|
|
184
219
|
return where()
|
|
185
220
|
if "--keys" in args:
|
|
@@ -192,6 +227,16 @@ def main():
|
|
|
192
227
|
return
|
|
193
228
|
if "--share" in args:
|
|
194
229
|
return share()
|
|
230
|
+
# A flag we do not know used to fall straight through and start the
|
|
231
|
+
# dashboard, so a typo (or a flag from a newer release than the one you
|
|
232
|
+
# have installed) looked like the command silently doing nothing.
|
|
233
|
+
known = {"--rebuild", "--detach", "--foreground"}
|
|
234
|
+
unknown = [a for a in args if a.startswith("-") and a not in known]
|
|
235
|
+
if unknown:
|
|
236
|
+
print(f"Unknown option: {unknown[0]}")
|
|
237
|
+
print(f"This is claude-finops {_version() or 'unknown'}; "
|
|
238
|
+
f"run --help to see what it supports.")
|
|
239
|
+
sys.exit(2)
|
|
195
240
|
stop() # replace a previous detached server
|
|
196
241
|
port = os.environ.get("PORT", "8787")
|
|
197
242
|
source = os.environ.get("CLAUDE_PROJECTS", os.path.join(os.path.expanduser("~"), ".claude", "projects"))
|
package/web/app.js
CHANGED
|
@@ -485,7 +485,10 @@ async function openSession(id) {
|
|
|
485
485
|
/* ---------- charts added to table-first pages ---------- */
|
|
486
486
|
// Inserts a chart card after the page's KPI row (or at the top) and draws into it.
|
|
487
487
|
function addChart(page, title, draw, opts = {}) {
|
|
488
|
-
|
|
488
|
+
// .chart carries every chart style (text fill, gridlines, axes); .cchart is
|
|
489
|
+
// just the hook this helper looks up. Missing .chart left SVG labels at the
|
|
490
|
+
// browser default fill — black text, invisible on the dark theme.
|
|
491
|
+
const c = h(card(title, '<div class="chart cchart"></div>', opts));
|
|
489
492
|
const anchor = opts.after ? page.querySelector(opts.after) : page.querySelector(':scope > .grid');
|
|
490
493
|
if (anchor) anchor.after(c); else page.prepend(c);
|
|
491
494
|
draw(c.querySelector('.cchart'));
|
package/web/styles.css
CHANGED
|
@@ -76,15 +76,19 @@ button, input, select { font: inherit; color: inherit; }
|
|
|
76
76
|
|
|
77
77
|
/* ---------- shell ---------- */
|
|
78
78
|
.app { display: grid; grid-template-columns: 216px 1fr; min-height: 100vh; }
|
|
79
|
+
/* The sidebar itself no longer scrolls: the nav does. A sticky footer inside a
|
|
80
|
+
scrolling column still has the rest of the list running underneath it, which
|
|
81
|
+
showed as nav items bleeding through the account block. */
|
|
79
82
|
.sidebar { background: var(--surface); border-right: 1px solid var(--border);
|
|
80
|
-
padding: 14px 0; position: sticky; top:0; height:100vh; overflow
|
|
83
|
+
padding: 14px 0; position: sticky; top:0; height:100vh; overflow:hidden;
|
|
81
84
|
display:flex; flex-direction:column; }
|
|
82
85
|
.brand { padding: 0 16px 14px; border-bottom: 1px solid var(--border); margin-bottom: 10px; }
|
|
83
86
|
.brand .mark { display:flex; align-items:center; gap:8px; font-weight:660; font-size:14px;
|
|
84
87
|
letter-spacing:-0.01em; }
|
|
85
88
|
.brand .dot { width:9px; height:9px; border-radius:3px; background:var(--s2); flex:none; }
|
|
86
89
|
.brand .sub { font-size:10.5px; color:var(--muted); margin-top:3px; letter-spacing:.02em; }
|
|
87
|
-
.nav { display:flex; flex-direction:column; gap:1px; padding: 0 8px;
|
|
90
|
+
.nav { display:flex; flex-direction:column; gap:1px; padding: 0 8px;
|
|
91
|
+
flex:1 1 auto; min-height:0; overflow-y:auto; }
|
|
88
92
|
.nav .group { font-size:9.5px; text-transform:uppercase; letter-spacing:.09em; color:var(--muted);
|
|
89
93
|
padding: 13px 8px 5px; font-weight:600; }
|
|
90
94
|
.nav a { display:flex; align-items:center; gap:8px; padding:6px 9px; border-radius:7px;
|
|
@@ -401,8 +405,7 @@ table.tbl .sub { font-size:10.5px; color:var(--muted); }
|
|
|
401
405
|
so every screenshot carries the name of the account it came from. */
|
|
402
406
|
/* Sticky, because the nav is taller than the viewport and an identity you have
|
|
403
407
|
to scroll to find is one nobody checks. */
|
|
404
|
-
.who {
|
|
405
|
-
display:flex; align-items:center; gap:9px;
|
|
408
|
+
.who { flex:none; display:flex; align-items:center; gap:9px;
|
|
406
409
|
padding:12px 16px; background:var(--surface);
|
|
407
410
|
border-top:1px solid var(--border); }
|
|
408
411
|
.who:empty { display:none; }
|