hacklab 0.12.1 → 0.12.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/README.md +28 -74
- package/dist/belt.d.ts +1 -1
- package/dist/belt.d.ts.map +1 -1
- package/dist/belt.js +2 -2
- package/dist/belt.js.map +1 -1
- package/dist/cli.js +5 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/daemon.d.ts +1 -1
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/login.d.ts +3 -16
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +78 -157
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/referral.js +2 -2
- package/dist/commands/referral.js.map +1 -1
- package/dist/commands/scan.d.ts +7 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +175 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/sync.js +1 -1
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +9 -11
- package/dist/commands/whoami.js.map +1 -1
- package/dist/daily-sync.js +1 -1
- package/dist/daily-sync.js.map +1 -1
- package/dist/posthog.js +1 -1
- package/dist/posthog.js.map +1 -1
- package/dist/referral.js +2 -2
- package/dist/referral.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +6 -11
- package/dist/registry.js.map +1 -1
- package/dist/scanners/index.d.ts +3 -3
- package/dist/scanners/index.js +3 -3
- package/dist/session.d.ts +4 -4
- package/dist/share.d.ts.map +1 -1
- package/dist/share.js +4 -4
- package/dist/share.js.map +1 -1
- package/dist/sync.d.ts +5 -6
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +7 -13
- package/dist/sync.js.map +1 -1
- package/dist/ui.d.ts +2 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +4 -0
- package/dist/ui.js.map +1 -1
- package/dist/utils/openBrowser.d.ts +1 -1
- package/dist/utils/openBrowser.js +1 -1
- package/dist/utils/waitForEnter.d.ts +2 -8
- package/dist/utils/waitForEnter.d.ts.map +1 -1
- package/dist/utils/waitForEnter.js +2 -11
- package/dist/utils/waitForEnter.js.map +1 -1
- package/package.json +1 -1
- package/dist/commands/join.d.ts +0 -15
- package/dist/commands/join.d.ts.map +0 -1
- package/dist/commands/join.js +0 -479
- package/dist/commands/join.js.map +0 -1
package/dist/commands/login.js
CHANGED
|
@@ -1,72 +1,54 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
|
-
import * as clack from '@clack/prompts';
|
|
3
1
|
import { clearSyncPaused } from '../daily-sync.js';
|
|
4
2
|
import { captureEvent, identifyUser } from '../posthog.js';
|
|
5
3
|
import { getAppUrl, saveSession } from '../session.js';
|
|
6
|
-
import { bold, dim,
|
|
4
|
+
import { bold, dim, link } from '../ui.js';
|
|
7
5
|
import { openBrowser } from '../utils/openBrowser.js';
|
|
8
6
|
import { waitForEnter } from '../utils/waitForEnter.js';
|
|
9
7
|
/**
|
|
10
|
-
* Authenticate
|
|
11
|
-
*
|
|
12
|
-
* github.com/login/device (signed into GitHub, not Hacklab), and the backend
|
|
13
|
-
* maps your GitHub identity to the linked Hacklab account. No local server,
|
|
14
|
-
* no port forwarding, no localhost/app URL. Works the same everywhere.
|
|
15
|
-
* - localhost-callback flow (only with `--browser`): opens the browser and
|
|
16
|
-
* catches the OAuth redirect on a local server.
|
|
17
|
-
*
|
|
18
|
-
* `allowSignup` (set by `join`) lets the device flow *create* an account when
|
|
19
|
-
* the GitHub identity has none yet; `login` leaves it off, so an unknown GitHub
|
|
20
|
-
* is told to run `join`. The callback flow always creates-or-logs-in via real
|
|
21
|
-
* OAuth, so it's signup-capable regardless.
|
|
8
|
+
* Authenticate via GitHub's device flow. Unknown GitHub identities get an
|
|
9
|
+
* account. An unclaimed GitHub-derived handle is claimed as-is.
|
|
22
10
|
*/
|
|
23
|
-
export async function login(
|
|
24
|
-
clack.intro('hacklab login');
|
|
11
|
+
export async function login() {
|
|
25
12
|
const appUrl = getAppUrl();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
}
|
|
13
|
+
const creds = await loginViaDevice(appUrl);
|
|
14
|
+
let handle = creds.handle;
|
|
15
|
+
let usernameClaimed = creds.usernameClaimed;
|
|
16
|
+
if (!handle) {
|
|
17
|
+
const me = await fetchMe(appUrl, creds.token);
|
|
18
|
+
handle = me?.handle ?? me?.githubUsername ?? undefined;
|
|
19
|
+
if (me?.claimed != null)
|
|
20
|
+
usernameClaimed = me.claimed;
|
|
35
21
|
}
|
|
36
|
-
if (!
|
|
37
|
-
|
|
22
|
+
if (handle && !usernameClaimed) {
|
|
23
|
+
const claimed = await claimHandle(appUrl, creds.token, handle);
|
|
24
|
+
if (claimed) {
|
|
25
|
+
handle = claimed;
|
|
26
|
+
usernameClaimed = true;
|
|
27
|
+
}
|
|
38
28
|
}
|
|
39
29
|
await saveSession({
|
|
40
30
|
token: creds.token,
|
|
41
31
|
email: creds.email,
|
|
42
|
-
handle
|
|
43
|
-
usernameClaimed
|
|
32
|
+
handle,
|
|
33
|
+
usernameClaimed,
|
|
44
34
|
appUrl,
|
|
45
35
|
savedAt: new Date().toISOString(),
|
|
46
36
|
expiresAt: creds.expiresAt,
|
|
47
37
|
});
|
|
48
|
-
// The session is fresh again — drop any "background sync paused" marker so a
|
|
49
|
-
// stale notice doesn't fire after the user just fixed it.
|
|
50
38
|
await clearSyncPaused();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
$set: { email: creds.email, handle: creds.handle, app_url: appUrl },
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(handle ? `signed in as @${handle}` : `signed in as ${creds.email}`);
|
|
41
|
+
if (handle) {
|
|
42
|
+
await identifyUser(handle, {
|
|
43
|
+
$set: { email: creds.email, handle, app_url: appUrl },
|
|
57
44
|
});
|
|
58
|
-
await captureEvent(
|
|
59
|
-
login_method:
|
|
60
|
-
username_claimed:
|
|
45
|
+
await captureEvent(handle, 'cli_login_completed', {
|
|
46
|
+
login_method: 'device_flow',
|
|
47
|
+
username_claimed: usernameClaimed ?? false,
|
|
61
48
|
});
|
|
62
49
|
}
|
|
63
50
|
}
|
|
64
|
-
|
|
65
|
-
* Device-code flow. Returns the credentials on approval, or null if the backend
|
|
66
|
-
* doesn't support it (so the caller can fall back). Throws on an explicit
|
|
67
|
-
* denial, expiry, or timeout.
|
|
68
|
-
*/
|
|
69
|
-
async function loginViaDevice(appUrl, allowSignup) {
|
|
51
|
+
async function loginViaDevice(appUrl) {
|
|
70
52
|
let startRes;
|
|
71
53
|
try {
|
|
72
54
|
startRes = await fetch(`${appUrl}/api/cli/device/start`, {
|
|
@@ -75,164 +57,103 @@ async function loginViaDevice(appUrl, allowSignup) {
|
|
|
75
57
|
});
|
|
76
58
|
}
|
|
77
59
|
catch {
|
|
78
|
-
// Connection refused / DNS / timeout — the server isn't reachable. Name the
|
|
79
|
-
// URL so the cause (e.g. local dev app not running) is obvious.
|
|
80
60
|
throw new Error(`could not reach ${appUrl} — is the server running?`);
|
|
81
61
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
62
|
+
if (startRes.status === 404) {
|
|
63
|
+
throw new Error(`device login is not available on ${appUrl}`);
|
|
64
|
+
}
|
|
85
65
|
if (!startRes.ok) {
|
|
86
66
|
throw new Error(`could not start device login (${startRes.status})`);
|
|
87
67
|
}
|
|
88
68
|
const start = (await startRes.json());
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
format: (line) => white(line),
|
|
99
|
-
});
|
|
100
|
-
await waitForEnter(` ${white('press Enter to open')} ${linkBlue(start.verificationUri)} ${white('in your browser')} `);
|
|
69
|
+
// Code first, then Enter, then the browser — they have to have read the
|
|
70
|
+
// code before a new window steals focus. The URL is printed in full so it
|
|
71
|
+
// is copyable if Enter doesn't open a browser; OSC-8 makes it clickable.
|
|
72
|
+
console.log(dim('copy code'));
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(bold(start.userCode));
|
|
75
|
+
console.log('');
|
|
76
|
+
console.log(link(start.verificationUri));
|
|
77
|
+
await waitForEnter('(press enter) ');
|
|
101
78
|
void openBrowser(start.verificationUriComplete ?? start.verificationUri);
|
|
102
|
-
info(dim(`if your browser didn't open, go to ${start.verificationUri} and enter the code above.`));
|
|
103
79
|
const intervalMs = Math.max(1000, (Number(start.interval) || 5) * 1000);
|
|
104
80
|
const deadline = Date.now() + (Number(start.expiresIn) || 900) * 1000;
|
|
105
|
-
const spin = clack.spinner();
|
|
106
|
-
spin.start('waiting for you to authorize on GitHub');
|
|
107
81
|
while (Date.now() < deadline) {
|
|
108
82
|
let data = null;
|
|
109
83
|
try {
|
|
110
84
|
const res = await fetch(`${appUrl}/api/cli/device/poll`, {
|
|
111
85
|
method: 'POST',
|
|
112
86
|
headers: { 'Content-Type': 'application/json' },
|
|
113
|
-
body: JSON.stringify({
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
deviceCode: start.deviceCode,
|
|
89
|
+
allowSignup: true,
|
|
90
|
+
}),
|
|
114
91
|
signal: AbortSignal.timeout(10_000),
|
|
115
92
|
});
|
|
116
|
-
// Back off naturally on a rate-limit; otherwise read the status.
|
|
117
93
|
if (res.status !== 429)
|
|
118
94
|
data = await res.json().catch(() => null);
|
|
119
95
|
}
|
|
120
96
|
catch {
|
|
121
|
-
//
|
|
97
|
+
// keep polling
|
|
122
98
|
}
|
|
123
99
|
const status = data?.status;
|
|
124
100
|
if (status === 'approved' && data?.token && data.email) {
|
|
125
|
-
spin.stop('approved');
|
|
126
101
|
return {
|
|
127
102
|
token: data.token,
|
|
128
103
|
email: data.email,
|
|
129
|
-
handle: data.handle,
|
|
104
|
+
handle: data.handle ?? data.login,
|
|
130
105
|
usernameClaimed: data.usernameClaimed,
|
|
131
106
|
expiresAt: data.expiresAt,
|
|
132
107
|
};
|
|
133
108
|
}
|
|
134
109
|
if (status === 'denied') {
|
|
135
|
-
spin.stop('denied');
|
|
136
110
|
throw new Error('authorization was denied on GitHub.');
|
|
137
111
|
}
|
|
138
112
|
if (status === 'expired') {
|
|
139
|
-
spin.stop('code expired');
|
|
140
113
|
throw new Error('the code expired — run `hacklab login` again.');
|
|
141
114
|
}
|
|
142
115
|
if (status === 'no_account') {
|
|
143
|
-
spin.stop('no linked account');
|
|
144
116
|
const who = typeof data?.login === 'string' ? ` (@${data.login})` : '';
|
|
145
|
-
throw new Error(`no hacklab account is linked to that GitHub${who}. run \`hacklab
|
|
117
|
+
throw new Error(`no hacklab account is linked to that GitHub${who}. run \`hacklab login\` again.`);
|
|
146
118
|
}
|
|
147
119
|
await sleep(intervalMs);
|
|
148
120
|
}
|
|
149
|
-
spin.stop('timed out');
|
|
150
121
|
throw new Error('login timed out — run `hacklab login` again.');
|
|
151
122
|
}
|
|
152
|
-
/** Localhost-callback flow: open the browser, catch the OAuth redirect locally. */
|
|
153
|
-
async function loginViaCallback(appUrl) {
|
|
154
|
-
return await new Promise((resolve, reject) => {
|
|
155
|
-
let timeout;
|
|
156
|
-
const server = createServer((req, res) => {
|
|
157
|
-
const url = new URL(req.url ?? '/', `http://localhost`);
|
|
158
|
-
const token = url.searchParams.get('token');
|
|
159
|
-
const email = url.searchParams.get('email');
|
|
160
|
-
const handle = url.searchParams.get('handle');
|
|
161
|
-
const usernameClaimed = url.searchParams.get('usernameClaimed') === '1';
|
|
162
|
-
const expiresAt = normalizeDateParam(url.searchParams.get('expiresAt'));
|
|
163
|
-
if (!token || !email) {
|
|
164
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
165
|
-
res.end('<h1>login failed</h1><p>missing token. try again.</p>');
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
169
|
-
res.end('<h1>logged in!</h1><p>you can close this tab and go back to your terminal.</p>');
|
|
170
|
-
stopServer();
|
|
171
|
-
resolve({
|
|
172
|
-
token,
|
|
173
|
-
email,
|
|
174
|
-
handle: handle ?? undefined,
|
|
175
|
-
usernameClaimed,
|
|
176
|
-
expiresAt,
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
// Bind 0 = a random free port. On a headless host you can't open the
|
|
180
|
-
// browser locally, so the user opens the URL elsewhere and the OAuth
|
|
181
|
-
// redirect must reach this callback server — which means forwarding the
|
|
182
|
-
// port (e.g. `ssh -L`). Pin it with HACKLAB_CALLBACK_PORT so the forward is
|
|
183
|
-
// deterministic across runs. (Headless usually uses the device flow above,
|
|
184
|
-
// so this hint is a fallback for a misdetected display.)
|
|
185
|
-
const callbackPort = Number(process.env.HACKLAB_CALLBACK_PORT) || 0;
|
|
186
|
-
server.listen(callbackPort, '127.0.0.1', () => {
|
|
187
|
-
const addr = server.address();
|
|
188
|
-
if (!addr || typeof addr === 'string') {
|
|
189
|
-
stopServer();
|
|
190
|
-
reject(new Error('failed to start callback server'));
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
const callbackUrl = `http://127.0.0.1:${addr.port}`;
|
|
194
|
-
const loginUrl = `${appUrl}/cli/auth?callback=${encodeURIComponent(callbackUrl)}`;
|
|
195
|
-
info('to sign in, open this URL in a browser:');
|
|
196
|
-
info(` ${loginUrl}`);
|
|
197
|
-
if (isLikelyHeadless()) {
|
|
198
|
-
info(dim('headless? drop --browser to use the device code, or forward the port:'));
|
|
199
|
-
info(dim(` ssh -L ${addr.port}:localhost:${addr.port} <this-host>`));
|
|
200
|
-
info(dim(' (pin it with HACKLAB_CALLBACK_PORT to reuse one forward)'));
|
|
201
|
-
}
|
|
202
|
-
// Best-effort auto-open — crash-safe, and a harmless no-op on headless.
|
|
203
|
-
void openBrowser(loginUrl);
|
|
204
|
-
});
|
|
205
|
-
// Timeout after 2 minutes
|
|
206
|
-
timeout = setTimeout(() => {
|
|
207
|
-
stopServer();
|
|
208
|
-
reject(new Error('login timed out. try again.'));
|
|
209
|
-
}, 120_000);
|
|
210
|
-
function stopServer() {
|
|
211
|
-
if (timeout)
|
|
212
|
-
clearTimeout(timeout);
|
|
213
|
-
server.close();
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
123
|
function sleep(ms) {
|
|
218
124
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
219
125
|
}
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
126
|
+
async function fetchMe(appUrl, token) {
|
|
127
|
+
try {
|
|
128
|
+
const res = await fetch(`${appUrl}/api/hackers/me?src=cli`, {
|
|
129
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
130
|
+
signal: AbortSignal.timeout(10_000),
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok)
|
|
133
|
+
return null;
|
|
134
|
+
return (await res.json());
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
225
139
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
140
|
+
async function claimHandle(appUrl, token, username) {
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(`${appUrl}/api/cli/claim`, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
Authorization: `Bearer ${token}`,
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify({ username }),
|
|
149
|
+
});
|
|
150
|
+
if (!res.ok)
|
|
151
|
+
return null;
|
|
152
|
+
const data = (await res.json());
|
|
153
|
+
return data.handle ?? username;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return null;
|
|
235
157
|
}
|
|
236
|
-
return !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY;
|
|
237
158
|
}
|
|
238
159
|
//# sourceMappingURL=login.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,11 +38,11 @@ export async function referral(args) {
|
|
|
38
38
|
const session = await requireSession(json);
|
|
39
39
|
const handle = session.handle;
|
|
40
40
|
if (!handle) {
|
|
41
|
-
const message = 'claim a username first with `hacklab
|
|
41
|
+
const message = 'claim a username first with `hacklab login`';
|
|
42
42
|
if (json)
|
|
43
43
|
emitJsonError('no_handle', message);
|
|
44
44
|
error('no referral link yet — you have not claimed a username');
|
|
45
|
-
hint('run `hacklab
|
|
45
|
+
hint('run `hacklab login` to finish setting up your profile');
|
|
46
46
|
process.exit(1);
|
|
47
47
|
}
|
|
48
48
|
const base = resolveAppUrl(session);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"referral.js","sourceRoot":"","sources":["../../src/commands/referral.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAgB,MAAM,eAAe,CAAA;AAC3D,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAUhE;;;;GAIG;AACH,KAAK,UAAU,kBAAkB,CAC/B,OAAgB,EAChB,SAAS,GAAG,IAAI;IAEhB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,mBAAmB,EAAE;YACpE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,KAAK,EAAE,EAAE;YACrD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;SACvC,CAAC,CAAA;QACF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAA;QACxB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAE7B,CAAA;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAA;QAClC,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAA;QAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC;YACjD,CAAC,CAAE,IAAI,CAAC,QAAQ,CAAC,MAA2B;YAC5C,CAAC,CAAC,EAAE,CAAA;QACN,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAc;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACpC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAA;IAE1C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAC7B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"referral.js","sourceRoot":"","sources":["../../src/commands/referral.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan local AI usage, render the share card, and optionally post it to X.
|
|
3
|
+
* Local-only unless they choose to share. Login is optional: a handle on the
|
|
4
|
+
* session goes on the card; without one the card still renders.
|
|
5
|
+
*/
|
|
6
|
+
export declare function scan(): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAiCA;;;;GAIG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAkE1C"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import * as clack from '@clack/prompts';
|
|
2
|
+
import { beltForTokens } from '../belt.js';
|
|
3
|
+
import { loadConfig, resolveCursorAuth, saveConfig } from '../config.js';
|
|
4
|
+
import { captureEvent } from '../posthog.js';
|
|
5
|
+
import { collectToolScans, computeStreaks, detectCursorUsage, formatTokens, mergeToolScans, rescanCursorWithApi, } from '../scanners/index.js';
|
|
6
|
+
import { loadSession } from '../session.js';
|
|
7
|
+
import { promptShareOnX, renderShareCard, } from '../share.js';
|
|
8
|
+
import { bold, dim } from '../ui.js';
|
|
9
|
+
const TOOL_LABELS = {
|
|
10
|
+
claude_code: 'Claude Code',
|
|
11
|
+
codex: 'Codex',
|
|
12
|
+
cursor: 'Cursor',
|
|
13
|
+
openclaw: 'OpenClaw',
|
|
14
|
+
hermes: 'Hermes',
|
|
15
|
+
opencode: 'OpenCode',
|
|
16
|
+
grok: 'Grok Build',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Scan local AI usage, render the share card, and optionally post it to X.
|
|
20
|
+
* Local-only unless they choose to share. Login is optional: a handle on the
|
|
21
|
+
* session goes on the card; without one the card still renders.
|
|
22
|
+
*/
|
|
23
|
+
export async function scan() {
|
|
24
|
+
const spin = clack.spinner();
|
|
25
|
+
spin.start('scanning local AI tool usage');
|
|
26
|
+
let results;
|
|
27
|
+
try {
|
|
28
|
+
results = await collectToolScans();
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
spin.stop('scan failed');
|
|
32
|
+
clack.cancel(`couldn't read local AI usage: ${err instanceof Error ? err.message : String(err)}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
spin.stop('scan complete');
|
|
36
|
+
let scanResult = mergeToolScans(results);
|
|
37
|
+
printToolTotals(scanResult);
|
|
38
|
+
const rescanned = await offerCursorApiKey(results, spin);
|
|
39
|
+
if (rescanned) {
|
|
40
|
+
results = rescanned;
|
|
41
|
+
scanResult = mergeToolScans(results);
|
|
42
|
+
printToolTotals(scanResult);
|
|
43
|
+
}
|
|
44
|
+
const session = await loadSession();
|
|
45
|
+
const handle = session?.handle ?? 'hacker';
|
|
46
|
+
const belt = beltForTokens(scanResult.grandTotal);
|
|
47
|
+
const streaks = computeStreaks(scanResult.dailyTotals.map((entry) => entry.date));
|
|
48
|
+
const card = {
|
|
49
|
+
handle,
|
|
50
|
+
level: belt.level,
|
|
51
|
+
title: belt.title,
|
|
52
|
+
beltColor: belt.beltColor,
|
|
53
|
+
tokensTotal: scanResult.grandTotal,
|
|
54
|
+
rank: 0,
|
|
55
|
+
streak: streaks.current,
|
|
56
|
+
longestStreak: streaks.longest,
|
|
57
|
+
progressPercent: belt.progressPercent,
|
|
58
|
+
estimatedCost: estimateCost(scanResult.toolTotals),
|
|
59
|
+
toolBreakdown: {
|
|
60
|
+
claudeCode: scanResult.toolTotals.claude_code ?? 0,
|
|
61
|
+
codex: scanResult.toolTotals.codex ?? 0,
|
|
62
|
+
cursor: scanResult.toolTotals.cursor ?? 0,
|
|
63
|
+
},
|
|
64
|
+
models: Object.entries(scanResult.modelTotals)
|
|
65
|
+
.map(([name, tokens]) => ({ name, tokens }))
|
|
66
|
+
.sort((a, b) => b.tokens - a.tokens),
|
|
67
|
+
dailyActivity: aggregateDailyActivity(scanResult.dailyTotals),
|
|
68
|
+
};
|
|
69
|
+
const cardPath = await renderShareCard(card);
|
|
70
|
+
if (session?.handle) {
|
|
71
|
+
await promptShareOnX(card, cardPath);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
clack.log.message(dim('hacklab login to put your name on the card'));
|
|
75
|
+
}
|
|
76
|
+
if (session?.handle) {
|
|
77
|
+
await captureEvent(session.handle, 'cli_scan_completed', {
|
|
78
|
+
tokens_total: scanResult.grandTotal,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
clack.outro(dim('done.'));
|
|
82
|
+
}
|
|
83
|
+
function printToolTotals(scan) {
|
|
84
|
+
for (const [tool, total] of Object.entries(scan.toolTotals)) {
|
|
85
|
+
if (total > 0) {
|
|
86
|
+
clack.log.message(`${(TOOL_LABELS[tool] ?? tool).padEnd(12)} ${formatTokens(total)} tokens`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
clack.log.message(bold(`total: ${formatTokens(scan.grandTotal)} tokens`));
|
|
90
|
+
}
|
|
91
|
+
async function offerCursorApiKey(results, spin) {
|
|
92
|
+
const { apiKey } = await resolveCursorAuth();
|
|
93
|
+
if (apiKey)
|
|
94
|
+
return null;
|
|
95
|
+
if (!(await detectCursorUsage()))
|
|
96
|
+
return null;
|
|
97
|
+
clack.log.step('cursor detected');
|
|
98
|
+
clack.log.message(dim('a cursor api key gets exact counts. enter to skip.'));
|
|
99
|
+
const entered = await clack.password({
|
|
100
|
+
message: 'cursor api key (enter to skip)',
|
|
101
|
+
});
|
|
102
|
+
if (clack.isCancel(entered))
|
|
103
|
+
return null;
|
|
104
|
+
const key = String(entered ?? '').trim();
|
|
105
|
+
if (!key) {
|
|
106
|
+
clack.log.message(dim('skipped — cursor tokens will stay estimated.'));
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const emailValue = await clack.text({
|
|
110
|
+
message: 'cursor account email (optional — scopes a team key to just you)',
|
|
111
|
+
placeholder: 'enter to skip',
|
|
112
|
+
});
|
|
113
|
+
const email = clack.isCancel(emailValue) || !String(emailValue ?? '').trim()
|
|
114
|
+
? undefined
|
|
115
|
+
: String(emailValue).trim();
|
|
116
|
+
await saveConfig({
|
|
117
|
+
...(await loadConfig()),
|
|
118
|
+
cursorApiKey: key,
|
|
119
|
+
...(email ? { cursorEmail: email } : {}),
|
|
120
|
+
});
|
|
121
|
+
spin.start('fetching exact cursor usage');
|
|
122
|
+
let rescanned;
|
|
123
|
+
try {
|
|
124
|
+
rescanned = await rescanCursorWithApi(results);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
spin.stop('cursor api scan failed');
|
|
128
|
+
clack.log.message(dim(`keeping the local estimate: ${err instanceof Error ? err.message : String(err)}`));
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const status = rescanned.find((r) => r.tool === 'cursor')?.cursorScanStatus;
|
|
132
|
+
switch (status?.source) {
|
|
133
|
+
case 'api':
|
|
134
|
+
spin.stop(`cursor: exact counts from ${status.events} usage events`);
|
|
135
|
+
return rescanned;
|
|
136
|
+
case 'api-partial':
|
|
137
|
+
spin.stop(`cursor: ${status.events} usage events (partial)`);
|
|
138
|
+
clack.log.message(dim(`stopped early: ${status.reason}`));
|
|
139
|
+
return rescanned;
|
|
140
|
+
case 'api-failed':
|
|
141
|
+
spin.stop('cursor api scan failed');
|
|
142
|
+
clack.log.message(dim(`${status.reason} — keeping the local estimate.`));
|
|
143
|
+
return null;
|
|
144
|
+
default:
|
|
145
|
+
spin.stop('cursor api returned no usage events');
|
|
146
|
+
clack.log.message(dim('keeping the local estimate.'));
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function estimateCost(toolTotals) {
|
|
151
|
+
const rate = {
|
|
152
|
+
claude_code: 0.6,
|
|
153
|
+
codex: 0.25,
|
|
154
|
+
cursor: 0.4,
|
|
155
|
+
openclaw: 0.5,
|
|
156
|
+
hermes: 0.5,
|
|
157
|
+
opencode: 0.5,
|
|
158
|
+
grok: 0.5,
|
|
159
|
+
};
|
|
160
|
+
let cost = 0;
|
|
161
|
+
for (const [tool, tokens] of Object.entries(toolTotals)) {
|
|
162
|
+
cost += (tokens / 1_000_000) * (rate[tool] ?? 0.4);
|
|
163
|
+
}
|
|
164
|
+
return cost;
|
|
165
|
+
}
|
|
166
|
+
function aggregateDailyActivity(dailyTotals) {
|
|
167
|
+
const byDate = new Map();
|
|
168
|
+
for (const entry of dailyTotals) {
|
|
169
|
+
byDate.set(entry.date, (byDate.get(entry.date) ?? 0) + entry.tokens);
|
|
170
|
+
}
|
|
171
|
+
return Array.from(byDate.entries())
|
|
172
|
+
.map(([date, tokens]) => ({ date, tokens }))
|
|
173
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/sync.js
CHANGED
|
@@ -246,7 +246,7 @@ async function interactiveSync() {
|
|
|
246
246
|
let result;
|
|
247
247
|
try {
|
|
248
248
|
// Manual `hacklab sync` — tag the upload as interactive so the backend counts
|
|
249
|
-
// it as user activity (the daily background job
|
|
249
|
+
// it as user activity (the daily background job doesn't).
|
|
250
250
|
result = await runSync(session, { interactive: true, promptConsent });
|
|
251
251
|
}
|
|
252
252
|
catch (e) {
|