@zeluizr/lattice 2.0.0 → 2.1.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/dist/app.js CHANGED
@@ -6,11 +6,12 @@ import { SystemCollector } from "./collectors/system.js";
6
6
  import { DisksCollector } from "./collectors/disks.js";
7
7
  import { GitCollector } from "./collectors/git.js";
8
8
  import { ZgitCollector } from "./collectors/zgit.js";
9
+ import { HFCollector } from "./collectors/huggingface.js";
9
10
  import { readGpu } from "./collectors/gpu.js";
10
11
  import { readSensors } from "./collectors/sensors.js";
11
12
  import { TokenCollector } from "./collectors/tokens.js";
12
13
  import { readVtex } from "./collectors/vtex.js";
13
- import { coreCell, fmtTok, humanBytes, humanRate, sparkline, statusLevel } from "./format.js";
14
+ import { agoShort, coreCell, fmtTok, humanBytes, humanRate, sparkline, statusLevel } from "./format.js";
14
15
  const MAX_HIST = 60;
15
16
  const push = (h, v) => [...h, v].slice(-MAX_HIST);
16
17
  const DEFAULT_INTERVAL = 1; // seconds between refreshes (adjust at runtime with +/-)
@@ -20,7 +21,7 @@ function timeStr() {
20
21
  return d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit" });
21
22
  }
22
23
  export function App(props) {
23
- const { t, pal, icon, repoRoots, zgitContainer } = props;
24
+ const { t, pal, icon, repoRoots, zgitContainer, hfCachePath } = props;
24
25
  const { exit } = useApp();
25
26
  const [sys, setSys] = useState(null);
26
27
  const [disks, setDisks] = useState(null);
@@ -30,6 +31,7 @@ export function App(props) {
30
31
  const [sensors, setSensors] = useState(null);
31
32
  const [tokens, setTokens] = useState(null);
32
33
  const [vtex, setVtex] = useState(null);
34
+ const [hf, setHf] = useState(null);
33
35
  const [hCpu, setHCpu] = useState([]);
34
36
  const [hMem, setHMem] = useState([]);
35
37
  const [hNet, setHNet] = useState([]);
@@ -44,6 +46,7 @@ export function App(props) {
44
46
  const gitRef = useRef(null);
45
47
  const zgitRef = useRef(null);
46
48
  const tokRef = useRef(null);
49
+ const hfRef = useRef(null);
47
50
  const pausedRef = useRef(false);
48
51
  const mounted = useRef(true);
49
52
  useEffect(() => {
@@ -57,6 +60,7 @@ export function App(props) {
57
60
  gitRef.current = new GitCollector(repoRoots);
58
61
  zgitRef.current = new ZgitCollector(zgitContainer);
59
62
  tokRef.current = new TokenCollector();
63
+ hfRef.current = new HFCollector(hfCachePath);
60
64
  const clock = setInterval(() => mounted.current && setNow(timeStr()), 1000);
61
65
  const onResize = () => setCols(process.stdout.columns || 100);
62
66
  process.stdout.on("resize", onResize);
@@ -94,11 +98,12 @@ export function App(props) {
94
98
  const refreshAux = useCallback(async () => {
95
99
  if (pausedRef.current || !tokRef.current)
96
100
  return;
97
- const [tk, vx, gt, zg] = await Promise.all([
101
+ const [tk, vx, gt, zg, hfd] = await Promise.all([
98
102
  tokRef.current.read(),
99
103
  readVtex(),
100
104
  gitRef.current ? gitRef.current.read() : Promise.resolve(null),
101
105
  zgitRef.current ? zgitRef.current.read() : Promise.resolve(null),
106
+ hfRef.current ? hfRef.current.read() : Promise.resolve(null),
102
107
  ]);
103
108
  if (!mounted.current)
104
109
  return;
@@ -106,6 +111,7 @@ export function App(props) {
106
111
  setVtex(vx);
107
112
  setGit(gt);
108
113
  setZgit(zg);
114
+ setHf(hfd);
109
115
  }, []);
110
116
  useEffect(() => {
111
117
  refreshData();
@@ -170,6 +176,9 @@ export function App(props) {
170
176
  const gitTotal = git?.total ?? 0;
171
177
  const zgitServer = zgit?.available ? zgit : null;
172
178
  const showGit = gitTotal > 0 || !!zgitServer;
179
+ const hfModels = hf?.models ?? [];
180
+ const showHf = !!hf?.available;
181
+ const nowSec = Math.floor(Date.now() / 1000);
173
182
  const hostLabel = (r) => r.hostKind === "github"
174
183
  ? "GitHub"
175
184
  : r.hostKind === "zgit"
@@ -200,7 +209,12 @@ export function App(props) {
200
209
  }) }), _jsx(Text, { color: pal.green, wrap: "truncate", children: sparkline(hMem, w2) }), _jsx(Text, { color: pal.comment, wrap: "truncate", children: caption(hMem, (v) => `${v.toFixed(0)}%`) })] })] }), _jsxs(Box, { flexDirection: "row", children: [_jsxs(Panel, { title: `${icon("temp")} ${t("panel.temp")}`, color: pal.orange, width: col3, children: [_jsx(Text, { wrap: "truncate", children: ct != null && gt != null ? (_jsxs(_Fragment, { children: [ct.toFixed(0), "\u00B0 / ", gt.toFixed(0), "\u00B0", " ", _jsx(Stat, { value: Math.max(ct, gt), warn: 65, crit: 80, metric: "temp" })] })) : ct != null || gt != null ? (_jsxs(_Fragment, { children: [(ct ?? gt ?? 0).toFixed(0), "\u00B0C", " ", _jsx(Stat, { value: ct ?? gt ?? 0, warn: 65, crit: 80, metric: "temp" })] })) : (_jsx(Text, { color: pal.comment, children: t("temp.unavailable") })) }), _jsx(Text, { wrap: "truncate", children: tempLine2 }), _jsx(Text, { color: pal.orange, wrap: "truncate", children: sparkline(hTemp, w3) }), _jsx(Text, { color: pal.comment, wrap: "truncate", children: caption(hTemp, (v) => `${v.toFixed(0)}°`) })] }), _jsxs(Panel, { title: `${icon("net")} ${t("panel.net")}`, color: pal.cyan, width: col3, children: [_jsxs(Text, { wrap: "truncate", children: ["\u2193 ", humanRate(sys?.netRecvBps)] }), _jsxs(Text, { wrap: "truncate", children: ["\u2191 ", humanRate(sys?.netSentBps)] }), _jsx(Text, { color: pal.cyan, wrap: "truncate", children: sparkline(hNet, w3) }), _jsx(Text, { color: pal.comment, wrap: "truncate", children: caption(hNet, (v) => humanRate(v * 1024 * 1024)) })] }), _jsxs(Panel, { title: `${icon("gpu")} ${t("panel.gpu")}`, color: pal.purple, width: col3, children: [_jsxs(Text, { wrap: "truncate", children: [t("gpu.usage"), ": ", _jsxs(Text, { bold: true, children: [util, "%"] }), " ", _jsx(Stat, { value: util, warn: 60, crit: 85, metric: "gpu" })] }), _jsx(Text, { wrap: "truncate", children: t("gpu.mem", { used: humanBytes(gpu?.memUsedBytes), alloc: humanBytes(gpu?.memAllocBytes) }) }), _jsx(Text, { color: pal.purple, wrap: "truncate", children: sparkline(hGpu, w3) }), _jsx(Text, { color: pal.comment, wrap: "truncate", children: caption(hGpu, (v) => `${v.toFixed(0)}%`) })] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: pal.cyan, paddingX: 1, marginRight: 1, width: fullW, children: [_jsxs(Text, { color: pal.cyan, bold: true, children: [icon("disk"), " ", t("panel.disks")] }), _jsxs(Text, { color: pal.purple, bold: true, wrap: "truncate", children: [t("disks.mount").padEnd(22), t("disks.read").padEnd(12), t("disks.write").padEnd(12), t("disks.usage")] }), diskRows.length === 0 ? (_jsx(Text, { color: pal.comment, children: t("spark.collecting") })) : (diskRows.map((d) => {
201
210
  const lvl = statusLevel(d.usePercent, 80, 92);
202
211
  return (_jsxs(Text, { wrap: "truncate", children: [d.mount.slice(0, 21).padEnd(22), humanRate(d.readBps).padEnd(12), humanRate(d.writeBps).padEnd(12), _jsxs(Text, { color: statColor(lvl), children: ["\u25CF ", d.usePercent.toFixed(0), "%"] }), " ", _jsxs(Text, { color: pal.comment, children: ["(", humanBytes(d.usedBytes), "/", humanBytes(d.sizeBytes), ")"] })] }, d.mount));
203
- }))] }), _jsxs(Box, { flexDirection: "row", children: [_jsxs(Panel, { title: `${icon("tokens")} ${t("panel.tokens")}`, color: pal.pink, width: col2, children: [_jsx(Text, { wrap: "truncate", children: t("tokens.spent", { cost: (tokens?.cost ?? 0).toFixed(2), messages: tokens?.messages ?? 0 }) }), _jsx(Text, { wrap: "truncate", children: t("tokens.tokens", {
212
+ }))] }), showHf && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: pal.yellow, paddingX: 1, marginRight: 1, width: fullW, children: [_jsxs(Text, { color: pal.yellow, bold: true, children: [icon("hf"), " ", t("panel.hf")] }), _jsxs(Text, { color: pal.purple, bold: true, wrap: "truncate", children: [t("hf.model").padEnd(30), t("hf.size").padEnd(9), t("hf.type").padEnd(16), t("hf.state")] }), hfModels.map((mdl) => (_jsxs(Text, { wrap: "truncate", children: [mdl.id.slice(0, 29).padEnd(30), humanBytes(mdl.sizeBytes).padEnd(9), (mdl.modelType || "—").slice(0, 15).padEnd(16), mdl.active ? (_jsxs(Text, { color: pal.green, children: ["\u25CF ", t("hf.active"), " ", mdl.procName.slice(0, 14), " ", mdl.pid] })) : mdl.lastUsed > 0 ? (_jsx(Text, { color: pal.comment, children: t("hf.usedAgo", { ago: agoShort(mdl.lastUsed, nowSec) }) })) : (_jsx(Text, { color: pal.comment, children: t("hf.idle") }))] }, mdl.id))), _jsx(Text, { color: pal.comment, wrap: "truncate", children: t("hf.summary", {
213
+ path: hf?.cachePath ?? "",
214
+ size: humanBytes(hf?.totalBytes),
215
+ n: hfModels.length,
216
+ active: hf?.activeCount ?? 0,
217
+ }) })] })), _jsxs(Box, { flexDirection: "row", children: [_jsxs(Panel, { title: `${icon("tokens")} ${t("panel.tokens")}`, color: pal.pink, width: col2, children: [_jsx(Text, { wrap: "truncate", children: t("tokens.spent", { cost: (tokens?.cost ?? 0).toFixed(2), messages: tokens?.messages ?? 0 }) }), _jsx(Text, { wrap: "truncate", children: t("tokens.tokens", {
204
218
  total: fmtTok(tokTotal),
205
219
  input: fmtTok(tokens?.input),
206
220
  output: fmtTok(tokens?.output),
package/dist/cli.js CHANGED
@@ -49,7 +49,7 @@ async function main() {
49
49
  const icon = makeIcons(cfg.icons ?? "nerd");
50
50
  const repoRoots = cfg.repoRoots?.length ? cfg.repoRoots : [dirname(process.cwd())];
51
51
  const zgitContainer = cfg.zgitContainer ?? "zgit";
52
- const { waitUntilExit } = render(_jsx(App, { t: t, pal: pal, icon: icon, repoRoots: repoRoots, zgitContainer: zgitContainer }));
52
+ const { waitUntilExit } = render(_jsx(App, { t: t, pal: pal, icon: icon, repoRoots: repoRoots, zgitContainer: zgitContainer, hfCachePath: cfg.hfCachePath }));
53
53
  await waitUntilExit();
54
54
  }
55
55
  main().catch((e) => {
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Local HuggingFace hub cache — which models are installed, and which are live.
3
+ *
4
+ * The hub cache is the standard layout `<cache>/models--<org>--<name>/` with
5
+ * `refs/main` (the checked-out revision), `snapshots/<rev>/` (symlinks) and
6
+ * `blobs/<sha>` (the real file content). Enumerating models is pure filesystem
7
+ * work — no dependency on the `hf` CLI, so it keeps working under `npx`.
8
+ *
9
+ * Two usage signals are layered on top:
10
+ * - "active" — a process currently holds one of the model's files open (a
11
+ * single `lsof` pass; the file's path always contains the model's
12
+ * `models--org--name` segment because each repo has its own `blobs/`).
13
+ * - "lastUsed" — the atime of the model's largest blob (the weights), which
14
+ * reflects the last real read. `statSync` reads metadata only, so polling it
15
+ * never updates atime; the weights blob is used (never config.json) so our
16
+ * own one-time config read can't pollute the signal.
17
+ *
18
+ * Fail-soft like every collector: any error returns the previous snapshot.
19
+ */
20
+ import { execFile } from "node:child_process";
21
+ import { promisify } from "node:util";
22
+ import { homedir } from "node:os";
23
+ import { join } from "node:path";
24
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
25
+ const run = promisify(execFile);
26
+ /** Resolve the hub cache dir: config → HF_HUB_CACHE → HF_HOME/hub → ~/.cache. */
27
+ function resolveCachePath(override) {
28
+ if (override)
29
+ return override;
30
+ if (process.env.HF_HUB_CACHE)
31
+ return process.env.HF_HUB_CACHE;
32
+ if (process.env.HF_HOME)
33
+ return join(process.env.HF_HOME, "hub");
34
+ return join(homedir(), ".cache", "huggingface", "hub");
35
+ }
36
+ export class HFCollector {
37
+ inflight = false;
38
+ last;
39
+ cachePath;
40
+ // model_type is static per revision and reading config.json bumps its atime,
41
+ // so we read it once and cache it keyed by "<dirName>@<revision>".
42
+ typeCache = new Map();
43
+ constructor(override) {
44
+ this.cachePath = resolveCachePath(override);
45
+ this.last = { available: false, cachePath: this.cachePath, totalBytes: 0, activeCount: 0, models: [] };
46
+ }
47
+ async read() {
48
+ if (this.inflight)
49
+ return this.last;
50
+ this.inflight = true;
51
+ try {
52
+ if (!existsSync(this.cachePath)) {
53
+ this.last = { available: false, cachePath: this.cachePath, totalBytes: 0, activeCount: 0, models: [] };
54
+ return this.last;
55
+ }
56
+ const dirs = readdirSync(this.cachePath, { withFileTypes: true })
57
+ .filter((e) => e.isDirectory() && e.name.startsWith("models--"))
58
+ .map((e) => e.name);
59
+ const byDir = new Map();
60
+ const models = [];
61
+ for (const dirName of dirs) {
62
+ const m = this.readModel(dirName);
63
+ if (m) {
64
+ models.push(m.model);
65
+ byDir.set(dirName, m.model);
66
+ }
67
+ }
68
+ // One lsof pass marks the models whose files a process currently holds.
69
+ await this.markActive(byDir);
70
+ const totalBytes = models.reduce((a, m) => a + m.sizeBytes, 0);
71
+ const activeCount = models.filter((m) => m.active).length;
72
+ models.sort((a, b) => Number(b.active) - Number(a.active) || b.lastUsed - a.lastUsed || b.sizeBytes - a.sizeBytes);
73
+ this.last = { available: models.length > 0, cachePath: this.cachePath, totalBytes, activeCount, models };
74
+ return this.last;
75
+ }
76
+ catch {
77
+ return this.last;
78
+ }
79
+ finally {
80
+ this.inflight = false;
81
+ }
82
+ }
83
+ /** Size (sum of blobs), weights atime, model_type and revision for one repo. */
84
+ readModel(dirName) {
85
+ try {
86
+ const id = dirName.replace(/^models--/, "").replace(/--/g, "/");
87
+ const name = id.slice(id.lastIndexOf("/") + 1);
88
+ const base = join(this.cachePath, dirName);
89
+ // Full hash locates the snapshot dir; a short prefix is what we display.
90
+ const revFull = readFileSync(join(base, "refs", "main"), "utf8").trim();
91
+ const revision = revFull.slice(0, 12);
92
+ // One pass over blobs: total size + atime of the largest blob (weights).
93
+ let sizeBytes = 0;
94
+ let biggest = -1;
95
+ let lastUsed = 0;
96
+ const blobsDir = join(base, "blobs");
97
+ if (existsSync(blobsDir)) {
98
+ for (const f of readdirSync(blobsDir)) {
99
+ try {
100
+ const st = statSync(join(blobsDir, f));
101
+ if (!st.isFile())
102
+ continue;
103
+ sizeBytes += st.size;
104
+ if (st.size > biggest) {
105
+ biggest = st.size;
106
+ lastUsed = Math.floor(st.atimeMs / 1000);
107
+ }
108
+ }
109
+ catch {
110
+ // skip unreadable blob
111
+ }
112
+ }
113
+ }
114
+ const modelType = this.readType(dirName, revFull, base);
115
+ return {
116
+ model: { id, name, sizeBytes, modelType, revision, active: false, procName: "", pid: 0, lastUsed },
117
+ };
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ /** config.json `model_type`, cached per revision (its read would bump atime). */
124
+ readType(dirName, revision, base) {
125
+ const key = `${dirName}@${revision}`;
126
+ const hit = this.typeCache.get(key);
127
+ if (hit !== undefined)
128
+ return hit;
129
+ let modelType = "";
130
+ try {
131
+ const cfg = JSON.parse(readFileSync(join(base, "snapshots", revision, "config.json"), "utf8"));
132
+ if (typeof cfg.model_type === "string")
133
+ modelType = cfg.model_type;
134
+ }
135
+ catch {
136
+ // no config.json (e.g. some MLX repos) — leave type blank
137
+ }
138
+ this.typeCache.set(key, modelType);
139
+ return modelType;
140
+ }
141
+ /** Single `lsof` pass; tag each model a process is holding files open for. */
142
+ async markActive(byDir) {
143
+ if (byDir.size === 0)
144
+ return;
145
+ // `lsof -nP` often exits non-zero with warnings about other users' procs
146
+ // while still printing useful stdout — keep the partial output from the error.
147
+ const { stdout } = await run("lsof", ["-nP", "-F", "pcn"], { maxBuffer: 1 << 24 }).catch((e) => ({ stdout: e?.stdout ?? "" }));
148
+ let pid = 0;
149
+ let cmd = "";
150
+ for (const line of stdout.split("\n")) {
151
+ const tag = line[0];
152
+ const val = line.slice(1);
153
+ if (tag === "p")
154
+ pid = Number(val) || 0;
155
+ else if (tag === "c")
156
+ cmd = val;
157
+ else if (tag === "n") {
158
+ const seg = val.match(/\/(models--[^/]+)\//);
159
+ if (!seg)
160
+ continue;
161
+ const model = byDir.get(seg[1]);
162
+ if (model && !model.active) {
163
+ model.active = true;
164
+ model.pid = pid;
165
+ model.procName = cmd;
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
package/dist/config.js CHANGED
@@ -27,6 +27,8 @@ export function loadConfig() {
27
27
  }
28
28
  if (typeof raw.zgitContainer === "string" && raw.zgitContainer)
29
29
  cfg.zgitContainer = raw.zgitContainer;
30
+ if (typeof raw.hfCachePath === "string" && raw.hfCachePath)
31
+ cfg.hfCachePath = raw.hfCachePath;
30
32
  return cfg;
31
33
  }
32
34
  catch {
package/dist/format.js CHANGED
@@ -45,6 +45,20 @@ export function sparkline(history, width = 0) {
45
45
  })
46
46
  .join("");
47
47
  }
48
+ /** Short relative time ("now" / "3m" / "2h" / "5d") from a unix-seconds stamp. */
49
+ export function agoShort(unixSeconds, nowSeconds) {
50
+ const ts = Number(unixSeconds || 0);
51
+ if (!ts)
52
+ return "—";
53
+ const s = Math.max(0, Math.floor(nowSeconds - ts));
54
+ if (s < 60)
55
+ return "now";
56
+ if (s < 3600)
57
+ return `${Math.floor(s / 60)}m`;
58
+ if (s < 86400)
59
+ return `${Math.floor(s / 3600)}h`;
60
+ return `${Math.floor(s / 86400)}d`;
61
+ }
48
62
  /** Pick a status level by thresholds (mirrors the Python status() helper). */
49
63
  export function statusLevel(value, warn, crit) {
50
64
  const v = Number(value || 0);
package/dist/i18n/en.js CHANGED
@@ -11,6 +11,7 @@ export const en = {
11
11
  "panel.gpu": "GPU",
12
12
  "panel.tokens": "AI · TOKENS TODAY",
13
13
  "panel.vtex": "VTEX",
14
+ "panel.hf": "HUGGINGFACE",
14
15
  "panel.procs": "PROCESSES",
15
16
  "status.cpu.ok": "calm",
16
17
  "status.cpu.warn": "busy",
@@ -64,6 +65,15 @@ export const en = {
64
65
  "vtex.workspace": "Workspace",
65
66
  "vtex.notConnected": "not connected",
66
67
  "vtex.signin": "Sign in with: vtex login <account>",
68
+ "hf.model": "MODEL",
69
+ "hf.size": "SIZE",
70
+ "hf.type": "TYPE",
71
+ "hf.state": "STATE",
72
+ "hf.active": "active",
73
+ "hf.idle": "idle",
74
+ "hf.usedAgo": "used {ago} ago",
75
+ "hf.empty": "no models cached",
76
+ "hf.summary": "{path} · {size} · {n} models · {active} active",
67
77
  "proc.cpu": "CPU%",
68
78
  "proc.mem": "MEM",
69
79
  "proc.pid": "PID",
package/dist/i18n/es.js CHANGED
@@ -10,6 +10,7 @@ export const es = {
10
10
  "panel.gpu": "GPU",
11
11
  "panel.tokens": "IA · TOKENS HOY",
12
12
  "panel.vtex": "VTEX",
13
+ "panel.hf": "HUGGINGFACE",
13
14
  "panel.procs": "PROCESOS",
14
15
  "status.cpu.ok": "tranquilo",
15
16
  "status.cpu.warn": "ocupado",
@@ -63,6 +64,15 @@ export const es = {
63
64
  "vtex.workspace": "Workspace",
64
65
  "vtex.notConnected": "no conectado",
65
66
  "vtex.signin": "Entra con: vtex login <cuenta>",
67
+ "hf.model": "MODELO",
68
+ "hf.size": "TAMAÑO",
69
+ "hf.type": "TIPO",
70
+ "hf.state": "ESTADO",
71
+ "hf.active": "activo",
72
+ "hf.idle": "inactivo",
73
+ "hf.usedAgo": "usado hace {ago}",
74
+ "hf.empty": "sin modelos en caché",
75
+ "hf.summary": "{path} · {size} · {n} modelos · {active} activos",
66
76
  "proc.cpu": "CPU%",
67
77
  "proc.mem": "MEM",
68
78
  "proc.pid": "PID",
@@ -10,6 +10,7 @@ export const ptBR = {
10
10
  "panel.gpu": "GPU",
11
11
  "panel.tokens": "IA · TOKENS HOJE",
12
12
  "panel.vtex": "VTEX",
13
+ "panel.hf": "HUGGINGFACE",
13
14
  "panel.procs": "PROCESSOS",
14
15
  "status.cpu.ok": "tranquilo",
15
16
  "status.cpu.warn": "ocupado",
@@ -63,6 +64,15 @@ export const ptBR = {
63
64
  "vtex.workspace": "Workspace",
64
65
  "vtex.notConnected": "não conectado",
65
66
  "vtex.signin": "Entre com: vtex login <conta>",
67
+ "hf.model": "MODELO",
68
+ "hf.size": "TAMANHO",
69
+ "hf.type": "TIPO",
70
+ "hf.state": "ESTADO",
71
+ "hf.active": "ativo",
72
+ "hf.idle": "ocioso",
73
+ "hf.usedAgo": "usado há {ago}",
74
+ "hf.empty": "nenhum modelo em cache",
75
+ "hf.summary": "{path} · {size} · {n} modelos · {active} ativos",
66
76
  "proc.cpu": "CPU%",
67
77
  "proc.mem": "MEM",
68
78
  "proc.pid": "PID",
package/dist/icons.js CHANGED
@@ -19,6 +19,7 @@ const NERD = {
19
19
  tokens: "", // money
20
20
  vtex: "", // shopping-cart
21
21
  git: "\uF126", // code-fork
22
+ hf: "\uF1B3", // cubes (model artifacts)
22
23
  proc: "", // tasks
23
24
  clock: "", // clock
24
25
  };
@@ -36,6 +37,7 @@ const EMOJI = {
36
37
  tokens: "🪙",
37
38
  vtex: "🛒",
38
39
  git: "🌿",
40
+ hf: "🤗",
39
41
  proc: "📋",
40
42
  clock: "🕐",
41
43
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeluizr/lattice",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Real-time terminal dashboard for macOS Apple Silicon — GPU, power, temperatures/fans, per-disk I/O (incl. /Volumes), network, memory, processes, and AI token cost.",
5
5
  "type": "module",
6
6
  "bin": {