@emesoft/tokenhub 0.6.0 → 0.7.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/README.md CHANGED
@@ -12,8 +12,13 @@ npx @emesoft/tokenhub dashboard
12
12
  npx @emesoft/tokenhub disconnect
13
13
  npx @emesoft/tokenhub connect --admin
14
14
  npx @emesoft/tokenhub disconnect --admin
15
+ npx @emesoft/tokenhub overlay
15
16
  ```
16
17
 
18
+ To type `tokenhub` without `npx`, install it **globally**: `npm install -g @emesoft/tokenhub`, then `tokenhub connect`. A plain `npm install @emesoft/tokenhub` puts it in the current folder's `node_modules`, which is not on your `PATH` (only `npx` finds it there). With nvm, global packages belong to one Node version: install again after `nvm use`.
19
+
20
+ `overlay` (Windows) downloads the TokenHub desktop overlay installer from the pool (`/downloads/overlay.exe`) and starts it. The overlay then shows your own usage after `connect`, or the whole pool after `connect --admin`.
21
+
17
22
  `connect` prints a short code and opens your browser. Confirm the code on the pool's member site and the CLI writes `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN` into `~/.claude/settings.json` (or `$CLAUDE_CONFIG_DIR/settings.json`). It also adds a `statusLine` that shows today's spend and credit under the Claude Code prompt (pass `--no-statusline` to skip it; a `statusLine` you already had is left alone). Other settings are kept, and the previous file is saved as `settings.json.bak`. Each paired machine gets its own key, which you can revoke on the Keys page.
18
23
 
19
24
  `usage` shows today's and this week's spend against your limits, your credit and the pool's health (`--json` for the raw report). It exits 2 when the pool would currently refuse your requests (out of credit or budget reached).
@@ -24,7 +29,7 @@ Requests to the pool carry your key and one extra header, `x-tokenhub-cli: <vers
24
29
 
25
30
  `disconnect` removes only those two entries and the TokenHub `statusLine`.
26
31
 
27
- `connect --admin` is for pool admins. It pairs this machine as an *admin device* for the TokenHub desktop overlay: you confirm the code on the pool's **admin** console (so only the signed-in admin can approve it), and the CLI saves a read-only device token to `~/.tokenhub/admin.json` (or `$TOKENHUB_CONFIG_DIR/admin.json`). It never touches Claude Code's settings. The token can read account names and quota levels, nothing else. Revoke a device on the admin Devices page (`/admin/pair`); `disconnect --admin` just forgets the token locally.
32
+ `connect --admin` is for pool admins. It pairs this machine as an *admin device* for the TokenHub desktop overlay: you confirm the code on the pool's **admin** console (so only the signed-in admin can approve it), and the CLI saves a device token to `~/.tokenhub/admin.json` (or `$TOKENHUB_CONFIG_DIR/admin.json`). It never touches Claude Code's settings. The token can read account names and quota levels and switch accounts on or off (the overlay's switch), nothing else: no emails, tokens, members or settings. Revoke a device on the admin Devices page (`/admin/pair`); `disconnect --admin` just forgets the token locally.
28
33
 
29
34
  If `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN` or `ANTHROPIC_API_KEY` is set in your shell, it overrides `settings.json`.
30
35
 
package/bin/tokenhub.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import { adminConfigPath, removeAdminConfig } from "../lib/admin.js";
4
4
  import { parseDashboardArgs, runDashboard } from "../lib/dashboard.js";
5
+ import { runOverlayInstall } from "../lib/overlay.js";
5
6
  import { DEFAULT_POOL_URL, runConnect } from "../lib/pair.js";
6
7
  import { disconnectSettings, readSettings, settingsPath } from "../lib/settings.js";
7
8
  import { fetchUsage, formatUsage, resolveCredentials } from "../lib/usage.js";
@@ -16,6 +17,7 @@ Usage:
16
17
  tokenhub connect [poolUrl] --admin
17
18
  Pair this machine as an admin device (for the desktop overlay's pool view): the admin
18
19
  confirms in the console, the token is saved to ~/.tokenhub/admin.json
20
+ tokenhub overlay [poolUrl] Download and install the TokenHub desktop overlay (Windows)
19
21
  tokenhub usage [--json] Show today's and this week's spend, credit and pool health
20
22
  tokenhub dashboard [--interval <sec>] [--once]
21
23
  Live full-screen view of spend, pool health and recent requests (q quits)
@@ -55,6 +57,9 @@ async function main(argv) {
55
57
  adminFile: adminConfigPath(),
56
58
  });
57
59
  }
60
+ if (command === "overlay") {
61
+ return runOverlayInstall(arg ? { poolUrl: arg } : {});
62
+ }
58
63
  if (command === "usage") {
59
64
  try {
60
65
  const credentials = resolveCredentials(readSettings(file).settings);
@@ -0,0 +1,9 @@
1
+ export const OVERLAY_PATH: string;
2
+ export function runOverlayInstall(options?: {
3
+ poolUrl?: string;
4
+ log?: (line: string) => void;
5
+ platform?: string;
6
+ run?: (file: string) => void;
7
+ dir?: string;
8
+ fetchImpl?: typeof fetch;
9
+ }): Promise<number>;
package/lib/overlay.js ADDED
@@ -0,0 +1,81 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdtempSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { normalizeUrl } from "./settings.js";
6
+ import { DEFAULT_POOL_URL } from "./pair.js";
7
+
8
+ /** Path of the overlay installer on the pool. */
9
+ export const OVERLAY_PATH = "/downloads/overlay.exe";
10
+
11
+ /**
12
+ * Start the installer and let it run on its own (it shows its own window; this process does not wait for it).
13
+ * @param {string} file Installer path.
14
+ * @returns {void}
15
+ */
16
+ function launch(file) {
17
+ const child = spawn(file, [], { detached: true, stdio: "ignore", windowsHide: false });
18
+ child.on("error", () => {});
19
+ child.unref();
20
+ }
21
+
22
+ /**
23
+ * Download the TokenHub desktop overlay's Windows installer from the pool and start it.
24
+ * @param {object} options
25
+ * @param {string} [options.poolUrl] Pool address (default `DEFAULT_POOL_URL`).
26
+ * @param {(line: string) => void} [options.log] Output sink.
27
+ * @param {string} [options.platform] Node platform name; the overlay is Windows only.
28
+ * @param {(file: string) => void} [options.run] Starts the downloaded installer (tests replace it).
29
+ * @param {string} [options.dir] Folder to save the installer in (default a new temp folder).
30
+ * @param {typeof fetch} [options.fetchImpl] `fetch` override for tests.
31
+ * @returns {Promise<number>} Process exit code.
32
+ */
33
+ export async function runOverlayInstall({
34
+ poolUrl = DEFAULT_POOL_URL,
35
+ log = console.log,
36
+ platform = process.platform,
37
+ run = launch,
38
+ dir,
39
+ fetchImpl = fetch,
40
+ } = {}) {
41
+ if (platform !== "win32") {
42
+ log("The TokenHub overlay is a Windows app; there is no build for this system yet.");
43
+ return 1;
44
+ }
45
+ let url;
46
+ try {
47
+ url = `${normalizeUrl(poolUrl)}${OVERLAY_PATH}`;
48
+ } catch (error) {
49
+ log(`Error: ${error.message}`);
50
+ return 1;
51
+ }
52
+ log(`Downloading the overlay from ${url} ...`);
53
+ let res;
54
+ try {
55
+ res = await fetchImpl(url, { signal: AbortSignal.timeout(120_000) });
56
+ } catch (error) {
57
+ log(`Error: could not reach the pool (${error.message}).`);
58
+ return 1;
59
+ }
60
+ if (res.status === 404) {
61
+ log("This pool has not published the overlay installer yet. Ask your admin.");
62
+ return 1;
63
+ }
64
+ if (!res.ok) {
65
+ log(`Error: the pool answered HTTP ${res.status}.`);
66
+ return 1;
67
+ }
68
+ const bytes = Buffer.from(await res.arrayBuffer());
69
+ const expected = Number(res.headers.get("content-length"));
70
+ // A Windows executable starts with "MZ"; anything else (an HTML error page, a cut-off download) is not run.
71
+ if ((expected > 0 && bytes.length !== expected) || bytes.length < 2 || bytes[0] !== 0x4d || bytes[1] !== 0x5a) {
72
+ log("Error: the download is incomplete or not an installer. Try again.");
73
+ return 1;
74
+ }
75
+ const file = join(dir ?? mkdtempSync(join(tmpdir(), "tokenhub-overlay-")), "TokenHub-Overlay-setup.exe");
76
+ writeFileSync(file, bytes);
77
+ log(`Downloaded ${(bytes.length / 1024 / 1024).toFixed(1)} MB. Starting the installer...`);
78
+ run(file);
79
+ log("Follow the installer. The overlay picks up `tokenhub connect` (your usage) or `tokenhub connect --admin` (the pool).");
80
+ return 0;
81
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emesoft/tokenhub",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Pair Claude Code on this machine with a TokenHub pool",
5
5
  "type": "module",
6
6
  "bin": {