hacklab 0.22.3 → 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 -415
- package/dist/index.js +317 -23
- package/package.json +22 -40
- package/LICENSE +0 -21
- package/dist/agent-handoff.d.ts +0 -51
- package/dist/agent-handoff.d.ts.map +0 -1
- package/dist/agent-handoff.js +0 -91
- package/dist/agent-handoff.js.map +0 -1
- package/dist/api-client.d.ts +0 -18
- package/dist/api-client.d.ts.map +0 -1
- package/dist/api-client.js +0 -40
- package/dist/api-client.js.map +0 -1
- package/dist/belt.d.ts +0 -25
- package/dist/belt.d.ts.map +0 -1
- package/dist/belt.js +0 -113
- package/dist/belt.js.map +0 -1
- package/dist/card.d.ts +0 -66
- package/dist/card.d.ts.map +0 -1
- package/dist/card.js +0 -151
- package/dist/card.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -238
- package/dist/cli.js.map +0 -1
- package/dist/commands/book.d.ts +0 -14
- package/dist/commands/book.d.ts.map +0 -1
- package/dist/commands/book.js +0 -90
- package/dist/commands/book.js.map +0 -1
- package/dist/commands/chat.d.ts +0 -36
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/chat.js +0 -642
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js +0 -84
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/daemon.d.ts +0 -19
- package/dist/commands/daemon.d.ts.map +0 -1
- package/dist/commands/daemon.js +0 -72
- package/dist/commands/daemon.js.map +0 -1
- package/dist/commands/drop.d.ts +0 -12
- package/dist/commands/drop.d.ts.map +0 -1
- package/dist/commands/drop.js +0 -103
- package/dist/commands/drop.js.map +0 -1
- package/dist/commands/drops.d.ts +0 -9
- package/dist/commands/drops.d.ts.map +0 -1
- package/dist/commands/drops.js +0 -96
- package/dist/commands/drops.js.map +0 -1
- package/dist/commands/essay.d.ts +0 -40
- package/dist/commands/essay.d.ts.map +0 -1
- package/dist/commands/essay.js +0 -651
- package/dist/commands/essay.js.map +0 -1
- package/dist/commands/hackathon.d.ts +0 -2
- package/dist/commands/hackathon.d.ts.map +0 -1
- package/dist/commands/hackathon.js +0 -893
- package/dist/commands/hackathon.js.map +0 -1
- package/dist/commands/hacker.d.ts +0 -2
- package/dist/commands/hacker.d.ts.map +0 -1
- package/dist/commands/hacker.js +0 -87
- package/dist/commands/hacker.js.map +0 -1
- package/dist/commands/jobs.d.ts +0 -18
- package/dist/commands/jobs.d.ts.map +0 -1
- package/dist/commands/jobs.js +0 -138
- package/dist/commands/jobs.js.map +0 -1
- package/dist/commands/keys.d.ts +0 -2
- package/dist/commands/keys.d.ts.map +0 -1
- package/dist/commands/keys.js +0 -138
- package/dist/commands/keys.js.map +0 -1
- package/dist/commands/login.d.ts +0 -83
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js +0 -236
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts +0 -2
- package/dist/commands/logout.d.ts.map +0 -1
- package/dist/commands/logout.js +0 -22
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/org-jobs.d.ts +0 -50
- package/dist/commands/org-jobs.d.ts.map +0 -1
- package/dist/commands/org-jobs.js +0 -383
- package/dist/commands/org-jobs.js.map +0 -1
- package/dist/commands/org.d.ts +0 -67
- package/dist/commands/org.d.ts.map +0 -1
- package/dist/commands/org.js +0 -1244
- package/dist/commands/org.js.map +0 -1
- package/dist/commands/ping.d.ts +0 -2
- package/dist/commands/ping.d.ts.map +0 -1
- package/dist/commands/ping.js +0 -50
- package/dist/commands/ping.js.map +0 -1
- package/dist/commands/profile.d.ts +0 -72
- package/dist/commands/profile.d.ts.map +0 -1
- package/dist/commands/profile.js +0 -672
- package/dist/commands/profile.js.map +0 -1
- package/dist/commands/project.d.ts +0 -2
- package/dist/commands/project.d.ts.map +0 -1
- package/dist/commands/project.js +0 -583
- package/dist/commands/project.js.map +0 -1
- package/dist/commands/referral.d.ts +0 -6
- package/dist/commands/referral.d.ts.map +0 -1
- package/dist/commands/referral.js +0 -93
- package/dist/commands/referral.js.map +0 -1
- package/dist/commands/rtfm.d.ts +0 -9
- package/dist/commands/rtfm.d.ts.map +0 -1
- package/dist/commands/rtfm.js +0 -114
- package/dist/commands/rtfm.js.map +0 -1
- package/dist/commands/scan.d.ts +0 -13
- package/dist/commands/scan.d.ts.map +0 -1
- package/dist/commands/scan.js +0 -286
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/scout.d.ts +0 -18
- package/dist/commands/scout.d.ts.map +0 -1
- package/dist/commands/scout.js +0 -232
- package/dist/commands/scout.js.map +0 -1
- package/dist/commands/setup-rail.d.ts +0 -58
- package/dist/commands/setup-rail.d.ts.map +0 -1
- package/dist/commands/setup-rail.js +0 -135
- package/dist/commands/setup-rail.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -29
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js +0 -442
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/sync.d.ts +0 -9
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/sync.js +0 -345
- package/dist/commands/sync.js.map +0 -1
- package/dist/commands/update.d.ts +0 -2
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js +0 -92
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/whoami.d.ts +0 -2
- package/dist/commands/whoami.d.ts.map +0 -1
- package/dist/commands/whoami.js +0 -41
- package/dist/commands/whoami.js.map +0 -1
- package/dist/config.d.ts +0 -78
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -93
- package/dist/config.js.map +0 -1
- package/dist/daily-sync.d.ts +0 -150
- package/dist/daily-sync.d.ts.map +0 -1
- package/dist/daily-sync.js +0 -665
- package/dist/daily-sync.js.map +0 -1
- package/dist/dossier.d.ts +0 -73
- package/dist/dossier.d.ts.map +0 -1
- package/dist/dossier.js +0 -141
- package/dist/dossier.js.map +0 -1
- package/dist/field-normalize.d.ts +0 -4
- package/dist/field-normalize.d.ts.map +0 -1
- package/dist/field-normalize.js +0 -8
- package/dist/field-normalize.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/job-fields.d.ts +0 -47
- package/dist/job-fields.d.ts.map +0 -1
- package/dist/job-fields.js +0 -148
- package/dist/job-fields.js.map +0 -1
- package/dist/machine.d.ts +0 -15
- package/dist/machine.d.ts.map +0 -1
- package/dist/machine.js +0 -64
- package/dist/machine.js.map +0 -1
- package/dist/org-fields.d.ts +0 -57
- package/dist/org-fields.d.ts.map +0 -1
- package/dist/org-fields.js +0 -167
- package/dist/org-fields.js.map +0 -1
- package/dist/posthog.d.ts +0 -38
- package/dist/posthog.d.ts.map +0 -1
- package/dist/posthog.js +0 -224
- package/dist/posthog.js.map +0 -1
- package/dist/project-fields.d.ts +0 -40
- package/dist/project-fields.d.ts.map +0 -1
- package/dist/project-fields.js +0 -79
- package/dist/project-fields.js.map +0 -1
- package/dist/prompt-consent.d.ts +0 -73
- package/dist/prompt-consent.d.ts.map +0 -1
- package/dist/prompt-consent.js +0 -191
- package/dist/prompt-consent.js.map +0 -1
- package/dist/prompt-stats.d.ts +0 -226
- package/dist/prompt-stats.d.ts.map +0 -1
- package/dist/prompt-stats.js +0 -561
- package/dist/prompt-stats.js.map +0 -1
- package/dist/referral.d.ts +0 -14
- package/dist/referral.d.ts.map +0 -1
- package/dist/referral.js +0 -28
- package/dist/referral.js.map +0 -1
- package/dist/registry.d.ts +0 -15
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -180
- package/dist/registry.js.map +0 -1
- package/dist/resolve-command.d.ts +0 -20
- package/dist/resolve-command.d.ts.map +0 -1
- package/dist/resolve-command.js +0 -21
- package/dist/resolve-command.js.map +0 -1
- package/dist/scanners/antigravity.d.ts +0 -41
- package/dist/scanners/antigravity.d.ts.map +0 -1
- package/dist/scanners/antigravity.js +0 -210
- package/dist/scanners/antigravity.js.map +0 -1
- package/dist/scanners/github-copilot-telemetry.d.ts +0 -23
- package/dist/scanners/github-copilot-telemetry.d.ts.map +0 -1
- package/dist/scanners/github-copilot-telemetry.js +0 -220
- package/dist/scanners/github-copilot-telemetry.js.map +0 -1
- package/dist/scanners/github-copilot.d.ts +0 -68
- package/dist/scanners/github-copilot.d.ts.map +0 -1
- package/dist/scanners/github-copilot.js +0 -262
- package/dist/scanners/github-copilot.js.map +0 -1
- package/dist/scanners/incremental.d.ts +0 -282
- package/dist/scanners/incremental.d.ts.map +0 -1
- package/dist/scanners/incremental.js +0 -788
- package/dist/scanners/incremental.js.map +0 -1
- package/dist/scanners/index.d.ts +0 -116
- package/dist/scanners/index.d.ts.map +0 -1
- package/dist/scanners/index.js +0 -726
- package/dist/scanners/index.js.map +0 -1
- package/dist/scanners/sqlite.d.ts +0 -18
- package/dist/scanners/sqlite.d.ts.map +0 -1
- package/dist/scanners/sqlite.js +0 -63
- package/dist/scanners/sqlite.js.map +0 -1
- package/dist/scanners/util.d.ts +0 -112
- package/dist/scanners/util.d.ts.map +0 -1
- package/dist/scanners/util.js +0 -167
- package/dist/scanners/util.js.map +0 -1
- package/dist/session.d.ts +0 -106
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -191
- package/dist/session.js.map +0 -1
- package/dist/share-card-colors.d.ts +0 -19
- package/dist/share-card-colors.d.ts.map +0 -1
- package/dist/share-card-colors.js +0 -36
- package/dist/share-card-colors.js.map +0 -1
- package/dist/share-card.d.ts +0 -31
- package/dist/share-card.d.ts.map +0 -1
- package/dist/share-card.js +0 -380
- package/dist/share-card.js.map +0 -1
- package/dist/share.d.ts +0 -17
- package/dist/share.d.ts.map +0 -1
- package/dist/share.js +0 -75
- package/dist/share.js.map +0 -1
- package/dist/sync.d.ts +0 -100
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -304
- package/dist/sync.js.map +0 -1
- package/dist/ui.d.ts +0 -33
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js +0 -127
- package/dist/ui.js.map +0 -1
- package/dist/utils/npmGlobal.d.ts +0 -30
- package/dist/utils/npmGlobal.d.ts.map +0 -1
- package/dist/utils/npmGlobal.js +0 -69
- package/dist/utils/npmGlobal.js.map +0 -1
- package/dist/utils/openBrowser.d.ts +0 -14
- package/dist/utils/openBrowser.d.ts.map +0 -1
- package/dist/utils/openBrowser.js +0 -47
- package/dist/utils/openBrowser.js.map +0 -1
- package/dist/utils/updateCheck.d.ts +0 -16
- package/dist/utils/updateCheck.d.ts.map +0 -1
- package/dist/utils/updateCheck.js +0 -111
- package/dist/utils/updateCheck.js.map +0 -1
- package/dist/utils/waitForEnter.d.ts +0 -12
- package/dist/utils/waitForEnter.d.ts.map +0 -1
- package/dist/utils/waitForEnter.js +0 -43
- package/dist/utils/waitForEnter.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,24 +1,318 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
// package.json
|
|
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
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/onboard.ts
|
|
127
|
+
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
128
|
+
|
|
129
|
+
// src/origin.ts
|
|
130
|
+
function cliOrigin() {
|
|
131
|
+
return "https://beta.hacklab.so";
|
|
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
|
|
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
|
+
// src/index.ts
|
|
244
|
+
var help = `Hacklab CLI
|
|
245
|
+
|
|
246
|
+
Get started:
|
|
247
|
+
npm install -g hacklab@beta
|
|
248
|
+
hacklab sync
|
|
249
|
+
|
|
250
|
+
Usage:
|
|
251
|
+
hacklab sync
|
|
252
|
+
hacklab onboard --file <profile.json> [--confirm-upload]
|
|
253
|
+
hacklab login
|
|
254
|
+
hacklab logout
|
|
255
|
+
hacklab --help
|
|
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.`;
|
|
271
|
+
try {
|
|
272
|
+
const { values, positionals } = parseArgs({
|
|
273
|
+
args: process.argv.slice(2),
|
|
274
|
+
options: {
|
|
275
|
+
help: { type: "boolean", short: "h" },
|
|
276
|
+
version: { type: "boolean", short: "v" },
|
|
277
|
+
file: { type: "string" },
|
|
278
|
+
"confirm-upload": { type: "boolean" }
|
|
279
|
+
},
|
|
280
|
+
allowPositionals: true
|
|
281
|
+
});
|
|
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.");
|
|
284
|
+
}
|
|
285
|
+
if (values.help) {
|
|
286
|
+
console.log(help);
|
|
287
|
+
} else if (values.version) {
|
|
288
|
+
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
|
+
} 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"]) {
|
|
311
|
+
throw new Error("Profile options require the onboard command.");
|
|
312
|
+
} else {
|
|
313
|
+
console.log(help);
|
|
314
|
+
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.error(error instanceof Error ? error.message : "Could not run Hacklab CLI.");
|
|
317
|
+
process.exitCode = 1;
|
|
318
|
+
}
|
package/package.json
CHANGED
|
@@ -1,53 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hacklab",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"publishConfig": {
|
|
6
|
-
"access": "public"
|
|
7
|
-
},
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/hacklabubu/cli.git"
|
|
11
|
-
},
|
|
12
|
-
"type": "module",
|
|
13
|
-
"packageManager": "pnpm@10.11.1",
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": ">=20"
|
|
16
|
-
},
|
|
3
|
+
"version": "26.921.2",
|
|
4
|
+
"description": "CLI for agent-assisted Hacklab profile onboarding",
|
|
17
5
|
"bin": {
|
|
18
6
|
"hacklab": "./dist/index.js"
|
|
19
7
|
},
|
|
20
|
-
"files": [
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public",
|
|
14
|
+
"tag": "beta"
|
|
15
|
+
},
|
|
21
16
|
"scripts": {
|
|
22
|
-
"build": "
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
17
|
+
"build": "bun --env-file=../../apps/web/.env --env-file=../../apps/web/.env.local build.ts",
|
|
18
|
+
"start": "bun src/index.ts",
|
|
19
|
+
"lint": "oxlint .",
|
|
20
|
+
"check-types": "tsc --noEmit",
|
|
21
|
+
"prepack": "bun run build",
|
|
22
|
+
"build:dev": "bun build.ts --development"
|
|
27
23
|
},
|
|
28
24
|
"dependencies": {
|
|
29
|
-
"
|
|
30
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
31
|
-
"@vercel/og": "^0.11.1",
|
|
32
|
-
"chalk": "^5.6.2",
|
|
33
|
-
"jsonc-parser": "^3.3.1",
|
|
34
|
-
"posthog-node": "^5.45.2",
|
|
35
|
-
"protobufjs": "^8.8.0",
|
|
36
|
-
"satori": "^0.26.0",
|
|
37
|
-
"sql.js": "^1.14.1",
|
|
38
|
-
"string-width": "^7.2.0",
|
|
39
|
-
"yaml": "^2.8.3",
|
|
40
|
-
"zod": "^4.3.6"
|
|
25
|
+
"ccusage": "20.0.24"
|
|
41
26
|
},
|
|
42
27
|
"devDependencies": {
|
|
43
|
-
"@
|
|
44
|
-
"@types/node": "
|
|
45
|
-
"
|
|
46
|
-
"tsx": "^4.21.0",
|
|
47
|
-
"typescript": "^5.9.3",
|
|
48
|
-
"vitest": "^4.1.2"
|
|
28
|
+
"@types/bun": "catalog:",
|
|
29
|
+
"@types/node": "catalog:",
|
|
30
|
+
"typescript": "catalog:"
|
|
49
31
|
},
|
|
50
|
-
"
|
|
51
|
-
"
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22"
|
|
52
34
|
}
|
|
53
35
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Homebrew Hackers Club inc
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/dist/agent-handoff.d.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The handoff: at the end of `hacklab setup` we look for a coding-agent CLI on
|
|
3
|
-
* the machine and, with one keypress of consent, hand it the profile-setup
|
|
4
|
-
* prompt. The agent runs interactively in the same terminal — the user watches
|
|
5
|
-
* it, answers its permission prompts, and (if the agent CLI is installed but
|
|
6
|
-
* logged out) walks its own login flow. Nothing here is silent or headless.
|
|
7
|
-
*
|
|
8
|
-
* Every outcome is reported to the backend, because the web onboarding waits
|
|
9
|
-
* only on positive evidence: a `launched` says an agent is running and the page
|
|
10
|
-
* should sit on "waiting for your agent"; `declined` / `unavailable` (and no
|
|
11
|
-
* signal at all) put the manual paste-the-prompt step up instead.
|
|
12
|
-
*/
|
|
13
|
-
export type AgentCli = {
|
|
14
|
-
/** Executable name, as it appears on PATH. */
|
|
15
|
-
bin: string;
|
|
16
|
-
/** What we call it in the terminal. */
|
|
17
|
-
name: string;
|
|
18
|
-
/** argv for "open interactively, seeded with this prompt". */
|
|
19
|
-
args: (prompt: string) => string[];
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Probed in order — first one on PATH wins. All three take the prompt as a
|
|
23
|
-
* single positional argument today; the shape stays per-entry so one of them
|
|
24
|
-
* can diverge without touching the others.
|
|
25
|
-
*/
|
|
26
|
-
export declare const AGENT_CLIS: AgentCli[];
|
|
27
|
-
/** The first agent CLI installed on this machine, or null if there is none. */
|
|
28
|
-
export declare function findAgentCli(): AgentCli | null;
|
|
29
|
-
/**
|
|
30
|
-
* Hand the terminal to `agent`, seeded with `prompt`, and block until its
|
|
31
|
-
* session exits. Returns false when the process never started (ENOENT, a broken
|
|
32
|
-
* shim) — the caller then treats the agent as unavailable. A non-zero exit is
|
|
33
|
-
* still a launch: the user was there and saw whatever happened.
|
|
34
|
-
*/
|
|
35
|
-
export declare function launchAgentCli(agent: AgentCli, prompt: string): boolean;
|
|
36
|
-
/**
|
|
37
|
-
* Tell the backend what happened to the profile work, so the web onboarding
|
|
38
|
-
* knows which state to show: `launched` is the positive evidence that an agent
|
|
39
|
-
* is now running and the page should wait for it; `declined` and `unavailable`
|
|
40
|
-
* mean nobody took it, so the page shows the manual paste-the-prompt step
|
|
41
|
-
* instead of waiting for a ping that is never coming.
|
|
42
|
-
*
|
|
43
|
-
* Best effort by design: a failure (offline, or a backend too old to know the
|
|
44
|
-
* `launched` outcome and 400ing it) must never break the end of setup.
|
|
45
|
-
*
|
|
46
|
-
* Server contract (POST /api/cli/agent-handoff):
|
|
47
|
-
* `Authorization: Bearer <session token>`, body `{ outcome, agent? }`,
|
|
48
|
-
* where `agent` is the binary name we launched or offered (e.g. `claude`).
|
|
49
|
-
*/
|
|
50
|
-
export declare function notifyAgentHandoff(appUrl: string, token: string, outcome: 'launched' | 'unavailable' | 'declined', agent?: string): Promise<void>;
|
|
51
|
-
//# sourceMappingURL=agent-handoff.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agent-handoff.d.ts","sourceRoot":"","sources":["../src/agent-handoff.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACrB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAA;IACX,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,EAAE,CAAA;CACnC,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,EAIhC,CAAA;AA2BD,+EAA+E;AAC/E,wBAAgB,YAAY,IAAI,QAAQ,GAAG,IAAI,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAWvE;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAAG,aAAa,GAAG,UAAU,EAChD,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAcf"}
|
package/dist/agent-handoff.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { accessSync, constants } from 'node:fs';
|
|
3
|
-
import { delimiter, join } from 'node:path';
|
|
4
|
-
/**
|
|
5
|
-
* Probed in order — first one on PATH wins. All three take the prompt as a
|
|
6
|
-
* single positional argument today; the shape stays per-entry so one of them
|
|
7
|
-
* can diverge without touching the others.
|
|
8
|
-
*/
|
|
9
|
-
export const AGENT_CLIS = [
|
|
10
|
-
{ bin: 'claude', name: 'Claude Code', args: (prompt) => [prompt] },
|
|
11
|
-
{ bin: 'codex', name: 'Codex', args: (prompt) => [prompt] },
|
|
12
|
-
{ bin: 'grok', name: 'Grok', args: (prompt) => [prompt] },
|
|
13
|
-
];
|
|
14
|
-
/** Is `bin` an executable file in one of the PATH directories? */
|
|
15
|
-
function onPath(bin) {
|
|
16
|
-
const dirs = (process.env.PATH ?? '').split(delimiter).filter(Boolean);
|
|
17
|
-
// On Windows the thing on PATH is `claude.cmd` / `claude.exe`, never a
|
|
18
|
-
// bare-named file, so every candidate needs the PATHEXT suffixes tried.
|
|
19
|
-
const exts = process.platform === 'win32'
|
|
20
|
-
? (process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD')
|
|
21
|
-
.split(';')
|
|
22
|
-
.filter(Boolean)
|
|
23
|
-
: [''];
|
|
24
|
-
for (const dir of dirs) {
|
|
25
|
-
for (const ext of exts) {
|
|
26
|
-
try {
|
|
27
|
-
accessSync(join(dir, bin + ext), constants.X_OK);
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
// Not here (missing, or not executable) — keep looking.
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
/** The first agent CLI installed on this machine, or null if there is none. */
|
|
38
|
-
export function findAgentCli() {
|
|
39
|
-
return AGENT_CLIS.find((agent) => onPath(agent.bin)) ?? null;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Hand the terminal to `agent`, seeded with `prompt`, and block until its
|
|
43
|
-
* session exits. Returns false when the process never started (ENOENT, a broken
|
|
44
|
-
* shim) — the caller then treats the agent as unavailable. A non-zero exit is
|
|
45
|
-
* still a launch: the user was there and saw whatever happened.
|
|
46
|
-
*/
|
|
47
|
-
export function launchAgentCli(agent, prompt) {
|
|
48
|
-
// Windows agent CLIs are `.cmd` shims, which Node refuses to spawn without a
|
|
49
|
-
// shell; cmd.exe joins argv verbatim, so the prompt needs its own quotes.
|
|
50
|
-
const win = process.platform === 'win32';
|
|
51
|
-
const args = agent.args(prompt).map((arg) => (win ? `"${arg}"` : arg));
|
|
52
|
-
try {
|
|
53
|
-
const res = spawnSync(agent.bin, args, { stdio: 'inherit', shell: win });
|
|
54
|
-
return !res.error;
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
const HANDOFF_TIMEOUT_MS = 8000;
|
|
61
|
-
/**
|
|
62
|
-
* Tell the backend what happened to the profile work, so the web onboarding
|
|
63
|
-
* knows which state to show: `launched` is the positive evidence that an agent
|
|
64
|
-
* is now running and the page should wait for it; `declined` and `unavailable`
|
|
65
|
-
* mean nobody took it, so the page shows the manual paste-the-prompt step
|
|
66
|
-
* instead of waiting for a ping that is never coming.
|
|
67
|
-
*
|
|
68
|
-
* Best effort by design: a failure (offline, or a backend too old to know the
|
|
69
|
-
* `launched` outcome and 400ing it) must never break the end of setup.
|
|
70
|
-
*
|
|
71
|
-
* Server contract (POST /api/cli/agent-handoff):
|
|
72
|
-
* `Authorization: Bearer <session token>`, body `{ outcome, agent? }`,
|
|
73
|
-
* where `agent` is the binary name we launched or offered (e.g. `claude`).
|
|
74
|
-
*/
|
|
75
|
-
export async function notifyAgentHandoff(appUrl, token, outcome, agent) {
|
|
76
|
-
try {
|
|
77
|
-
await fetch(`${appUrl}/api/cli/agent-handoff`, {
|
|
78
|
-
method: 'POST',
|
|
79
|
-
headers: {
|
|
80
|
-
'Content-Type': 'application/json',
|
|
81
|
-
Authorization: `Bearer ${token}`,
|
|
82
|
-
},
|
|
83
|
-
body: JSON.stringify({ outcome, ...(agent ? { agent } : {}) }),
|
|
84
|
-
signal: AbortSignal.timeout(HANDOFF_TIMEOUT_MS),
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
// Best effort — setup is already done, and this only steers the web page.
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
//# sourceMappingURL=agent-handoff.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agent-handoff.js","sourceRoot":"","sources":["../src/agent-handoff.ts"],"names":[],"mappings":"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"}
|
package/dist/api-client.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { type Session } from './session.js';
|
|
2
|
-
/** Print the versioned JSON error envelope and exit 1 (agent mode). */
|
|
3
|
-
export declare function emitJsonError(code: string, message: string): never;
|
|
4
|
-
/** Login gate: returns the session or exits with mode-appropriate copy. */
|
|
5
|
-
export declare function requireSession(json: boolean): Promise<Session>;
|
|
6
|
-
/**
|
|
7
|
-
* Human-readable message for a non-2xx envelope body. 401 gets the per-backend
|
|
8
|
-
* re-login hint (an --env mismatch names the exact login command), 429 the
|
|
9
|
-
* slow-down copy; anything else relays the server's message.
|
|
10
|
-
*/
|
|
11
|
-
export declare function apiErrorMessage(status: number, body: {
|
|
12
|
-
error?: {
|
|
13
|
-
message?: string;
|
|
14
|
-
};
|
|
15
|
-
} | null, session: Session): string;
|
|
16
|
-
/** Read a failed Response's envelope and shape it via apiErrorMessage. */
|
|
17
|
-
export declare function readApiError(res: Response, session: Session): Promise<string>;
|
|
18
|
-
//# sourceMappingURL=api-client.d.ts.map
|
package/dist/api-client.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,OAAO,EAAoB,MAAM,cAAc,CAAA;AAO1E,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAGlE;AAED,2EAA2E;AAC3E,wBAAsB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CASpE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,IAAI,EAC7C,OAAO,EAAE,OAAO,GACf,MAAM,CAIR;AAED,0EAA0E;AAC1E,wBAAsB,YAAY,CAChC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,MAAM,CAAC,CAKjB"}
|
package/dist/api-client.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { loadSession, unauthorizedHint } from './session.js';
|
|
2
|
-
import { dim, error, info } from './ui.js';
|
|
3
|
-
// Shared plumbing for authenticated agent-friendly commands (profile, hacker):
|
|
4
|
-
// one login gate, one JSON error envelope, one reader for the server's
|
|
5
|
-
// {schemaVersion, error} shape — so the 401/429 copy can't drift per command.
|
|
6
|
-
/** Print the versioned JSON error envelope and exit 1 (agent mode). */
|
|
7
|
-
export function emitJsonError(code, message) {
|
|
8
|
-
console.log(JSON.stringify({ schemaVersion: 1, error: { code, message } }));
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
11
|
-
/** Login gate: returns the session or exits with mode-appropriate copy. */
|
|
12
|
-
export async function requireSession(json) {
|
|
13
|
-
const session = await loadSession();
|
|
14
|
-
if (!session) {
|
|
15
|
-
if (json)
|
|
16
|
-
emitJsonError('unauthorized', 'not logged in');
|
|
17
|
-
error('not logged in');
|
|
18
|
-
info(`run ${dim('hacklab login')} first`);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
return session;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Human-readable message for a non-2xx envelope body. 401 gets the per-backend
|
|
25
|
-
* re-login hint (an --env mismatch names the exact login command), 429 the
|
|
26
|
-
* slow-down copy; anything else relays the server's message.
|
|
27
|
-
*/
|
|
28
|
-
export function apiErrorMessage(status, body, session) {
|
|
29
|
-
if (status === 401)
|
|
30
|
-
return unauthorizedHint(session);
|
|
31
|
-
if (status === 429)
|
|
32
|
-
return 'slow down. try again in a minute.';
|
|
33
|
-
return body?.error?.message ?? `request failed (${status})`;
|
|
34
|
-
}
|
|
35
|
-
/** Read a failed Response's envelope and shape it via apiErrorMessage. */
|
|
36
|
-
export async function readApiError(res, session) {
|
|
37
|
-
const body = (await res.json().catch(() => null));
|
|
38
|
-
return apiErrorMessage(res.status, body, session);
|
|
39
|
-
}
|
|
40
|
-
//# sourceMappingURL=api-client.js.map
|