@emesoft/tokenhub 0.8.0 → 0.8.2
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/README.md +1 -1
- package/bin/banner.js +4 -0
- package/bin/tokenhub.js +6 -0
- package/lib/banner.d.ts +7 -0
- package/lib/banner.js +158 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ From then on, today's spend and your credit sit under the Claude Code prompt:
|
|
|
34
34
|
|
|
35
35
|
<img src="https://tokenhub.aimpact.io.vn/readme/statusline.png" alt="The TokenHub status line under the Claude Code prompt" width="560">
|
|
36
36
|
|
|
37
|
-
Prefer not to install? Use `npx @emesoft/tokenhub connect
|
|
37
|
+
Prefer not to install? Use `npx @emesoft/tokenhub connect <your tenant name>` instead.
|
|
38
38
|
|
|
39
39
|
## Commands
|
|
40
40
|
|
package/bin/banner.js
ADDED
package/bin/tokenhub.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { adminConfigPath, removeAdminConfig } from "../lib/admin.js";
|
|
4
|
+
import { showBanner } from "../lib/banner.js";
|
|
4
5
|
import { parseDashboardArgs, runDashboard } from "../lib/dashboard.js";
|
|
5
6
|
import { runOverlayInstall } from "../lib/overlay.js";
|
|
6
7
|
import { promptTenant, runConnect, TENANT_DOMAIN } from "../lib/pair.js";
|
|
@@ -24,6 +25,7 @@ Usage:
|
|
|
24
25
|
Live full-screen view of spend, pool health and recent requests (q quits)
|
|
25
26
|
tokenhub disconnect Remove the pool entries and the status line from settings.json
|
|
26
27
|
tokenhub disconnect --admin Forget the admin device token on this machine (revoke it on the pool's admin Devices page)
|
|
28
|
+
tokenhub banner Display the TokenHub welcome banner
|
|
27
29
|
tokenhub --help | --version
|
|
28
30
|
`;
|
|
29
31
|
|
|
@@ -45,6 +47,10 @@ async function main(argv) {
|
|
|
45
47
|
console.log(pkg.version);
|
|
46
48
|
return 0;
|
|
47
49
|
}
|
|
50
|
+
if (command === "banner") {
|
|
51
|
+
await showBanner();
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
48
54
|
const file = settingsPath();
|
|
49
55
|
if (command === "connect") {
|
|
50
56
|
const controller = new AbortController();
|
package/lib/banner.d.ts
ADDED
package/lib/banner.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenHub CLI welcome banner with progressive reveal animation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const TOKEN_COLOR = "\x1b[38;5;39;1m"; // Electric cyan bold
|
|
6
|
+
const HUB_COLOR = "\x1b[38;5;208;1m"; // TokenHub warm amber/clay bold
|
|
7
|
+
const CYAN = "\x1b[38;5;39m";
|
|
8
|
+
const MUTED = "\x1b[38;5;244m";
|
|
9
|
+
const GREEN = "\x1b[38;5;114m";
|
|
10
|
+
const RESET = "\x1b[0m";
|
|
11
|
+
const BOLD = "\x1b[1m";
|
|
12
|
+
|
|
13
|
+
export const LOGO_PARTS = [
|
|
14
|
+
{
|
|
15
|
+
token: " ████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗",
|
|
16
|
+
hub: "██╗ ██╗██╗ ██╗██████╗ ",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
token: " ╚══██╔══╝██╔═══██╗██║ ██╔╝██╔════╝████╗ ██║",
|
|
20
|
+
hub: "██║ ██║██║ ██║██╔══██╗",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
token: " ██║ ██║ ██║█████╔╝ █████╗ ██╔██╗ ██║",
|
|
24
|
+
hub: "███████║██║ ██║██████╔╝",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
token: " ██║ ██║ ██║██╔═██╗ ██╔══╝ ██║╚██╗██║",
|
|
28
|
+
hub: "██╔══██║██║ ██║██╔═══╝ ",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
token: " ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚████║",
|
|
32
|
+
hub: "██║ ██║╚██████╔╝██║ ",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
token: " ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝",
|
|
36
|
+
hub: "╚═╝ ╚═╝ ╚═════╝ ╚═╝ ",
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const SUBTITLE = " ⚡ Enterprise AI Gateway & Smart Pooling for Claude Code";
|
|
41
|
+
const DIVIDER = " ─────────────────────────────────────────────────────────────";
|
|
42
|
+
const ACTIONS = [
|
|
43
|
+
` ${BOLD}→ Get started:${RESET} ${CYAN}tokenhub connect <tenant>${RESET}`,
|
|
44
|
+
` ${BOLD}→ Spend & ops:${RESET} ${CYAN}tokenhub usage${RESET} ${MUTED}|${RESET} ${CYAN}tokenhub dashboard${RESET}`,
|
|
45
|
+
` ${BOLD}→ Docs & hub:${RESET} ${MUTED}https://tokenhub.aimpact.io.vn${RESET}`,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sleep helper for animations.
|
|
50
|
+
* @param {number} ms
|
|
51
|
+
* @returns {Promise<void>}
|
|
52
|
+
*/
|
|
53
|
+
function sleep(ms) {
|
|
54
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if the current terminal supports interactive animations and color.
|
|
59
|
+
* @param {NodeJS.WriteStream} [stream]
|
|
60
|
+
* @returns {boolean}
|
|
61
|
+
*/
|
|
62
|
+
export function isInteractive(stream = process.stdout) {
|
|
63
|
+
if (Boolean(process.env.CI) || Boolean(process.env.NO_COLOR)) return false;
|
|
64
|
+
return Boolean(stream?.isTTY);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Render the banner to the target stream.
|
|
69
|
+
* In interactive terminals, progressively reveals the logo and typewriter-reveals the text.
|
|
70
|
+
* In non-interactive environments, outputs the plain text cleanly.
|
|
71
|
+
*
|
|
72
|
+
* @param {object} [options]
|
|
73
|
+
* @param {NodeJS.WriteStream} [options.stream]
|
|
74
|
+
* @param {boolean} [options.animate]
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
77
|
+
export async function showBanner({ stream = process.stdout, animate } = {}) {
|
|
78
|
+
const interactive = animate ?? isInteractive(stream);
|
|
79
|
+
const color = !process.env.NO_COLOR && (interactive || Boolean(stream?.isTTY));
|
|
80
|
+
|
|
81
|
+
if (!interactive) {
|
|
82
|
+
// Non-interactive fallback: instant output
|
|
83
|
+
stream.write("\n");
|
|
84
|
+
for (const row of LOGO_PARTS) {
|
|
85
|
+
if (color) {
|
|
86
|
+
stream.write(`${TOKEN_COLOR}${row.token}${HUB_COLOR}${row.hub}${RESET}\n`);
|
|
87
|
+
} else {
|
|
88
|
+
stream.write(`${row.token}${row.hub}\n`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
stream.write("\n");
|
|
92
|
+
stream.write(color ? `${CYAN}${SUBTITLE}${RESET}\n` : `${SUBTITLE}\n`);
|
|
93
|
+
stream.write(color ? `${MUTED}${DIVIDER}${RESET}\n` : `${DIVIDER}\n`);
|
|
94
|
+
for (const action of ACTIONS) {
|
|
95
|
+
stream.write(color ? `${action}\n` : `${action.replace(/\x1b\[[0-9;]*m/g, "")}\n`);
|
|
96
|
+
}
|
|
97
|
+
stream.write("\n");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Interactive progressive reveal
|
|
102
|
+
stream.write("\n");
|
|
103
|
+
|
|
104
|
+
// Draw the logo lines progressively with a smooth typewriter scan
|
|
105
|
+
for (const row of LOGO_PARTS) {
|
|
106
|
+
const tokenPart = `${TOKEN_COLOR}${row.token}`;
|
|
107
|
+
const hubPart = `${HUB_COLOR}${row.hub}${RESET}\n`;
|
|
108
|
+
|
|
109
|
+
// Progressive character scan per line
|
|
110
|
+
const fullLine = `${tokenPart}${hubPart}`;
|
|
111
|
+
// Split into 4 chunks per line for high-tech sweep effect
|
|
112
|
+
const chunkSize = 16;
|
|
113
|
+
let idx = 0;
|
|
114
|
+
while (idx < row.token.length) {
|
|
115
|
+
const nextToken = row.token.slice(idx, idx + chunkSize);
|
|
116
|
+
stream.write(`${TOKEN_COLOR}${nextToken}${RESET}`);
|
|
117
|
+
idx += chunkSize;
|
|
118
|
+
await sleep(10);
|
|
119
|
+
}
|
|
120
|
+
idx = 0;
|
|
121
|
+
while (idx < row.hub.length) {
|
|
122
|
+
const nextHub = row.hub.slice(idx, idx + chunkSize);
|
|
123
|
+
stream.write(`${HUB_COLOR}${nextHub}${RESET}`);
|
|
124
|
+
idx += chunkSize;
|
|
125
|
+
await sleep(10);
|
|
126
|
+
}
|
|
127
|
+
stream.write("\n");
|
|
128
|
+
await sleep(25);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
stream.write("\n");
|
|
132
|
+
await sleep(40);
|
|
133
|
+
|
|
134
|
+
// Divider reveal
|
|
135
|
+
stream.write(`${MUTED}`);
|
|
136
|
+
for (let i = 0; i < DIVIDER.length; i += 4) {
|
|
137
|
+
stream.write(DIVIDER.slice(i, i + 4));
|
|
138
|
+
await sleep(8);
|
|
139
|
+
}
|
|
140
|
+
stream.write(`${RESET}\n`);
|
|
141
|
+
|
|
142
|
+
// Subtitle typewriter effect
|
|
143
|
+
stream.write(`${CYAN}`);
|
|
144
|
+
for (let i = 0; i < SUBTITLE.length; i++) {
|
|
145
|
+
stream.write(SUBTITLE[i]);
|
|
146
|
+
await sleep(8);
|
|
147
|
+
}
|
|
148
|
+
stream.write(`${RESET}\n`);
|
|
149
|
+
await sleep(40);
|
|
150
|
+
|
|
151
|
+
// Quick actions lines
|
|
152
|
+
for (const action of ACTIONS) {
|
|
153
|
+
stream.write(`${action}\n`);
|
|
154
|
+
await sleep(40);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
stream.write("\n");
|
|
158
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emesoft/tokenhub",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Connect Claude Code to a TokenHub pool in one command, and track your spend from the terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"bin": {
|
|
17
17
|
"tokenhub": "bin/tokenhub.js"
|
|
18
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"postinstall": "node bin/banner.js"
|
|
21
|
+
},
|
|
19
22
|
"files": [
|
|
20
23
|
"bin",
|
|
21
24
|
"lib",
|