@inferhub/usage 0.1.1 → 0.1.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/bin/statusline.mjs +5 -7
- package/dist/format.d.ts +3 -3
- package/dist/format.js +46 -24
- package/dist/format.test.js +38 -35
- package/package.json +1 -1
- package/src/format.test.ts +41 -35
- package/src/format.ts +61 -37
package/bin/statusline.mjs
CHANGED
|
@@ -22,15 +22,13 @@ try {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
try {
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
undefined;
|
|
30
|
-
|
|
25
|
+
// Do NOT pass Claude's session_id through: InferHub derives its own conversation
|
|
26
|
+
// key server-side (hash of system+first user message / X-Session-Id). Claude's
|
|
27
|
+
// UUID almost never matches, which produced "sess 0r/0". Default API path uses
|
|
28
|
+
// the user's latest InferHub session.
|
|
31
29
|
const { usage } = await client.fetchAccountUsageAuto({
|
|
32
30
|
window: "day",
|
|
33
|
-
|
|
31
|
+
force: false,
|
|
34
32
|
});
|
|
35
33
|
const contextPct =
|
|
36
34
|
typeof session?.context_window?.used_percentage === "number"
|
package/dist/format.d.ts
CHANGED
|
@@ -5,14 +5,14 @@ export declare function shortModel(model: string | null | undefined): string;
|
|
|
5
5
|
/**
|
|
6
6
|
* Compact one-liner for Claude statusline / Codex Stop summary.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Plain-language labels. Free models correctly show $0 billed while still
|
|
9
|
+
* reporting request/token activity.
|
|
10
10
|
*/
|
|
11
11
|
export declare function formatStatusLine(usage: AccountUsage, extras?: {
|
|
12
12
|
sessionCostUsd?: number | null;
|
|
13
13
|
contextPct?: number | null;
|
|
14
14
|
model?: string | null;
|
|
15
|
-
/** When true, include Claude
|
|
15
|
+
/** When true, include Claude local list-price estimate. Default false. */
|
|
16
16
|
showEstimate?: boolean;
|
|
17
17
|
}): string;
|
|
18
18
|
/** Multi-line human report for /usage skills. */
|
package/dist/format.js
CHANGED
|
@@ -20,38 +20,60 @@ export function shortModel(model) {
|
|
|
20
20
|
const parts = model.split("/");
|
|
21
21
|
return parts[parts.length - 1] || model;
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/** Human activity + optional billed spend. */
|
|
24
|
+
function activity(label, opts) {
|
|
25
|
+
const req = Number(opts.requests ?? 0);
|
|
26
|
+
const tok = Number(opts.totalTokens ?? 0);
|
|
27
|
+
const spend = Number(opts.spendUsdc ?? 0);
|
|
28
|
+
const bits = [];
|
|
29
|
+
if (req > 0 || tok > 0) {
|
|
30
|
+
bits.push(`${req.toLocaleString()} req`);
|
|
31
|
+
bits.push(`${tokens(tok)} tok`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
bits.push("no traffic");
|
|
35
|
+
}
|
|
25
36
|
if (spend > 0)
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
bits.push(money(spend));
|
|
38
|
+
else
|
|
39
|
+
bits.push("$0");
|
|
40
|
+
return `${label} ${bits.join(" / ")}`;
|
|
28
41
|
}
|
|
29
42
|
/**
|
|
30
43
|
* Compact one-liner for Claude statusline / Codex Stop summary.
|
|
31
44
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
45
|
+
* Plain-language labels. Free models correctly show $0 billed while still
|
|
46
|
+
* reporting request/token activity.
|
|
34
47
|
*/
|
|
35
48
|
export function formatStatusLine(usage, extras) {
|
|
36
|
-
const
|
|
37
|
-
? spendOrActivity(usage.session.spend_usdc, usage.session.requests, usage.session.total_tokens)
|
|
38
|
-
: null;
|
|
39
|
-
const day = spendOrActivity(usage.window.spend_usdc, usage.window.requests, usage.window.total_tokens);
|
|
40
|
-
const all = money(usage.all_time.spend_usdc);
|
|
49
|
+
const model = extras?.model ? shortModel(extras.model) : null;
|
|
41
50
|
const bal = money(usage.balance.amount_usdc);
|
|
42
51
|
const top = shortModel(usage.top_model?.model);
|
|
43
|
-
const model = extras?.model ? shortModel(extras.model) : null;
|
|
44
52
|
const ctx = extras?.contextPct != null && Number.isFinite(extras.contextPct)
|
|
45
|
-
? `${Math.round(extras.contextPct)}%
|
|
53
|
+
? `${Math.round(extras.contextPct)}% context`
|
|
46
54
|
: null;
|
|
47
55
|
const parts = ["InferHub"];
|
|
48
56
|
if (model)
|
|
49
57
|
parts.push(model);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
// Latest conversation on InferHub (server-derived session), if present and useful
|
|
59
|
+
if (usage.session && (usage.session.requests > 0 || Number(usage.session.spend_usdc) > 0)) {
|
|
60
|
+
parts.push(activity("session", {
|
|
61
|
+
spendUsdc: usage.session.spend_usdc,
|
|
62
|
+
requests: usage.session.requests,
|
|
63
|
+
totalTokens: usage.session.total_tokens,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
parts.push(activity("today", {
|
|
67
|
+
spendUsdc: usage.window.spend_usdc,
|
|
68
|
+
requests: usage.window.requests,
|
|
69
|
+
totalTokens: usage.window.total_tokens,
|
|
70
|
+
}));
|
|
71
|
+
parts.push(activity("all-time", {
|
|
72
|
+
spendUsdc: usage.all_time.spend_usdc,
|
|
73
|
+
requests: usage.all_time.requests,
|
|
74
|
+
totalTokens: usage.all_time.total_tokens,
|
|
75
|
+
}));
|
|
76
|
+
parts.push(`balance ${bal}`);
|
|
55
77
|
if (top && top !== "—")
|
|
56
78
|
parts.push(`top ${top}`);
|
|
57
79
|
if (ctx)
|
|
@@ -60,21 +82,21 @@ export function formatStatusLine(usage, extras) {
|
|
|
60
82
|
extras.sessionCostUsd != null &&
|
|
61
83
|
Number.isFinite(extras.sessionCostUsd) &&
|
|
62
84
|
extras.sessionCostUsd > 0) {
|
|
63
|
-
parts.push(`est ${money(extras.sessionCostUsd)}`);
|
|
85
|
+
parts.push(`claude-est ${money(extras.sessionCostUsd)}`);
|
|
64
86
|
}
|
|
65
87
|
return parts.join(" · ");
|
|
66
88
|
}
|
|
67
89
|
/** Multi-line human report for /usage skills. */
|
|
68
90
|
export function formatReport(usage) {
|
|
69
91
|
const lines = [
|
|
70
|
-
"InferHub usage",
|
|
71
|
-
` Balance:
|
|
92
|
+
"InferHub usage (billed USDC)",
|
|
93
|
+
` Balance: ${money(usage.balance.amount_usdc)} remaining`,
|
|
72
94
|
];
|
|
73
95
|
if (usage.session) {
|
|
74
|
-
lines.push(` Session:
|
|
96
|
+
lines.push(` Session: ${usage.session.requests} requests · ${tokens(usage.session.total_tokens)} tokens · ${money(usage.session.spend_usdc)} billed`);
|
|
75
97
|
}
|
|
76
|
-
lines.push(` ${usage.window.
|
|
98
|
+
lines.push(` Today: ${usage.window.requests} requests · ${tokens(usage.window.total_tokens)} tokens · ${money(usage.window.spend_usdc)} billed`, ` All-time: ${usage.all_time.requests} requests · ${tokens(usage.all_time.total_tokens)} tokens · ${money(usage.all_time.spend_usdc)} billed`, ` Top model: ${usage.top_model?.model ?? "—"} (${usage.top_model?.requests ?? 0} req · ${money(usage.top_model?.spend_usdc ?? 0)})`);
|
|
77
99
|
if (usage.cache?.cached)
|
|
78
|
-
lines.push(` (cached
|
|
100
|
+
lines.push(` (cached ${usage.cache.ttl_seconds}s)`);
|
|
79
101
|
return lines.join("\n");
|
|
80
102
|
}
|
package/dist/format.test.js
CHANGED
|
@@ -9,7 +9,7 @@ test("usageUrl", () => {
|
|
|
9
9
|
assert.equal(usageUrl("https://api.inferhub.dev", "day", "UTC"), "https://api.inferhub.dev/v1/me/usage?window=day&tz=UTC");
|
|
10
10
|
assert.equal(usageUrl("https://api.inferhub.dev", "day", "UTC", "abc"), "https://api.inferhub.dev/v1/me/usage?window=day&tz=UTC&session_id=abc");
|
|
11
11
|
});
|
|
12
|
-
test("formatStatusLine", () => {
|
|
12
|
+
test("formatStatusLine is plain language", () => {
|
|
13
13
|
const usage = {
|
|
14
14
|
object: "account.usage",
|
|
15
15
|
currency: "USDC",
|
|
@@ -19,64 +19,67 @@ test("formatStatusLine", () => {
|
|
|
19
19
|
tz: "UTC",
|
|
20
20
|
since: "",
|
|
21
21
|
until: "",
|
|
22
|
-
requests:
|
|
23
|
-
prompt_tokens:
|
|
24
|
-
completion_tokens:
|
|
25
|
-
total_tokens:
|
|
26
|
-
spend_usdc: "0
|
|
22
|
+
requests: 86,
|
|
23
|
+
prompt_tokens: 14_000_000,
|
|
24
|
+
completion_tokens: 100_000,
|
|
25
|
+
total_tokens: 14_100_000,
|
|
26
|
+
spend_usdc: "0",
|
|
27
27
|
},
|
|
28
28
|
all_time: {
|
|
29
|
-
requests:
|
|
30
|
-
prompt_tokens:
|
|
31
|
-
completion_tokens:
|
|
32
|
-
total_tokens:
|
|
33
|
-
spend_usdc: "0.
|
|
29
|
+
requests: 3890,
|
|
30
|
+
prompt_tokens: 300_000_000,
|
|
31
|
+
completion_tokens: 1_000_000,
|
|
32
|
+
total_tokens: 301_000_000,
|
|
33
|
+
spend_usdc: "0.410000",
|
|
34
34
|
},
|
|
35
35
|
session: {
|
|
36
36
|
id: "sess-1",
|
|
37
|
-
requests:
|
|
38
|
-
prompt_tokens:
|
|
39
|
-
completion_tokens:
|
|
40
|
-
total_tokens:
|
|
41
|
-
spend_usdc: "0
|
|
37
|
+
requests: 12,
|
|
38
|
+
prompt_tokens: 200_000,
|
|
39
|
+
completion_tokens: 5_000,
|
|
40
|
+
total_tokens: 205_000,
|
|
41
|
+
spend_usdc: "0",
|
|
42
42
|
since: null,
|
|
43
43
|
until: null,
|
|
44
44
|
},
|
|
45
45
|
top_model: {
|
|
46
|
-
model: "
|
|
47
|
-
requests:
|
|
48
|
-
tokens:
|
|
49
|
-
spend_usdc: "0.
|
|
46
|
+
model: "cx/gpt-5.5",
|
|
47
|
+
requests: 212,
|
|
48
|
+
tokens: 239830,
|
|
49
|
+
spend_usdc: "0.137155",
|
|
50
50
|
},
|
|
51
51
|
cache: { cached: false, ttl_seconds: 30 },
|
|
52
52
|
};
|
|
53
53
|
const line = formatStatusLine(usage, {
|
|
54
|
-
sessionCostUsd: 2.69,
|
|
55
|
-
contextPct: 12,
|
|
56
54
|
model: "free/grok/grok-4.5",
|
|
55
|
+
contextPct: 8,
|
|
56
|
+
sessionCostUsd: 2.69,
|
|
57
57
|
});
|
|
58
58
|
assert.match(line, /^InferHub/);
|
|
59
|
+
assert.match(line, /session 12 req \/ 205\.0k tok \/ \$0/);
|
|
60
|
+
assert.match(line, /today 86 req \/ 14\.1M tok \/ \$0/);
|
|
61
|
+
assert.match(line, /all-time 3,890 req \/ 301\.0M tok \/ \$0\.41/);
|
|
62
|
+
assert.match(line, /balance \$1\.25/);
|
|
63
|
+
assert.match(line, /top gpt-5\.5/);
|
|
64
|
+
assert.match(line, /8% context/);
|
|
59
65
|
assert.doesNotMatch(line, /\bIH\b/);
|
|
60
|
-
assert.
|
|
61
|
-
assert.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const freeDay = {
|
|
66
|
+
assert.doesNotMatch(line, /0r\/0/);
|
|
67
|
+
assert.doesNotMatch(line, /claude-est/); // estimate off by default
|
|
68
|
+
// empty/zero session should be omitted (not "sess 0r/0")
|
|
69
|
+
const noSess = {
|
|
65
70
|
...usage,
|
|
66
71
|
session: {
|
|
67
|
-
id: "
|
|
68
|
-
requests:
|
|
69
|
-
prompt_tokens:
|
|
72
|
+
id: "empty",
|
|
73
|
+
requests: 0,
|
|
74
|
+
prompt_tokens: 0,
|
|
70
75
|
completion_tokens: 0,
|
|
71
|
-
total_tokens:
|
|
76
|
+
total_tokens: 0,
|
|
72
77
|
spend_usdc: "0",
|
|
73
78
|
since: null,
|
|
74
79
|
until: null,
|
|
75
80
|
},
|
|
76
|
-
window: { ...usage.window, spend_usdc: "0", requests: 86, total_tokens: 14_100_000 },
|
|
77
81
|
};
|
|
78
|
-
const
|
|
79
|
-
assert.
|
|
80
|
-
assert.match(freeLine, /day 86r\/14\.1M/);
|
|
82
|
+
const line2 = formatStatusLine(noSess, { model: "Grok" });
|
|
83
|
+
assert.doesNotMatch(line2, /session /);
|
|
81
84
|
assert.equal(money("0.001"), "$0.0010");
|
|
82
85
|
});
|
package/package.json
CHANGED
package/src/format.test.ts
CHANGED
|
@@ -19,7 +19,7 @@ test("usageUrl", () => {
|
|
|
19
19
|
);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
test("formatStatusLine", () => {
|
|
22
|
+
test("formatStatusLine is plain language", () => {
|
|
23
23
|
const usage: AccountUsage = {
|
|
24
24
|
object: "account.usage",
|
|
25
25
|
currency: "USDC",
|
|
@@ -29,65 +29,71 @@ test("formatStatusLine", () => {
|
|
|
29
29
|
tz: "UTC",
|
|
30
30
|
since: "",
|
|
31
31
|
until: "",
|
|
32
|
-
requests:
|
|
33
|
-
prompt_tokens:
|
|
34
|
-
completion_tokens:
|
|
35
|
-
total_tokens:
|
|
36
|
-
spend_usdc: "0
|
|
32
|
+
requests: 86,
|
|
33
|
+
prompt_tokens: 14_000_000,
|
|
34
|
+
completion_tokens: 100_000,
|
|
35
|
+
total_tokens: 14_100_000,
|
|
36
|
+
spend_usdc: "0",
|
|
37
37
|
},
|
|
38
38
|
all_time: {
|
|
39
|
-
requests:
|
|
40
|
-
prompt_tokens:
|
|
41
|
-
completion_tokens:
|
|
42
|
-
total_tokens:
|
|
43
|
-
spend_usdc: "0.
|
|
39
|
+
requests: 3890,
|
|
40
|
+
prompt_tokens: 300_000_000,
|
|
41
|
+
completion_tokens: 1_000_000,
|
|
42
|
+
total_tokens: 301_000_000,
|
|
43
|
+
spend_usdc: "0.410000",
|
|
44
44
|
},
|
|
45
45
|
session: {
|
|
46
46
|
id: "sess-1",
|
|
47
|
-
requests:
|
|
48
|
-
prompt_tokens:
|
|
49
|
-
completion_tokens:
|
|
50
|
-
total_tokens:
|
|
51
|
-
spend_usdc: "0
|
|
47
|
+
requests: 12,
|
|
48
|
+
prompt_tokens: 200_000,
|
|
49
|
+
completion_tokens: 5_000,
|
|
50
|
+
total_tokens: 205_000,
|
|
51
|
+
spend_usdc: "0",
|
|
52
52
|
since: null,
|
|
53
53
|
until: null,
|
|
54
54
|
},
|
|
55
55
|
top_model: {
|
|
56
|
-
model: "
|
|
57
|
-
requests:
|
|
58
|
-
tokens:
|
|
59
|
-
spend_usdc: "0.
|
|
56
|
+
model: "cx/gpt-5.5",
|
|
57
|
+
requests: 212,
|
|
58
|
+
tokens: 239830,
|
|
59
|
+
spend_usdc: "0.137155",
|
|
60
60
|
},
|
|
61
61
|
cache: { cached: false, ttl_seconds: 30 },
|
|
62
62
|
};
|
|
63
|
+
|
|
63
64
|
const line = formatStatusLine(usage, {
|
|
64
|
-
sessionCostUsd: 2.69,
|
|
65
|
-
contextPct: 12,
|
|
66
65
|
model: "free/grok/grok-4.5",
|
|
66
|
+
contextPct: 8,
|
|
67
|
+
sessionCostUsd: 2.69,
|
|
67
68
|
});
|
|
69
|
+
|
|
68
70
|
assert.match(line, /^InferHub/);
|
|
71
|
+
assert.match(line, /session 12 req \/ 205\.0k tok \/ \$0/);
|
|
72
|
+
assert.match(line, /today 86 req \/ 14\.1M tok \/ \$0/);
|
|
73
|
+
assert.match(line, /all-time 3,890 req \/ 301\.0M tok \/ \$0\.41/);
|
|
74
|
+
assert.match(line, /balance \$1\.25/);
|
|
75
|
+
assert.match(line, /top gpt-5\.5/);
|
|
76
|
+
assert.match(line, /8% context/);
|
|
69
77
|
assert.doesNotMatch(line, /\bIH\b/);
|
|
70
|
-
assert.
|
|
71
|
-
assert.
|
|
72
|
-
assert.match(line, /top grok-4\.5/);
|
|
73
|
-
assert.doesNotMatch(line, /est /);
|
|
78
|
+
assert.doesNotMatch(line, /0r\/0/);
|
|
79
|
+
assert.doesNotMatch(line, /claude-est/); // estimate off by default
|
|
74
80
|
|
|
75
|
-
|
|
81
|
+
// empty/zero session should be omitted (not "sess 0r/0")
|
|
82
|
+
const noSess: AccountUsage = {
|
|
76
83
|
...usage,
|
|
77
84
|
session: {
|
|
78
|
-
id: "
|
|
79
|
-
requests:
|
|
80
|
-
prompt_tokens:
|
|
85
|
+
id: "empty",
|
|
86
|
+
requests: 0,
|
|
87
|
+
prompt_tokens: 0,
|
|
81
88
|
completion_tokens: 0,
|
|
82
|
-
total_tokens:
|
|
89
|
+
total_tokens: 0,
|
|
83
90
|
spend_usdc: "0",
|
|
84
91
|
since: null,
|
|
85
92
|
until: null,
|
|
86
93
|
},
|
|
87
|
-
window: { ...usage.window, spend_usdc: "0", requests: 86, total_tokens: 14_100_000 },
|
|
88
94
|
};
|
|
89
|
-
const
|
|
90
|
-
assert.
|
|
91
|
-
|
|
95
|
+
const line2 = formatStatusLine(noSess, { model: "Grok" });
|
|
96
|
+
assert.doesNotMatch(line2, /session /);
|
|
97
|
+
|
|
92
98
|
assert.equal(money("0.001"), "$0.0010");
|
|
93
99
|
});
|
package/src/format.ts
CHANGED
|
@@ -20,21 +20,35 @@ export function shortModel(model: string | null | undefined): string {
|
|
|
20
20
|
return parts[parts.length - 1] || model;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
/** Human activity + optional billed spend. */
|
|
24
|
+
function activity(
|
|
25
|
+
label: string,
|
|
26
|
+
opts: {
|
|
27
|
+
spendUsdc?: string | number | null;
|
|
28
|
+
requests?: number | null;
|
|
29
|
+
totalTokens?: number | null;
|
|
30
|
+
},
|
|
27
31
|
): string {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
const req = Number(opts.requests ?? 0);
|
|
33
|
+
const tok = Number(opts.totalTokens ?? 0);
|
|
34
|
+
const spend = Number(opts.spendUsdc ?? 0);
|
|
35
|
+
const bits: string[] = [];
|
|
36
|
+
if (req > 0 || tok > 0) {
|
|
37
|
+
bits.push(`${req.toLocaleString()} req`);
|
|
38
|
+
bits.push(`${tokens(tok)} tok`);
|
|
39
|
+
} else {
|
|
40
|
+
bits.push("no traffic");
|
|
41
|
+
}
|
|
42
|
+
if (spend > 0) bits.push(money(spend));
|
|
43
|
+
else bits.push("$0");
|
|
44
|
+
return `${label} ${bits.join(" / ")}`;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
/**
|
|
34
48
|
* Compact one-liner for Claude statusline / Codex Stop summary.
|
|
35
49
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
50
|
+
* Plain-language labels. Free models correctly show $0 billed while still
|
|
51
|
+
* reporting request/token activity.
|
|
38
52
|
*/
|
|
39
53
|
export function formatStatusLine(
|
|
40
54
|
usage: AccountUsage,
|
|
@@ -42,37 +56,47 @@ export function formatStatusLine(
|
|
|
42
56
|
sessionCostUsd?: number | null;
|
|
43
57
|
contextPct?: number | null;
|
|
44
58
|
model?: string | null;
|
|
45
|
-
/** When true, include Claude
|
|
59
|
+
/** When true, include Claude local list-price estimate. Default false. */
|
|
46
60
|
showEstimate?: boolean;
|
|
47
61
|
},
|
|
48
62
|
): string {
|
|
49
|
-
const
|
|
50
|
-
? spendOrActivity(
|
|
51
|
-
usage.session.spend_usdc,
|
|
52
|
-
usage.session.requests,
|
|
53
|
-
usage.session.total_tokens,
|
|
54
|
-
)
|
|
55
|
-
: null;
|
|
56
|
-
const day = spendOrActivity(
|
|
57
|
-
usage.window.spend_usdc,
|
|
58
|
-
usage.window.requests,
|
|
59
|
-
usage.window.total_tokens,
|
|
60
|
-
);
|
|
61
|
-
const all = money(usage.all_time.spend_usdc);
|
|
63
|
+
const model = extras?.model ? shortModel(extras.model) : null;
|
|
62
64
|
const bal = money(usage.balance.amount_usdc);
|
|
63
65
|
const top = shortModel(usage.top_model?.model);
|
|
64
|
-
const model = extras?.model ? shortModel(extras.model) : null;
|
|
65
66
|
const ctx =
|
|
66
67
|
extras?.contextPct != null && Number.isFinite(extras.contextPct)
|
|
67
|
-
? `${Math.round(extras.contextPct)}%
|
|
68
|
+
? `${Math.round(extras.contextPct)}% context`
|
|
68
69
|
: null;
|
|
69
70
|
|
|
70
71
|
const parts: string[] = ["InferHub"];
|
|
71
72
|
if (model) parts.push(model);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
|
|
74
|
+
// Latest conversation on InferHub (server-derived session), if present and useful
|
|
75
|
+
if (usage.session && (usage.session.requests > 0 || Number(usage.session.spend_usdc) > 0)) {
|
|
76
|
+
parts.push(
|
|
77
|
+
activity("session", {
|
|
78
|
+
spendUsdc: usage.session.spend_usdc,
|
|
79
|
+
requests: usage.session.requests,
|
|
80
|
+
totalTokens: usage.session.total_tokens,
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
parts.push(
|
|
86
|
+
activity("today", {
|
|
87
|
+
spendUsdc: usage.window.spend_usdc,
|
|
88
|
+
requests: usage.window.requests,
|
|
89
|
+
totalTokens: usage.window.total_tokens,
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
parts.push(
|
|
93
|
+
activity("all-time", {
|
|
94
|
+
spendUsdc: usage.all_time.spend_usdc,
|
|
95
|
+
requests: usage.all_time.requests,
|
|
96
|
+
totalTokens: usage.all_time.total_tokens,
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
parts.push(`balance ${bal}`);
|
|
76
100
|
if (top && top !== "—") parts.push(`top ${top}`);
|
|
77
101
|
if (ctx) parts.push(ctx);
|
|
78
102
|
|
|
@@ -82,7 +106,7 @@ export function formatStatusLine(
|
|
|
82
106
|
Number.isFinite(extras.sessionCostUsd) &&
|
|
83
107
|
extras.sessionCostUsd > 0
|
|
84
108
|
) {
|
|
85
|
-
parts.push(`est ${money(extras.sessionCostUsd)}`);
|
|
109
|
+
parts.push(`claude-est ${money(extras.sessionCostUsd)}`);
|
|
86
110
|
}
|
|
87
111
|
|
|
88
112
|
return parts.join(" · ");
|
|
@@ -91,19 +115,19 @@ export function formatStatusLine(
|
|
|
91
115
|
/** Multi-line human report for /usage skills. */
|
|
92
116
|
export function formatReport(usage: AccountUsage): string {
|
|
93
117
|
const lines = [
|
|
94
|
-
"InferHub usage",
|
|
95
|
-
` Balance:
|
|
118
|
+
"InferHub usage (billed USDC)",
|
|
119
|
+
` Balance: ${money(usage.balance.amount_usdc)} remaining`,
|
|
96
120
|
];
|
|
97
121
|
if (usage.session) {
|
|
98
122
|
lines.push(
|
|
99
|
-
` Session:
|
|
123
|
+
` Session: ${usage.session.requests} requests · ${tokens(usage.session.total_tokens)} tokens · ${money(usage.session.spend_usdc)} billed`,
|
|
100
124
|
);
|
|
101
125
|
}
|
|
102
126
|
lines.push(
|
|
103
|
-
` ${usage.window.
|
|
104
|
-
` All-time:
|
|
105
|
-
` Top model:
|
|
127
|
+
` Today: ${usage.window.requests} requests · ${tokens(usage.window.total_tokens)} tokens · ${money(usage.window.spend_usdc)} billed`,
|
|
128
|
+
` All-time: ${usage.all_time.requests} requests · ${tokens(usage.all_time.total_tokens)} tokens · ${money(usage.all_time.spend_usdc)} billed`,
|
|
129
|
+
` Top model: ${usage.top_model?.model ?? "—"} (${usage.top_model?.requests ?? 0} req · ${money(usage.top_model?.spend_usdc ?? 0)})`,
|
|
106
130
|
);
|
|
107
|
-
if (usage.cache?.cached) lines.push(` (cached
|
|
131
|
+
if (usage.cache?.cached) lines.push(` (cached ${usage.cache.ttl_seconds}s)`);
|
|
108
132
|
return lines.join("\n");
|
|
109
133
|
}
|