hacklab 26.921.2 → 26.921.4
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 +14 -55
- package/dist/index.js +10 -109
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,77 +1,36 @@
|
|
|
1
1
|
# Hacklab CLI
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Requires Node.js 22 or newer. Bun 1.4.2 or newer is also supported.
|
|
3
|
+
Sync coding agent usage to Hacklab. Requires Node.js 22+ or Bun 1.4.2+.
|
|
6
4
|
|
|
7
5
|
```sh
|
|
8
6
|
npm install --global hacklab@beta
|
|
9
|
-
hacklab --help
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
For Bun, use `bun install --global hacklab@beta` and `bunx --bun hacklab@beta --help`.
|
|
13
|
-
|
|
14
|
-
Copy the agent instructions from the signed-in onboarding dialog. They contain no credentials. When you upload, the CLI opens a browser page to sign in with GitHub and authorize the CLI through Better Auth. Check that the code matches your terminal before approving.
|
|
15
|
-
|
|
16
|
-
## Preview a profile
|
|
17
|
-
|
|
18
|
-
Create a local JSON file containing a one-line `bio` (at most 160 characters) and a `socials` object. Supported social keys are `github`, `linkedin`, `x`, `instagram`, `youtube`, `huggingface`, `producthunt`, and `goodreads`. Use full HTTPS profile URLs. Omitted links remain unchanged; `null` removes a link.
|
|
19
|
-
|
|
20
|
-
```sh
|
|
21
|
-
hacklab onboard --file /path/to/profile.json
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
This validates and previews locally without making a network request.
|
|
25
|
-
|
|
26
|
-
## Upload after approval
|
|
27
|
-
|
|
28
|
-
Review the exact bio and links first. Agents must ask the user for approval before using `--confirm-upload`.
|
|
29
|
-
|
|
30
|
-
```sh
|
|
31
|
-
hacklab onboard --file /path/to/profile.json --confirm-upload
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Confirmed onboarding first syncs usage, then saves the profile, sharing one browser authorization. Approval must cover both the profile and the usage report. Preview mode does neither. If syncing fails, onboarding stops before saving the profile.
|
|
35
|
-
|
|
36
|
-
## Sync usage
|
|
37
|
-
|
|
38
|
-
```sh
|
|
39
7
|
hacklab sync
|
|
40
8
|
```
|
|
41
9
|
|
|
42
|
-
The
|
|
43
|
-
|
|
44
|
-
The API accepts the exact `{ session, totals }` envelope. `token_usage` stores each session with the upstream camelCase field names and nested JSON unchanged; only the authenticated `user_id` is added by the server. Totals are validated but not stored because they can be derived from the session rows. Repeated syncs replace counts for the same user, agent, and period rather than adding them. Missing sessions are retained, so syncing another machine does not delete earlier uploads. Reports are limited to 20 MB. The first upload uses browser authorization. Later commands reuse the saved session, checking that it is still valid; expired or revoked sessions open the browser again. Credentials are stored per Hacklab origin in `~/.config/hacklab/credentials/`, with owner-only directory and file permissions on Unix. Run `hacklab logout` to forget local authorization for the current Hacklab server.
|
|
45
|
-
|
|
46
|
-
## Log in
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
hacklab login
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Authorizes the CLI and saves the session without collecting or uploading usage. An existing valid session is reused. To check the authorization dialog again, run `hacklab logout` followed by `hacklab login`.
|
|
10
|
+
The first sync opens your browser to sign in and authorize the CLI. Check that the code matches your terminal before approving. Later commands reuse saved authorization until it expires or is revoked.
|
|
53
11
|
|
|
54
|
-
##
|
|
12
|
+
## Commands
|
|
55
13
|
|
|
56
14
|
```sh
|
|
57
|
-
hacklab
|
|
58
|
-
hacklab
|
|
15
|
+
hacklab sync # Sign in and upload usage
|
|
16
|
+
hacklab login # Sign in without uploading
|
|
17
|
+
hacklab logout # Clear saved authorization
|
|
18
|
+
hacklab --help
|
|
19
|
+
hacklab --version
|
|
59
20
|
```
|
|
60
21
|
|
|
61
|
-
|
|
22
|
+
The CLI runs bundled `ccusage@20.0.24 session --json` and uploads its output to `/api/token-usage`. Reports include session identifiers, tokens, costs, model breakdowns, and metadata that may include project paths. Source code and transcripts are not uploaded. Reports are limited to 20 MB. Repeated syncs update existing sessions; sessions from other machines are retained.
|
|
62
23
|
|
|
63
|
-
|
|
24
|
+
Credentials are stored per server in `~/.config/hacklab/credentials/`, with owner-only permissions on Unix. Logout removes the local credential for the current server; it does not sign out your browser or revoke the server session.
|
|
64
25
|
|
|
65
|
-
|
|
26
|
+
## Development
|
|
66
27
|
|
|
67
|
-
|
|
28
|
+
Building requires Bun 1.4.2+.
|
|
68
29
|
|
|
69
30
|
```sh
|
|
70
31
|
bun install --frozen-lockfile
|
|
71
32
|
bun run --cwd packages/cli build:dev
|
|
72
|
-
|
|
73
|
-
bun link
|
|
74
|
-
hacklab onboard --file /path/to/profile.json
|
|
33
|
+
bun run --cwd packages/cli start --help
|
|
75
34
|
```
|
|
76
35
|
|
|
77
|
-
Source runs and
|
|
36
|
+
Source runs and development builds use `http://localhost:3000`. Production builds embed `BETTER_AUTH_URL` from the build environment or `apps/web/.env` / `.env.local`; it must be an HTTPS origin. No `--url` override is supported. Development and production credentials are stored separately.
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
// package.json
|
|
6
|
-
var version = "26.921.
|
|
6
|
+
var version = "26.921.4";
|
|
7
7
|
|
|
8
8
|
// src/auth.ts
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
@@ -123,9 +123,6 @@ Waiting for you to connect your account…`);
|
|
|
123
123
|
throw new Error("CLI sign-in expired. Run the command again to retry.");
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
// src/onboard.ts
|
|
127
|
-
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
128
|
-
|
|
129
126
|
// src/origin.ts
|
|
130
127
|
function cliOrigin() {
|
|
131
128
|
return "https://beta.hacklab.so";
|
|
@@ -170,7 +167,7 @@ async function uploadUsage(origin, token, body) {
|
|
|
170
167
|
throw new Error("Hacklab did not confirm the sync. Run hacklab sync to retry.");
|
|
171
168
|
console.log(`Synced ${result.sessions} usage sessions to Hacklab.`);
|
|
172
169
|
if (result.sessions === 0)
|
|
173
|
-
console.log("No usage found. Use your coding agent, then run hacklab sync again
|
|
170
|
+
console.log("No usage found. Use your coding agent, then run hacklab sync again.");
|
|
174
171
|
}
|
|
175
172
|
async function sync() {
|
|
176
173
|
const origin = cliOrigin();
|
|
@@ -180,135 +177,39 @@ async function sync() {
|
|
|
180
177
|
await uploadUsage(origin, token, body);
|
|
181
178
|
}
|
|
182
179
|
|
|
183
|
-
// src/onboard.ts
|
|
184
|
-
async function onboard(options) {
|
|
185
|
-
const origin = cliOrigin();
|
|
186
|
-
const file = await stat(options.file);
|
|
187
|
-
if (file.size > 8192)
|
|
188
|
-
throw new Error("Profile must be smaller than 8 KB.");
|
|
189
|
-
const contents = await readFile2(options.file);
|
|
190
|
-
if (contents.byteLength > 8192)
|
|
191
|
-
throw new Error("Profile must be smaller than 8 KB.");
|
|
192
|
-
const profile = JSON.parse(contents.toString("utf8"));
|
|
193
|
-
if (!profile || typeof profile !== "object" || Array.isArray(profile) || Object.keys(profile).some((key) => !["bio", "socials"].includes(key)) || typeof profile.bio !== "string" || !profile.bio.trim() || profile.bio.trim().length > 160 || /[\r\n]/.test(profile.bio) || !profile.socials || typeof profile.socials !== "object" || Array.isArray(profile.socials))
|
|
194
|
-
throw new Error("Expected a one-line bio (1–160 characters) and a socials object, with no other fields.");
|
|
195
|
-
const hosts = {
|
|
196
|
-
github: ["github.com"],
|
|
197
|
-
linkedin: ["linkedin.com"],
|
|
198
|
-
x: ["x.com", "twitter.com"],
|
|
199
|
-
instagram: ["instagram.com"],
|
|
200
|
-
youtube: ["youtube.com"],
|
|
201
|
-
huggingface: ["huggingface.co"],
|
|
202
|
-
producthunt: ["producthunt.com"],
|
|
203
|
-
goodreads: ["goodreads.com"]
|
|
204
|
-
};
|
|
205
|
-
for (const [platform, value] of Object.entries(profile.socials)) {
|
|
206
|
-
if (!Object.hasOwn(hosts, platform))
|
|
207
|
-
throw new Error(`Unsupported social platform: ${platform}`);
|
|
208
|
-
if (value === null)
|
|
209
|
-
continue;
|
|
210
|
-
if (typeof value !== "string" || value.length > 500)
|
|
211
|
-
throw new Error(`Invalid ${platform} profile URL.`);
|
|
212
|
-
const url = new URL(value);
|
|
213
|
-
if (url.protocol !== "https:" || url.username || url.password || url.port || url.pathname === "/" || !hosts[platform].includes(url.hostname.replace(/^www\./, ""))) {
|
|
214
|
-
throw new Error(`Use an HTTPS ${platform} profile URL.`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
const payload = JSON.stringify({ bio: profile.bio.trim(), socials: profile.socials }, null, 2);
|
|
218
|
-
console.log(`Hacklab profile preview for ${origin}:
|
|
219
|
-
${payload}`);
|
|
220
|
-
if (!options.confirmUpload) {
|
|
221
|
-
console.log("Nothing uploaded. Ask the user to approve this exact profile and the first ccusage session sync, then run again with --confirm-upload.");
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
const usage = await collectUsage();
|
|
225
|
-
const token = await authenticate(origin);
|
|
226
|
-
await uploadUsage(origin, token, usage);
|
|
227
|
-
const response = await fetch(`${origin}/api/onboarding`, {
|
|
228
|
-
method: "POST",
|
|
229
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
230
|
-
body: payload,
|
|
231
|
-
redirect: "error",
|
|
232
|
-
signal: AbortSignal.timeout(30000)
|
|
233
|
-
});
|
|
234
|
-
if (!response.ok) {
|
|
235
|
-
throw new Error(`Hacklab rejected the profile (HTTP ${response.status}). Check the profile and sign-in before retrying.`);
|
|
236
|
-
}
|
|
237
|
-
const result = await response.json();
|
|
238
|
-
if (result.ok !== true)
|
|
239
|
-
throw new Error("Hacklab did not confirm the update. Check the website before retrying.");
|
|
240
|
-
console.log("Your Hacklab bio and social links were saved.");
|
|
241
|
-
}
|
|
242
|
-
|
|
243
180
|
// src/index.ts
|
|
244
181
|
var help = `Hacklab CLI
|
|
245
182
|
|
|
246
|
-
Get started:
|
|
247
|
-
npm install -g hacklab@beta
|
|
248
|
-
hacklab sync
|
|
249
|
-
|
|
250
183
|
Usage:
|
|
251
|
-
hacklab sync
|
|
252
|
-
hacklab
|
|
253
|
-
hacklab
|
|
254
|
-
hacklab
|
|
255
|
-
hacklab --
|
|
256
|
-
hacklab --version
|
|
257
|
-
|
|
258
|
-
Optional profile update:
|
|
259
|
-
--file Local JSON containing bio and socials
|
|
260
|
-
--confirm-upload Upload after approval of the profile and first usage sync
|
|
261
|
-
|
|
262
|
-
Without --confirm-upload, onboard validates and previews locally without uploading.
|
|
263
|
-
Confirmed onboarding also runs the first usage sync.
|
|
264
|
-
Sync signs in automatically and uploads unmodified ccusage session --json data.
|
|
265
|
-
Onboarding completes when Hacklab receives your usage.
|
|
266
|
-
The first upload opens your browser to authorize the CLI.
|
|
267
|
-
Later uploads reuse saved authorization until it expires or is revoked.
|
|
268
|
-
Login authorizes the CLI without uploading any data.
|
|
269
|
-
Logout clears saved authorization for this Hacklab server.
|
|
270
|
-
Requires Node.js 22 or newer. Bun 1.4.2 or newer is also supported.`;
|
|
184
|
+
hacklab sync Sign in and upload usage
|
|
185
|
+
hacklab login Sign in without uploading
|
|
186
|
+
hacklab logout Clear saved authorization
|
|
187
|
+
hacklab --help Show help
|
|
188
|
+
hacklab --version Show version`;
|
|
271
189
|
try {
|
|
272
190
|
const { values, positionals } = parseArgs({
|
|
273
191
|
args: process.argv.slice(2),
|
|
274
192
|
options: {
|
|
275
193
|
help: { type: "boolean", short: "h" },
|
|
276
|
-
version: { type: "boolean", short: "v" }
|
|
277
|
-
file: { type: "string" },
|
|
278
|
-
"confirm-upload": { type: "boolean" }
|
|
194
|
+
version: { type: "boolean", short: "v" }
|
|
279
195
|
},
|
|
280
196
|
allowPositionals: true
|
|
281
197
|
});
|
|
282
|
-
if (positionals.length > 1 || positionals[0] && !["
|
|
283
|
-
throw new Error("Unknown command.
|
|
198
|
+
if (positionals.length > 1 || positionals[0] && !["sync", "login", "logout"].includes(positionals[0])) {
|
|
199
|
+
throw new Error("Unknown command. Run hacklab --help for usage.");
|
|
284
200
|
}
|
|
285
201
|
if (values.help) {
|
|
286
202
|
console.log(help);
|
|
287
203
|
} else if (values.version) {
|
|
288
204
|
console.log(version);
|
|
289
|
-
} else if (positionals[0] === "onboard") {
|
|
290
|
-
if (!values.file)
|
|
291
|
-
throw new Error("Pass --file with the local profile JSON to preview or upload.");
|
|
292
|
-
await onboard({
|
|
293
|
-
file: values.file,
|
|
294
|
-
confirmUpload: values["confirm-upload"] ?? false
|
|
295
|
-
});
|
|
296
205
|
} else if (positionals[0] === "login") {
|
|
297
|
-
if (values.file || values["confirm-upload"])
|
|
298
|
-
throw new Error("Profile options require the onboard command.");
|
|
299
206
|
await authenticate(cliOrigin());
|
|
300
207
|
console.log("Signed in to Hacklab. Run hacklab sync to upload usage.");
|
|
301
208
|
} else if (positionals[0] === "logout") {
|
|
302
|
-
if (values.file || values["confirm-upload"])
|
|
303
|
-
throw new Error("Profile options require the onboard command.");
|
|
304
209
|
await removeCredential(cliOrigin());
|
|
305
210
|
console.log("Saved CLI authorization cleared. Run hacklab sync to sign in again.");
|
|
306
211
|
} else if (positionals[0] === "sync") {
|
|
307
|
-
if (values.file || values["confirm-upload"])
|
|
308
|
-
throw new Error("Profile options require the onboard command.");
|
|
309
212
|
await sync();
|
|
310
|
-
} else if (values.file || values["confirm-upload"]) {
|
|
311
|
-
throw new Error("Profile options require the onboard command.");
|
|
312
213
|
} else {
|
|
313
214
|
console.log(help);
|
|
314
215
|
}
|
package/package.json
CHANGED