@naraya/cli 0.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/src/status.mjs ADDED
@@ -0,0 +1,71 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { BASE_URL as DEFAULT_BASE_URL } from "./config.mjs";
4
+
5
+ /** Read the saved key from models.json (written by `naraya login`). Null if absent. */
6
+ function readKey(agentDir) {
7
+ const p = path.join(agentDir, "models.json");
8
+ if (!fs.existsSync(p)) return null;
9
+ try {
10
+ return JSON.parse(fs.readFileSync(p, "utf8")).providers?.naraya?.apiKey ?? null;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ /** Format the credit. Credits are Rupiah (1 credit = Rp1); `--usd` shows the
17
+ * server-provided USD equivalent instead. */
18
+ function formatCredit(credit, usd) {
19
+ if (usd && credit?.usd_equivalent != null) {
20
+ return `$${Number(credit.usd_equivalent).toFixed(2)} available (USD est.)`;
21
+ }
22
+ const amount = Number(credit?.available ?? 0);
23
+ return `Rp ${amount.toLocaleString("id-ID", { maximumFractionDigits: 0 })} available`;
24
+ }
25
+
26
+ /**
27
+ * Format the /v1/me response into a printable block. Pure (no I/O).
28
+ * quota.remaining/limit are DAILY TOKENS (unit:"tokens"), never request counts —
29
+ * usage.requests_* are the counts, kept separate.
30
+ */
31
+ export function renderStatus(s, { usd = false } = {}) {
32
+ const quota =
33
+ s.quota?.limit > 0
34
+ ? `${s.quota.remaining.toLocaleString("en-US")} / ${s.quota.limit.toLocaleString("en-US")} ${s.quota.unit ?? "tokens"} today (reset ${String(s.quota.reset_at).slice(0, 10)})`
35
+ : "no daily cap (fair-use)";
36
+ const models = Array.isArray(s.models) ? s.models : [];
37
+ const modelList = models.slice(0, 6).join(", ") + (models.length > 6 ? ` … (${models.length})` : ` (${models.length})`);
38
+ const ok = s.usage ? `${s.usage.requests_today} req, ${(s.usage.success_rate * 100).toFixed(1)}% ok` : "";
39
+ return [
40
+ `Signed in: ${s.account?.email ?? "?"} [${s.account?.plan ?? "?"}]`,
41
+ `Credit: ${formatCredit(s.credit, usd)}`,
42
+ `Quota: ${quota}`,
43
+ `Today: ${ok}`,
44
+ `Models: ${modelList}`,
45
+ ].join("\n");
46
+ }
47
+
48
+ /** Fetch /v1/me with the saved key and print the status block. */
49
+ export async function status(agentDir, { BASE_URL = DEFAULT_BASE_URL } = {}, { usd = false } = {}) {
50
+ const key = readKey(agentDir);
51
+ if (!key) {
52
+ console.error("Run `naraya login` first.");
53
+ process.exit(1);
54
+ }
55
+ let res;
56
+ try {
57
+ res = await fetch(`${BASE_URL}/me`, { headers: { authorization: `Bearer ${key}` } });
58
+ } catch (err) {
59
+ console.error(`status failed: ${err.message}`);
60
+ process.exit(1);
61
+ }
62
+ if (res.status === 401) {
63
+ console.error("Key rejected. Run `naraya login` again.");
64
+ process.exit(1);
65
+ }
66
+ if (!res.ok) {
67
+ console.error(`status failed: HTTP ${res.status}`);
68
+ process.exit(1);
69
+ }
70
+ console.log(renderStatus(await res.json(), { usd }));
71
+ }