@voxli/cli 0.5.0 → 0.5.1

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.
@@ -0,0 +1,12 @@
1
+ export interface Credentials {
2
+ apiKey: string;
3
+ source: string;
4
+ isEnvToken: boolean;
5
+ }
6
+ export declare function isTokenExpiringSoon(token: string): boolean;
7
+ export declare function resolveCredentials(): Promise<Credentials>;
8
+ /**
9
+ * Wraps an API call with automatic token refresh on 401/403.
10
+ * Proactively refreshes the token if it expires within 15 minutes.
11
+ */
12
+ export declare function withAuth<T>(fn: (apiKey: string) => Promise<T>): Promise<T>;
@@ -0,0 +1,73 @@
1
+ import { join } from "node:path";
2
+ import { resolveApiKey, resolveConfig, attemptTokenRefresh, } from "./config.js";
3
+ import { ApiError } from "./api.js";
4
+ const REFRESH_THRESHOLD_SECONDS = 15 * 60;
5
+ export function isTokenExpiringSoon(token) {
6
+ try {
7
+ const parts = token.split(".");
8
+ if (parts.length !== 3)
9
+ return false;
10
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
11
+ if (!payload.exp)
12
+ return false;
13
+ return payload.exp - Date.now() / 1000 < REFRESH_THRESHOLD_SECONDS;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ export async function resolveCredentials() {
20
+ const isEnvToken = !!resolveApiKey();
21
+ if (isEnvToken) {
22
+ const apiKey = resolveApiKey();
23
+ if (apiKey) {
24
+ return { apiKey, source: "VOXLI_API_TOKEN", isEnvToken: true };
25
+ }
26
+ }
27
+ const resolved = await resolveConfig();
28
+ if (resolved) {
29
+ const { config, configDir } = resolved;
30
+ const apiKey = config.accessToken ?? config.apiKey ?? null;
31
+ if (apiKey) {
32
+ return {
33
+ apiKey,
34
+ source: join(configDir, "config.json"),
35
+ isEnvToken: false,
36
+ };
37
+ }
38
+ }
39
+ console.error("Error: No API key found. Set VOXLI_API_TOKEN or run `voxli auth`.");
40
+ process.exit(1);
41
+ }
42
+ /**
43
+ * Wraps an API call with automatic token refresh on 401/403.
44
+ * Proactively refreshes the token if it expires within 15 minutes.
45
+ */
46
+ export async function withAuth(fn) {
47
+ let credentials = await resolveCredentials();
48
+ if (!credentials.isEnvToken && isTokenExpiringSoon(credentials.apiKey)) {
49
+ const newToken = await attemptTokenRefresh();
50
+ if (newToken) {
51
+ credentials = { ...credentials, apiKey: newToken };
52
+ }
53
+ }
54
+ try {
55
+ return await fn(credentials.apiKey);
56
+ }
57
+ catch (err) {
58
+ if (!(err instanceof ApiError))
59
+ throw err;
60
+ if (err.status !== 401 && err.status !== 403)
61
+ throw err;
62
+ if (credentials.isEnvToken) {
63
+ console.error(`Error: Authentication failed (${err.status}). Your VOXLI_API_TOKEN may be expired or invalid.`);
64
+ process.exit(1);
65
+ }
66
+ const newToken = await attemptTokenRefresh();
67
+ if (newToken) {
68
+ return await fn(newToken);
69
+ }
70
+ console.error("Error: Could not refresh access token. Please re-authenticate with `voxli auth`.");
71
+ process.exit(1);
72
+ }
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxli/cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "CLI agent for running Voxli test scenarios locally",
5
5
  "type": "module",
6
6
  "bin": {