@tokscale/cli 1.0.6 → 1.0.8
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/dist/cli.d.ts +1 -1
- package/dist/cli.js +8 -3
- package/dist/cli.js.map +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +1 -114
- package/dist/native.js.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/Header.js.map +1 -1
- package/package.json +5 -10
- package/src/auth.ts +211 -0
- package/src/cli.ts +1042 -0
- package/src/credentials.ts +123 -0
- package/src/cursor.ts +558 -0
- package/src/graph-types.ts +188 -0
- package/src/graph.ts +485 -0
- package/src/native-runner.ts +105 -0
- package/src/native.ts +807 -0
- package/src/pricing.ts +309 -0
- package/src/sessions/claudecode.ts +119 -0
- package/src/sessions/codex.ts +227 -0
- package/src/sessions/gemini.ts +108 -0
- package/src/sessions/index.ts +126 -0
- package/src/sessions/opencode.ts +94 -0
- package/src/sessions/reports.ts +475 -0
- package/src/sessions/types.ts +59 -0
- package/src/spinner.ts +283 -0
- package/src/submit.ts +175 -0
- package/src/table.ts +233 -0
- package/src/tui/components/Header.tsx +1 -1
- package/src/types.d.ts +28 -0
package/src/auth.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tokscale CLI Authentication Commands
|
|
3
|
+
* Handles login, logout, and whoami commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import {
|
|
9
|
+
saveCredentials,
|
|
10
|
+
loadCredentials,
|
|
11
|
+
clearCredentials,
|
|
12
|
+
getApiBaseUrl,
|
|
13
|
+
} from "./credentials.js";
|
|
14
|
+
|
|
15
|
+
const MAX_POLL_ATTEMPTS = 180; // 15 minutes max
|
|
16
|
+
|
|
17
|
+
interface DeviceCodeResponse {
|
|
18
|
+
deviceCode: string;
|
|
19
|
+
userCode: string;
|
|
20
|
+
verificationUrl: string;
|
|
21
|
+
expiresIn: number;
|
|
22
|
+
interval: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface PollResponse {
|
|
26
|
+
status: "pending" | "complete" | "expired";
|
|
27
|
+
token?: string;
|
|
28
|
+
user?: {
|
|
29
|
+
username: string;
|
|
30
|
+
avatarUrl?: string;
|
|
31
|
+
};
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Login command - initiates device flow authentication
|
|
37
|
+
*/
|
|
38
|
+
export async function login(): Promise<void> {
|
|
39
|
+
const credentials = loadCredentials();
|
|
40
|
+
if (credentials) {
|
|
41
|
+
console.log(pc.yellow(`\n Already logged in as ${pc.bold(credentials.username)}`));
|
|
42
|
+
console.log(pc.gray(" Run 'tokscale logout' to sign out first.\n"));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const baseUrl = getApiBaseUrl();
|
|
47
|
+
|
|
48
|
+
console.log(pc.cyan("\n Tokscale - Login\n"));
|
|
49
|
+
|
|
50
|
+
// Step 1: Request device code
|
|
51
|
+
console.log(pc.gray(" Requesting authorization code..."));
|
|
52
|
+
|
|
53
|
+
let deviceCodeData: DeviceCodeResponse;
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(`${baseUrl}/api/auth/device`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "Content-Type": "application/json" },
|
|
58
|
+
body: JSON.stringify({ deviceName: getDeviceName() }),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
throw new Error(`Server returned ${response.status}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
deviceCodeData = await response.json();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(pc.red(`\n Error: Failed to connect to server.`));
|
|
68
|
+
console.error(pc.gray(` ${(error as Error).message}\n`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Step 2: Display instructions
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(pc.white(" Open this URL in your browser:"));
|
|
75
|
+
console.log(pc.cyan(` ${deviceCodeData.verificationUrl}\n`));
|
|
76
|
+
console.log(pc.white(" Enter this code:"));
|
|
77
|
+
console.log(pc.bold(pc.green(` ${deviceCodeData.userCode}\n`)));
|
|
78
|
+
|
|
79
|
+
// Try to open browser automatically
|
|
80
|
+
await openBrowser(deviceCodeData.verificationUrl);
|
|
81
|
+
|
|
82
|
+
console.log(pc.gray(" Waiting for authorization..."));
|
|
83
|
+
|
|
84
|
+
// Step 3: Poll for completion
|
|
85
|
+
let attempts = 0;
|
|
86
|
+
const pollInterval = (deviceCodeData.interval || 5) * 1000;
|
|
87
|
+
|
|
88
|
+
while (attempts < MAX_POLL_ATTEMPTS) {
|
|
89
|
+
await sleep(pollInterval);
|
|
90
|
+
attempts++;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetch(`${baseUrl}/api/auth/device/poll`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify({ deviceCode: deviceCodeData.deviceCode }),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const data: PollResponse = await response.json();
|
|
100
|
+
|
|
101
|
+
if (data.status === "complete" && data.token && data.user) {
|
|
102
|
+
// Success!
|
|
103
|
+
saveCredentials({
|
|
104
|
+
token: data.token,
|
|
105
|
+
username: data.user.username,
|
|
106
|
+
avatarUrl: data.user.avatarUrl,
|
|
107
|
+
createdAt: new Date().toISOString(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log(pc.green(`\n Success! Logged in as ${pc.bold(data.user.username)}`));
|
|
111
|
+
console.log(pc.gray(" You can now use 'tokscale submit' to share your usage.\n"));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (data.status === "expired") {
|
|
116
|
+
console.error(pc.red("\n Authorization code expired. Please try again.\n"));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Still pending - show a dot to indicate progress
|
|
121
|
+
process.stdout.write(pc.gray("."));
|
|
122
|
+
} catch (error) {
|
|
123
|
+
// Network error - continue polling
|
|
124
|
+
process.stdout.write(pc.red("!"));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.error(pc.red("\n\n Timeout: Authorization took too long. Please try again.\n"));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Logout command - clears stored credentials
|
|
134
|
+
*/
|
|
135
|
+
export async function logout(): Promise<void> {
|
|
136
|
+
const credentials = loadCredentials();
|
|
137
|
+
|
|
138
|
+
if (!credentials) {
|
|
139
|
+
console.log(pc.yellow("\n Not logged in.\n"));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const username = credentials.username;
|
|
144
|
+
const cleared = clearCredentials();
|
|
145
|
+
|
|
146
|
+
if (cleared) {
|
|
147
|
+
console.log(pc.green(`\n Logged out from ${pc.bold(username)}\n`));
|
|
148
|
+
} else {
|
|
149
|
+
console.error(pc.red("\n Failed to clear credentials.\n"));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Whoami command - displays current user info
|
|
156
|
+
*/
|
|
157
|
+
export async function whoami(): Promise<void> {
|
|
158
|
+
const credentials = loadCredentials();
|
|
159
|
+
|
|
160
|
+
if (!credentials) {
|
|
161
|
+
console.log(pc.yellow("\n Not logged in."));
|
|
162
|
+
console.log(pc.gray(" Run 'tokscale login' to authenticate.\n"));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(pc.cyan("\n Tokscale - Account Info\n"));
|
|
167
|
+
console.log(pc.white(` Username: ${pc.bold(credentials.username)}`));
|
|
168
|
+
console.log(pc.gray(` Logged in: ${new Date(credentials.createdAt).toLocaleDateString()}`));
|
|
169
|
+
console.log();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get a device name for the token
|
|
174
|
+
*/
|
|
175
|
+
function getDeviceName(): string {
|
|
176
|
+
return `CLI on ${os.hostname()}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Sleep helper
|
|
181
|
+
*/
|
|
182
|
+
function sleep(ms: number): Promise<void> {
|
|
183
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Try to open browser automatically
|
|
188
|
+
*/
|
|
189
|
+
async function openBrowser(url: string): Promise<void> {
|
|
190
|
+
try {
|
|
191
|
+
const { exec } = await import("node:child_process");
|
|
192
|
+
const platform = process.platform;
|
|
193
|
+
|
|
194
|
+
let command: string;
|
|
195
|
+
if (platform === "darwin") {
|
|
196
|
+
command = `open "${url}"`;
|
|
197
|
+
} else if (platform === "win32") {
|
|
198
|
+
command = `start "" "${url}"`;
|
|
199
|
+
} else {
|
|
200
|
+
command = `xdg-open "${url}"`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
exec(command, (error) => {
|
|
204
|
+
if (error) {
|
|
205
|
+
// Silent fail - user can still open manually
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
} catch {
|
|
209
|
+
// Silent fail
|
|
210
|
+
}
|
|
211
|
+
}
|