hacklab 26.921.1 → 26.921.2
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 +39 -8
- package/dist/index.js +225 -40
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# Hacklab CLI
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A CLI for updating your Hacklab bio and social links with your coding agent.
|
|
4
4
|
|
|
5
|
-
Requires Bun 1.4.2 or newer.
|
|
5
|
+
Requires Node.js 22 or newer. Bun 1.4.2 or newer is also supported.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
|
|
8
|
+
npm install --global hacklab@beta
|
|
9
9
|
hacklab --help
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
|
|
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.
|
|
13
15
|
|
|
14
16
|
## Preview a profile
|
|
15
17
|
|
|
@@ -29,18 +31,47 @@ Review the exact bio and links first. Agents must ask the user for approval befo
|
|
|
29
31
|
hacklab onboard --file /path/to/profile.json --confirm-upload
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
|
|
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
|
+
hacklab sync
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The CLI includes pinned `ccusage@20.0.24` and runs `ccusage session --json`. No separate ccusage installation is needed. Its stdout is posted unchanged to `/api/token-usage`. This report includes agent/session identifiers, tokens, costs, model breakdowns, and metadata, which can include project paths. Source code and transcript contents are not uploaded.
|
|
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`.
|
|
53
|
+
|
|
54
|
+
## Log out
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
hacklab logout
|
|
58
|
+
hacklab sync
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Logout clears the saved CLI credential for the current Hacklab server, even when offline. The next sync opens the authorization dialog on the Arena again. Your browser stays signed in. Other Hacklab servers' credentials are preserved; this command does not revoke the server session.
|
|
33
62
|
|
|
34
63
|
## Local development
|
|
35
64
|
|
|
65
|
+
Building from source requires Bun 1.4.2 or newer; the published package runs with Node.js without Bun.
|
|
66
|
+
|
|
36
67
|
From the Hacklab repository:
|
|
37
68
|
|
|
38
69
|
```sh
|
|
39
70
|
bun install --frozen-lockfile
|
|
40
|
-
|
|
71
|
+
bun run --cwd packages/cli build:dev
|
|
41
72
|
cd packages/cli
|
|
42
73
|
bun link
|
|
43
|
-
hacklab onboard --file /path/to/profile.json
|
|
74
|
+
hacklab onboard --file /path/to/profile.json
|
|
44
75
|
```
|
|
45
76
|
|
|
46
|
-
|
|
77
|
+
Source runs and `build:dev` always 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. Set the production URL before running `bun run build` or publishing. No `--url` override is supported. Credentials are saved locally for reuse and are never copied into the prompt. Development and production credentials are stored separately.
|
package/dist/index.js
CHANGED
|
@@ -1,27 +1,197 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
// @bun
|
|
1
|
+
#!/usr/bin/env node
|
|
3
2
|
|
|
4
3
|
// src/index.ts
|
|
5
|
-
import { parseArgs } from "util";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
6
5
|
// package.json
|
|
7
|
-
var version = "26.921.
|
|
6
|
+
var version = "26.921.2";
|
|
7
|
+
|
|
8
|
+
// src/auth.ts
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
11
|
+
|
|
12
|
+
// src/credentials.ts
|
|
13
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
14
|
+
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
var credentialsDirectory = join(homedir(), ".config", "hacklab", "credentials");
|
|
18
|
+
function credentialPath(origin, directory) {
|
|
19
|
+
return join(directory, `${createHash("sha256").update(origin).digest("hex")}.json`);
|
|
20
|
+
}
|
|
21
|
+
async function readCredential(origin, directory = credentialsDirectory) {
|
|
22
|
+
try {
|
|
23
|
+
const value = JSON.parse(await readFile(credentialPath(origin, directory), "utf8"));
|
|
24
|
+
return value?.origin === origin && typeof value.token === "string" && value.token.length > 0 ? value.token : null;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error instanceof SyntaxError || error.code === "ENOENT")
|
|
27
|
+
return null;
|
|
28
|
+
throw new Error("Could not read saved Hacklab authorization.");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function saveCredential(origin, token, directory = credentialsDirectory) {
|
|
32
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
33
|
+
await chmod(directory, 448);
|
|
34
|
+
const path = credentialPath(origin, directory);
|
|
35
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
36
|
+
try {
|
|
37
|
+
await writeFile(temporary, JSON.stringify({ origin, token }), { mode: 384, flag: "wx" });
|
|
38
|
+
await rename(temporary, path);
|
|
39
|
+
} finally {
|
|
40
|
+
await rm(temporary, { force: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function removeCredential(origin, directory = credentialsDirectory) {
|
|
44
|
+
await rm(credentialPath(origin, directory), { force: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/auth.ts
|
|
48
|
+
var clientId = "hacklab-cli";
|
|
49
|
+
function openBrowser(url) {
|
|
50
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
|
|
51
|
+
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
52
|
+
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
53
|
+
child.on("error", () => console.log("Open the authorization URL above in your browser."));
|
|
54
|
+
child.unref();
|
|
55
|
+
}
|
|
56
|
+
async function authenticate(origin, launchBrowser = openBrowser, directory = credentialsDirectory) {
|
|
57
|
+
const saved = await readCredential(origin, directory);
|
|
58
|
+
if (saved) {
|
|
59
|
+
const response = await fetch(`${origin}/api/auth/get-session`, {
|
|
60
|
+
headers: { Authorization: `Bearer ${saved}` },
|
|
61
|
+
redirect: "error",
|
|
62
|
+
signal: AbortSignal.timeout(30000)
|
|
63
|
+
});
|
|
64
|
+
if (response.ok) {
|
|
65
|
+
const session = await response.json();
|
|
66
|
+
if (session?.session?.id && session.user?.id)
|
|
67
|
+
return saved;
|
|
68
|
+
if (session !== null)
|
|
69
|
+
throw new Error("Invalid saved authorization response. Try again later.");
|
|
70
|
+
} else if (response.status !== 401) {
|
|
71
|
+
throw new Error(`Could not verify saved Hacklab authorization (HTTP ${response.status}). Try again later.`);
|
|
72
|
+
}
|
|
73
|
+
await removeCredential(origin, directory);
|
|
74
|
+
console.log("Your saved Hacklab authorization expired or was revoked. Sign in again.");
|
|
75
|
+
}
|
|
76
|
+
const request = (path, body) => fetch(`${origin}/api/auth${path}`, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify(body),
|
|
80
|
+
redirect: "error",
|
|
81
|
+
signal: AbortSignal.timeout(30000)
|
|
82
|
+
});
|
|
83
|
+
const response = await request("/device/code", { client_id: clientId });
|
|
84
|
+
if (!response.ok)
|
|
85
|
+
throw new Error(`Could not start CLI sign-in (HTTP ${response.status}).`);
|
|
86
|
+
const code = await response.json();
|
|
87
|
+
const verification = new URL(code.verification_uri_complete, origin);
|
|
88
|
+
if (verification.origin !== origin || !code.device_code || !code.user_code || !Number.isFinite(code.expires_in) || code.expires_in <= 0 || !Number.isFinite(code.interval) || code.interval <= 0) {
|
|
89
|
+
throw new Error("Invalid CLI authorization response.");
|
|
90
|
+
}
|
|
91
|
+
console.log(`Opening your browser to sign in to Hacklab.
|
|
92
|
+
If it does not open, visit: ${verification.href}
|
|
93
|
+
Waiting for you to connect your account…`);
|
|
94
|
+
launchBrowser(verification.href);
|
|
95
|
+
const deadline = Date.now() + code.expires_in * 1000;
|
|
96
|
+
let interval = code.interval * 1000;
|
|
97
|
+
while (Date.now() < deadline) {
|
|
98
|
+
await sleep(Math.min(interval, deadline - Date.now()));
|
|
99
|
+
if (Date.now() >= deadline)
|
|
100
|
+
break;
|
|
101
|
+
const tokenResponse = await request("/device/token", {
|
|
102
|
+
client_id: clientId,
|
|
103
|
+
device_code: code.device_code,
|
|
104
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
105
|
+
});
|
|
106
|
+
const result = await tokenResponse.json();
|
|
107
|
+
if (tokenResponse.ok && result.access_token) {
|
|
108
|
+
await saveCredential(origin, result.access_token, directory);
|
|
109
|
+
return result.access_token;
|
|
110
|
+
}
|
|
111
|
+
if (result.error === "authorization_pending")
|
|
112
|
+
continue;
|
|
113
|
+
if (result.error === "slow_down") {
|
|
114
|
+
interval += 5000;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (result.error === "access_denied")
|
|
118
|
+
throw new Error("CLI authorization was denied. Nothing uploaded.");
|
|
119
|
+
if (result.error === "expired_token")
|
|
120
|
+
break;
|
|
121
|
+
throw new Error(`CLI sign-in failed (HTTP ${tokenResponse.status}). Run the command again to retry.`);
|
|
122
|
+
}
|
|
123
|
+
throw new Error("CLI sign-in expired. Run the command again to retry.");
|
|
124
|
+
}
|
|
8
125
|
|
|
9
126
|
// src/onboard.ts
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return url.origin;
|
|
127
|
+
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
128
|
+
|
|
129
|
+
// src/origin.ts
|
|
130
|
+
function cliOrigin() {
|
|
131
|
+
return "https://beta.hacklab.so";
|
|
16
132
|
}
|
|
133
|
+
|
|
134
|
+
// src/sync.ts
|
|
135
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
136
|
+
import { once } from "node:events";
|
|
137
|
+
import { createRequire } from "node:module";
|
|
138
|
+
import { text } from "node:stream/consumers";
|
|
139
|
+
async function collectUsage() {
|
|
140
|
+
const require2 = createRequire(import.meta.url);
|
|
141
|
+
const child = spawn2(process.execPath, [require2.resolve("ccusage/src/cli.js"), "session", "--json"], { stdio: ["ignore", "pipe", "inherit"] });
|
|
142
|
+
const [body, [exitCode]] = await Promise.all([text(child.stdout), once(child, "close")]);
|
|
143
|
+
if (exitCode !== 0)
|
|
144
|
+
throw new Error(`ccusage failed (exit ${exitCode}). Nothing synced.`);
|
|
145
|
+
let report;
|
|
146
|
+
try {
|
|
147
|
+
report = JSON.parse(body);
|
|
148
|
+
} catch {
|
|
149
|
+
throw new Error("ccusage returned invalid JSON. Nothing synced.");
|
|
150
|
+
}
|
|
151
|
+
if (!Array.isArray(report?.session) || !report.totals) {
|
|
152
|
+
throw new Error("Unexpected ccusage report. Nothing synced.");
|
|
153
|
+
}
|
|
154
|
+
if (Buffer.byteLength(body) > 20 * 1024 * 1024)
|
|
155
|
+
throw new Error("Usage report exceeds 20 MB. Nothing synced.");
|
|
156
|
+
return body;
|
|
157
|
+
}
|
|
158
|
+
async function uploadUsage(origin, token, body) {
|
|
159
|
+
const response = await fetch(`${origin}/api/token-usage`, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
162
|
+
body,
|
|
163
|
+
redirect: "error",
|
|
164
|
+
signal: AbortSignal.timeout(60000)
|
|
165
|
+
});
|
|
166
|
+
if (!response.ok)
|
|
167
|
+
throw new Error(`Hacklab rejected the usage report (HTTP ${response.status}). Run hacklab sync to retry.`);
|
|
168
|
+
const result = await response.json();
|
|
169
|
+
if (result.ok !== true)
|
|
170
|
+
throw new Error("Hacklab did not confirm the sync. Run hacklab sync to retry.");
|
|
171
|
+
console.log(`Synced ${result.sessions} usage sessions to Hacklab.`);
|
|
172
|
+
if (result.sessions === 0)
|
|
173
|
+
console.log("No usage found. Use your coding agent, then run hacklab sync again to complete onboarding.");
|
|
174
|
+
}
|
|
175
|
+
async function sync() {
|
|
176
|
+
const origin = cliOrigin();
|
|
177
|
+
const token = await authenticate(origin);
|
|
178
|
+
console.log("Collecting ccusage session data…");
|
|
179
|
+
const body = await collectUsage();
|
|
180
|
+
await uploadUsage(origin, token, body);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/onboard.ts
|
|
17
184
|
async function onboard(options) {
|
|
18
|
-
const origin =
|
|
19
|
-
const file =
|
|
185
|
+
const origin = cliOrigin();
|
|
186
|
+
const file = await stat(options.file);
|
|
20
187
|
if (file.size > 8192)
|
|
21
188
|
throw new Error("Profile must be smaller than 8 KB.");
|
|
22
|
-
const
|
|
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"));
|
|
23
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))
|
|
24
|
-
throw new Error("Expected a one-line bio (1
|
|
194
|
+
throw new Error("Expected a one-line bio (1–160 characters) and a socials object, with no other fields.");
|
|
25
195
|
const hosts = {
|
|
26
196
|
github: ["github.com"],
|
|
27
197
|
linkedin: ["linkedin.com"],
|
|
@@ -48,21 +218,12 @@ async function onboard(options) {
|
|
|
48
218
|
console.log(`Hacklab profile preview for ${origin}:
|
|
49
219
|
${payload}`);
|
|
50
220
|
if (!options.confirmUpload) {
|
|
51
|
-
console.log("Nothing uploaded. Ask the user to approve this exact profile, then run again with --confirm-upload.");
|
|
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.");
|
|
52
222
|
return;
|
|
53
223
|
}
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
let claims;
|
|
58
|
-
try {
|
|
59
|
-
claims = JSON.parse(Buffer.from(token.split(".")[0], "base64url").toString());
|
|
60
|
-
} catch {
|
|
61
|
-
throw new Error("Invalid onboarding token. Copy fresh agent instructions.");
|
|
62
|
-
}
|
|
63
|
-
if (claims.audience !== origin || typeof claims.expiresAt !== "number" || claims.expiresAt <= Date.now()) {
|
|
64
|
-
throw new Error("Onboarding token is expired or belongs to another Hacklab origin. Copy fresh agent instructions.");
|
|
65
|
-
}
|
|
224
|
+
const usage = await collectUsage();
|
|
225
|
+
const token = await authenticate(origin);
|
|
226
|
+
await uploadUsage(origin, token, usage);
|
|
66
227
|
const response = await fetch(`${origin}/api/onboarding`, {
|
|
67
228
|
method: "POST",
|
|
68
229
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
@@ -71,7 +232,7 @@ ${payload}`);
|
|
|
71
232
|
signal: AbortSignal.timeout(30000)
|
|
72
233
|
});
|
|
73
234
|
if (!response.ok) {
|
|
74
|
-
throw new Error(`Hacklab rejected the profile (HTTP ${response.status}). Check the profile and
|
|
235
|
+
throw new Error(`Hacklab rejected the profile (HTTP ${response.status}). Check the profile and sign-in before retrying.`);
|
|
75
236
|
}
|
|
76
237
|
const result = await response.json();
|
|
77
238
|
if (result.ok !== true)
|
|
@@ -82,33 +243,44 @@ ${payload}`);
|
|
|
82
243
|
// src/index.ts
|
|
83
244
|
var help = `Hacklab CLI
|
|
84
245
|
|
|
246
|
+
Get started:
|
|
247
|
+
npm install -g hacklab@beta
|
|
248
|
+
hacklab sync
|
|
249
|
+
|
|
85
250
|
Usage:
|
|
86
|
-
hacklab
|
|
251
|
+
hacklab sync
|
|
252
|
+
hacklab onboard --file <profile.json> [--confirm-upload]
|
|
253
|
+
hacklab login
|
|
254
|
+
hacklab logout
|
|
87
255
|
hacklab --help
|
|
88
256
|
hacklab --version
|
|
89
257
|
|
|
90
|
-
|
|
258
|
+
Optional profile update:
|
|
91
259
|
--file Local JSON containing bio and socials
|
|
92
|
-
--
|
|
93
|
-
--confirm-upload Upload only after the user approves the exact profile
|
|
260
|
+
--confirm-upload Upload after approval of the profile and first usage sync
|
|
94
261
|
|
|
95
262
|
Without --confirm-upload, onboard validates and previews locally without uploading.
|
|
96
|
-
|
|
97
|
-
|
|
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.`;
|
|
98
271
|
try {
|
|
99
272
|
const { values, positionals } = parseArgs({
|
|
100
|
-
args:
|
|
273
|
+
args: process.argv.slice(2),
|
|
101
274
|
options: {
|
|
102
275
|
help: { type: "boolean", short: "h" },
|
|
103
276
|
version: { type: "boolean", short: "v" },
|
|
104
277
|
file: { type: "string" },
|
|
105
|
-
url: { type: "string" },
|
|
106
278
|
"confirm-upload": { type: "boolean" }
|
|
107
279
|
},
|
|
108
280
|
allowPositionals: true
|
|
109
281
|
});
|
|
110
|
-
if (positionals.length > 1 || positionals[0] &&
|
|
111
|
-
throw new Error("Unknown command. Use hacklab onboard or hacklab --help.");
|
|
282
|
+
if (positionals.length > 1 || positionals[0] && !["onboard", "sync", "login", "logout"].includes(positionals[0])) {
|
|
283
|
+
throw new Error("Unknown command. Use hacklab onboard, hacklab sync, hacklab login, hacklab logout, or hacklab --help.");
|
|
112
284
|
}
|
|
113
285
|
if (values.help) {
|
|
114
286
|
console.log(help);
|
|
@@ -119,10 +291,23 @@ try {
|
|
|
119
291
|
throw new Error("Pass --file with the local profile JSON to preview or upload.");
|
|
120
292
|
await onboard({
|
|
121
293
|
file: values.file,
|
|
122
|
-
origin: values.url ?? "https://beta.hacklab.so",
|
|
123
294
|
confirmUpload: values["confirm-upload"] ?? false
|
|
124
295
|
});
|
|
125
|
-
} else if (
|
|
296
|
+
} else if (positionals[0] === "login") {
|
|
297
|
+
if (values.file || values["confirm-upload"])
|
|
298
|
+
throw new Error("Profile options require the onboard command.");
|
|
299
|
+
await authenticate(cliOrigin());
|
|
300
|
+
console.log("Signed in to Hacklab. Run hacklab sync to upload usage.");
|
|
301
|
+
} else if (positionals[0] === "logout") {
|
|
302
|
+
if (values.file || values["confirm-upload"])
|
|
303
|
+
throw new Error("Profile options require the onboard command.");
|
|
304
|
+
await removeCredential(cliOrigin());
|
|
305
|
+
console.log("Saved CLI authorization cleared. Run hacklab sync to sign in again.");
|
|
306
|
+
} else if (positionals[0] === "sync") {
|
|
307
|
+
if (values.file || values["confirm-upload"])
|
|
308
|
+
throw new Error("Profile options require the onboard command.");
|
|
309
|
+
await sync();
|
|
310
|
+
} else if (values.file || values["confirm-upload"]) {
|
|
126
311
|
throw new Error("Profile options require the onboard command.");
|
|
127
312
|
} else {
|
|
128
313
|
console.log(help);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hacklab",
|
|
3
|
-
"version": "26.921.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "26.921.2",
|
|
4
|
+
"description": "CLI for agent-assisted Hacklab profile onboarding",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hacklab": "./dist/index.js"
|
|
7
7
|
},
|
|
@@ -14,18 +14,22 @@
|
|
|
14
14
|
"tag": "beta"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "bun
|
|
17
|
+
"build": "bun --env-file=../../apps/web/.env --env-file=../../apps/web/.env.local build.ts",
|
|
18
18
|
"start": "bun src/index.ts",
|
|
19
19
|
"lint": "oxlint .",
|
|
20
20
|
"check-types": "tsc --noEmit",
|
|
21
21
|
"prepack": "bun run build",
|
|
22
|
-
"
|
|
22
|
+
"build:dev": "bun build.ts --development"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"ccusage": "20.0.24"
|
|
23
26
|
},
|
|
24
27
|
"devDependencies": {
|
|
25
|
-
"@types/bun": "
|
|
26
|
-
"
|
|
28
|
+
"@types/bun": "catalog:",
|
|
29
|
+
"@types/node": "catalog:",
|
|
30
|
+
"typescript": "catalog:"
|
|
27
31
|
},
|
|
28
32
|
"engines": {
|
|
29
|
-
"
|
|
33
|
+
"node": ">=22"
|
|
30
34
|
}
|
|
31
35
|
}
|