hacklab 0.22.3 → 26.921.1
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 +23 -430
- package/dist/index.js +132 -23
- package/package.json +20 -42
- 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/README.md
CHANGED
|
@@ -1,453 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hacklab CLI
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
for AI-native hackers. Sign in to Hacklab, scan your local AI token usage, share
|
|
5
|
-
a card.
|
|
3
|
+
A Bun CLI for updating your Hacklab bio and social links with your coding agent.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Install, then run setup:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
curl -fsSL https://hacklab.so/install | sh
|
|
13
|
-
hacklab setup
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
The script checks for Node 20+ and installs the CLI globally — that's all it
|
|
17
|
-
does. After it, `hacklab` is a real command on your PATH, and `hacklab setup` is
|
|
18
|
-
the guided first run: it scans this machine's AI usage, signs you in to Hacklab,
|
|
19
|
-
uploads your usage, and starts the background sync. If you already have Node 20
|
|
20
|
-
or newer (including a version manager), you can skip the script:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm i -g hacklab@latest
|
|
24
|
-
hacklab setup
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
`login`, `scan`, and `sync` all still exist as standalone commands for re-running
|
|
28
|
-
one piece on its own.
|
|
29
|
-
|
|
30
|
-
### Windows
|
|
31
|
-
|
|
32
|
-
On native Windows (PowerShell), use the `.ps1` installer instead — it checks
|
|
33
|
-
for Node 20+ and installs the CLI globally, then points you at `hacklab setup`:
|
|
34
|
-
|
|
35
|
-
```powershell
|
|
36
|
-
irm https://hacklab.so/install.ps1 | iex
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
The CLI reads your agents' usage from your Windows home (`%USERPROFILE%\.claude`,
|
|
40
|
-
`.codex`, `.grok`, and Cursor's native tracking DB), and `hacklab daemon` registers the
|
|
41
|
-
background sync as two Task Scheduler tasks. If you run Claude Code or Codex
|
|
42
|
-
**inside WSL**, install there instead with the `curl … | sh` command above, run
|
|
43
|
-
from your WSL shell.
|
|
44
|
-
|
|
45
|
-
The installer also enables local PowerShell scripts for your user
|
|
46
|
-
(`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`) so the `hacklab` command
|
|
47
|
-
runs. If you install manually instead (`npm i -g hacklab@latest`) and PowerShell
|
|
48
|
-
says *"running scripts is disabled on this system"*, run that one line yourself,
|
|
49
|
-
or call `hacklab.cmd …` (which no execution policy blocks).
|
|
50
|
-
|
|
51
|
-
The `curl | sh` installer itself only installs — it never touches your session.
|
|
52
|
-
|
|
53
|
-
The CLI prints a one-line nudge when a newer version is published (checked at
|
|
54
|
-
most once a day); update with `npm i -g hacklab@latest`.
|
|
55
|
-
|
|
56
|
-
## Scan
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
login → scan this machine → upload → receipt → share on X → daemon on
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
`hacklab scan` requires `hacklab login`. It reads local AI token usage from
|
|
63
|
-
Claude Code, Codex, Cursor, OpenClaw, Hermes, OpenCode, Grok Build, GitHub Copilot,
|
|
64
|
-
and Antigravity, uploads it to your profile, and prints a text usage receipt
|
|
65
|
-
without displaying a card or image in the terminal. Then it asks whether to
|
|
66
|
-
share a card on X, using the live account's rank, belt, and streak.
|
|
67
|
-
A successful scan summons the daemon so the card stays current; `hacklab scan
|
|
68
|
-
--no-daemon` skips the schedule, and `hacklab daemon off` tears it down.
|
|
69
|
-
Skip the whole command if this computer isn't yours — it would upload that
|
|
70
|
-
machine's usage to your profile. Cursor users with no API key are offered one
|
|
71
|
-
(local Cursor data is only an estimate).
|
|
72
|
-
|
|
73
|
-
## Commands
|
|
74
|
-
|
|
75
|
-
- `hacklab setup` — the guided first run: scan, sign-in, one question
|
|
76
|
-
about syncing your prompt activity, upload, background sync on. Safe to
|
|
77
|
-
re-run; it checks your saved session with the server first, skips whatever is
|
|
78
|
-
already done, and stops early once everything is. If that session has expired
|
|
79
|
-
or been revoked it signs you in again instead of reporting you as set up; if
|
|
80
|
-
the server can't be reached it says so and carries on with what's on disk.
|
|
81
|
-
- `hacklab scan` — scan this machine, upload to your profile, share the card.
|
|
82
|
-
Requires login. Summons the daemon afterwards (`--no-daemon` to skip).
|
|
83
|
-
- `hacklab sync` — re-scan local AI usage and sync it to your profile.
|
|
84
|
-
- `hacklab daemon` — summon the daemon: two OS-native background jobs (launchd on
|
|
85
|
-
macOS, systemd user timers on Linux, Task Scheduler tasks on Windows) so your
|
|
86
|
-
tokens, rank, and streak stay current without you running anything. A **tick
|
|
87
|
-
every minute** reads only what your tools appended since the last run — no
|
|
88
|
-
network call at all on a minute where nothing happened — and a **full sync once
|
|
89
|
-
a day** re-scans everything and repairs whatever the tick missed. With prompt
|
|
90
|
-
sync on (see below), the same minutely read also pushes your prompt activity.
|
|
91
|
-
No daemon, no streak. Re-running it is idempotent; `hacklab daemon off` tears both down, and
|
|
92
|
-
`hacklab logout` removes them too. On a platform we can't schedule (BSD, a
|
|
93
|
-
locked-down box) it prints the commands to schedule yourself instead of
|
|
94
|
-
pretending it worked. `hacklab sync --install-daily` still forwards here.
|
|
95
|
-
- `hacklab whoami` — show who you're logged in as.
|
|
96
|
-
- `hacklab drop "message"` — post a drop to your feed (`-u <url>` to attach a
|
|
97
|
-
link). Human output prints its profile URL; `--json` returns a stable envelope
|
|
98
|
-
(`schemaVersion`, `id`, `path`, full `url`).
|
|
99
|
-
- `hacklab chat` — open the live channel (typing posts; this is the default, so
|
|
100
|
-
bare `hacklab chat` and `hacklab chat live` are the same). Other subcommands:
|
|
101
|
-
`tail`, `post`, `history`, `dms`, `dm <handle>`, `flag`. Author handles are
|
|
102
|
-
coloured by belt rank. Add `--json` to any non-interactive subcommand for
|
|
103
|
-
machine-readable output an agent can drive.
|
|
104
|
-
- `hacklab login` — sign in to Hacklab (creates an account if you don't have one).
|
|
105
|
-
- `hacklab logout` — clear your saved session on this machine.
|
|
106
|
-
- `hacklab config <key> <value>` — set config (`cursor-api-key`, `cursor-email`,
|
|
107
|
-
`prompt-sync`).
|
|
108
|
-
Bare `hacklab config` prints the effective values and where each came from.
|
|
109
|
-
- `hacklab project` — agent help for publishing a project to your profile.
|
|
110
|
-
`project add --title "…" [--repo <git url>] [--url <live url>] [--desc "…"]`
|
|
111
|
-
posts it (`--json` for agents); one of `--repo`/`--url` is required. `--repo`
|
|
112
|
-
takes any git host; a github.com `--url` with no `--repo` is treated as the
|
|
113
|
-
repo. A github repo is probed for visibility so a private one stays hidden on
|
|
114
|
-
the web; `--private`/`--public` override that. Re-run with the same title to
|
|
115
|
-
update. `project list` shows yours, `project view <slug>` prints one full
|
|
116
|
-
page, and `project edit <slug> [--title/--desc/--repo/--url/--private/--public]`
|
|
117
|
-
changes fields — editing a GitHub-synced project ends that sync, so it needs
|
|
118
|
-
`--yes`. `project delete <slug>` removes one; it confirms first, and `--yes`
|
|
119
|
-
is required when there's no terminal to ask (scripts, agents, `--json`).
|
|
120
|
-
- `hacklab essay` — agent help for posting an essay. `essay post --title "…"
|
|
121
|
-
--content <md>` publishes it (`--file <path.md>`, or a bare `<path.md>`, for
|
|
122
|
-
a file on disk; `--json` for agents). `essay update <id>` replaces the body at
|
|
123
|
-
the same URL. `essay list` shows yours — `essay list <handle>` a hacker's,
|
|
124
|
-
`essay list org <slug>` an org's, `--page N` for more. `essay view <id>` reads
|
|
125
|
-
one essay. `essay delete <id>` removes one of yours; like `project delete` it
|
|
126
|
-
confirms first and needs `--yes` when nothing can ask.
|
|
127
|
-
- `hacklab org` — hub for company management. If you already own a company, pick
|
|
128
|
-
a field, type the new value, and it saves as you go. If you don't own one yet,
|
|
129
|
-
it offers to claim or create. Subcommands: `hacklab org claim` (take ownership
|
|
130
|
-
of a YC-seeded company you're a member of, or whose domain matches your login
|
|
131
|
-
email) and `hacklab org create` (register a brand-new company from the CLI).
|
|
132
|
-
- `hacklab org access` — manage who controls a company, and at what level.
|
|
133
|
-
Several people can be on one company's access list, at one of two roles:
|
|
134
|
-
**admin**, who can do everything (edit the profile, change this list, post
|
|
135
|
-
jobs), and **recruiter**, who can only reach `hacklab org jobs`. Bare `org
|
|
136
|
-
access` (or `org access list`) shows everyone and their role; `org access
|
|
137
|
-
grant <handle> [--role admin|recruiter]` adds someone (defaulting to admin —
|
|
138
|
-
re-granting an existing person changes their role); `org access revoke
|
|
139
|
-
<handle>` removes them — including yourself, though the last remaining
|
|
140
|
-
**admin** can't be removed or demoted. Only admins change the list; a
|
|
141
|
-
recruiter can stand down but nothing else. Use `--org <slug>` when you're on
|
|
142
|
-
more than one, and `--json` on any of them for agents.
|
|
143
|
-
- `hacklab org jobs` — your company's Job Shop listings. Bare `org jobs` (or
|
|
144
|
-
`org jobs list`) shows every listing you've posted and its status; `org jobs
|
|
145
|
-
view <id>` reads one; `org jobs post` creates one, interactively or from
|
|
146
|
-
flags (`--role`, `--description`, `--apply-url`, `--contact`, plus optional
|
|
147
|
-
`--company`, `--company-url`, `--salary`, `--work-style`, `--min-belt`);
|
|
148
|
-
`org jobs close <id>` takes a live one down early. A listing costs
|
|
149
|
-
**$1,000**, so `post` can't finish in the terminal — it creates the listing
|
|
150
|
-
and hands back a Stripe checkout link to open. Once that's paid we review it
|
|
151
|
-
before it goes live, and it runs for 30 days. Admins and recruiters both
|
|
152
|
-
reach all of this; `--json` on any subcommand for agents.
|
|
153
|
-
- `hacklab jobs` — browse the Job Shop. Bare `hacklab jobs` (or `jobs list`,
|
|
154
|
-
with `--limit 1-100`) lists what's hiring; `jobs view <id>` reads one listing
|
|
155
|
-
in full with its apply link. Read-only — posting is `hacklab org jobs post`.
|
|
156
|
-
`hacklab jo` resolves to `jobs`.
|
|
157
|
-
- `hacklab profile` — view and edit your own profile. Bare `profile` (or
|
|
158
|
-
`profile view`) shows it; `profile edit` is an org-style autosave editor;
|
|
159
|
-
`profile set <field> <value>` writes one field (`--clear` unsets, handles like
|
|
160
|
-
`x mattbratos` become full links); `profile set https://x.com/mattbratos`
|
|
161
|
-
picks the field from the host (x, youtube, instagram, goodreads); `profile
|
|
162
|
-
set readme --file profile.md` writes a long Markdown README without shell
|
|
163
|
-
quoting; `profile apply profile.yaml` writes many fields in one shot. Fields:
|
|
164
|
-
`name`, `bio`, `readme`, `website`, `blog`, `x`, `youtube`, `instagram`,
|
|
165
|
-
`goodreads`, `rss`, `open-to-work`.
|
|
166
|
-
Add `--json` to `view`/`set`/`apply` for machine-readable output.
|
|
167
|
-
- `hacklab hacker <username> --json` — read the rich agent profile including
|
|
168
|
-
links, counts, skills, recent projects, essays, and drops.
|
|
169
|
-
- `hacklab hackathon` — RSVP, team up, and submit for a hacklab hackathon. Each
|
|
170
|
-
event has a challenge mode — `open` (build anything, no theme or tracks),
|
|
171
|
-
`theme` (one subject everyone builds to), or `tracks` (teams each pick one
|
|
172
|
-
of several) — and the organizer may keep the theme/track list hidden from
|
|
173
|
-
participants until the hackathon starts.
|
|
174
|
-
`hackathon list [--past]` shows upcoming (or past) events; `hackathon view
|
|
175
|
-
<slug>` shows one event's phase, highlights its next deadline (RSVP
|
|
176
|
-
closes / teams lock / tracks lock / submissions due), and shows the
|
|
177
|
-
challenge — the theme/tracks if revealed, or a note that it's announced
|
|
178
|
-
when the hackathon starts; `hackathon rsvp <slug> [--token <t>]` RSVPs (an
|
|
179
|
-
invite link from the organizer also works if you're not on the invite
|
|
180
|
-
list); `hackathon invite <slug> --file <path>` or `--emails
|
|
181
|
-
a@b.com,c@d.com` sends invites and lists every rejected line; `hackathon
|
|
182
|
-
team create <slug> --name "X" [--summary S] [--max N] [--closed]` starts a
|
|
183
|
-
team, `team join <slug> <teamSlug>` requests to join one, `team
|
|
184
|
-
accept|reject <slug> <teamSlug> <handle>` decides a request, and `team list
|
|
185
|
-
<slug>` lists teams; `hackathon track <slug> <teamSlug> <trackSlug>` sets a
|
|
186
|
-
team's track (refused once the challenge is locked, or if this hackathon
|
|
187
|
-
has no tracks); `hackathon tracks <slug>` prints the theme or track list on
|
|
188
|
-
its own, honouring the same reveal rule; `hackathon submit <slug>
|
|
189
|
-
<teamSlug> --title T --description D [--repo/--video/--site/--track]`
|
|
190
|
-
submits a project; `hackathon export <slug> [--format csv|json] [--out
|
|
191
|
-
<path>]` downloads the participant list (this contains personal data —
|
|
192
|
-
handle exported files carefully). Every subcommand takes `--json` for
|
|
193
|
-
agents.
|
|
194
|
-
- `hacklab --version` / `hacklab --help`.
|
|
195
|
-
|
|
196
|
-
## Cursor usage
|
|
197
|
-
|
|
198
|
-
Cursor is the one tool whose local data can't give exact numbers. Without a key
|
|
199
|
-
the scanner estimates from Cursor's commit-tracking database (AI lines written
|
|
200
|
-
× 30, all attributed to today, so no real daily history). With a key it reads
|
|
201
|
-
exact per-event token counts and real dates from Cursor's API.
|
|
202
|
-
|
|
203
|
-
Supply a key three ways, highest priority first:
|
|
5
|
+
Requires Bun 1.4.2 or newer.
|
|
204
6
|
|
|
205
7
|
```sh
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
hacklab config cursor-api-key <key> # config file: persists
|
|
8
|
+
bun add --global hacklab@beta
|
|
9
|
+
hacklab --help
|
|
209
10
|
```
|
|
210
11
|
|
|
211
|
-
|
|
212
|
-
account email, which resolves independently of the key. **If your key is a team
|
|
213
|
-
key, set the email.** Cursor's usage endpoint returns every team member's events
|
|
214
|
-
otherwise, and all of it would land on your profile as your own.
|
|
215
|
-
|
|
216
|
-
If a key is set but Cursor rejects it, the scan says so and falls back to the
|
|
217
|
-
local estimate — it won't quietly hand you an estimate you think is exact.
|
|
218
|
-
|
|
219
|
-
## Prompt sync
|
|
220
|
-
|
|
221
|
-
`sync` can also track *how* you prompt, not just how many tokens you burned.
|
|
222
|
-
With it on, the same minutely tick that pushes your token counts also pushes
|
|
223
|
-
prompt metadata — so your profile shows your coding sessions, how many you run
|
|
224
|
-
at once, and your prompt counts, kept current rather than once a day. Every
|
|
225
|
-
full sync carries whatever is still outstanding too, so a machine with no
|
|
226
|
-
daemon gets its prompt activity up by running `hacklab sync` by hand. The daily
|
|
227
|
-
full sync also adds a histogram of your prompt lengths — every bar an exact
|
|
228
|
-
word count, and everything past the end of the axis as a list of length/count
|
|
229
|
-
pairs instead, so a tail of unusually long prompts keeps its real lengths
|
|
230
|
-
rather than piling into a final catch-all bar — and a prompt count per project.
|
|
231
|
-
Sources are local Claude Code transcripts (`~/.claude/projects`), GitHub Copilot
|
|
232
|
-
transcripts, and readable Antigravity CLI/IDE logs.
|
|
233
|
-
|
|
234
|
-
**Only what you actually typed counts.** A transcript records far more than
|
|
235
|
-
your prompts, and a lot of it is stored in the same shape: a background
|
|
236
|
-
subagent's report, the body of a skill that got invoked, the echo of a slash
|
|
237
|
-
command and its output, the marker left when you hit escape, bash-mode
|
|
238
|
-
commands, hook output, and the context Claude Code injects into a turn. Left
|
|
239
|
-
in, those were 39% of the "prompts" and 65% of the words on a typical machine —
|
|
240
|
-
one `/browse` looked like a 7,268-word prompt. The scan drops any entry the
|
|
241
|
-
harness flagged as its own, plus anything that *starts* with one of those
|
|
242
|
-
markers. Starts with, not contains: a real prompt that quotes
|
|
243
|
-
`<command-name>` while asking about it is still your prompt, and still counts.
|
|
244
|
-
|
|
245
|
-
For Antigravity, an explicit-user-input entry counts only when it contains a
|
|
246
|
-
nonempty `<USER_REQUEST>` body. That body is preserved verbatim, including blank
|
|
247
|
-
lines; surrounding metadata, model-setting changes, and artifact-comment prose
|
|
248
|
-
are excluded from both word counts and the `full`-tier sample. Empty request
|
|
249
|
-
bodies, including artifact approvals, do not count as prompts. `created_at` is
|
|
250
|
-
the preferred timestamp, with a valid `timestamp` as fallback. If neither is
|
|
251
|
-
valid, the prompt stays undated — no timestamp is inferred.
|
|
252
|
-
|
|
253
|
-
Nothing conversation-derived leaves your machine until you say so. The first
|
|
254
|
-
interactive `sync` asks and remembers the answer for the disclosed sources.
|
|
255
|
-
Adding IDE chats requires fresh consent from anyone whose previous answer
|
|
256
|
-
covered only Claude Code; an earlier `none` remains a refusal. Until you answer
|
|
257
|
-
again, unattended sync uploads tokens only. There are three tiers:
|
|
12
|
+
Sign in at [Hacklab](https://beta.hacklab.so/?onboarding=1) and copy the agent instructions. They include a one-hour credential scoped to your profile. Keep it private and pass it as `HACKLAB_ONBOARDING_TOKEN` in the upload command's environment.
|
|
258
13
|
|
|
259
|
-
|
|
260
|
-
| ------- | ------------------------ |
|
|
261
|
-
| `none` | token counts only — nothing prompt-related |
|
|
262
|
-
| `stats` | + prompt counts, word counts, timestamps and session ids, synced continuously, plus the length histogram and per-project counts. Your prompt text never leaves the machine |
|
|
263
|
-
| `full` | + a rolling sample of your most recent prompts (≤20k chars) sent with the daily sync, used only to estimate a technical-level score and then discarded server-side. Never stored |
|
|
14
|
+
## Preview a profile
|
|
264
15
|
|
|
265
|
-
|
|
266
|
-
remote, so a project count only lands on a project you've already added to
|
|
267
|
-
hacklab. Missing remotes skip **project attribution**, not the aggregate prompt
|
|
268
|
-
statistics or the separately consented text sample.
|
|
269
|
-
|
|
270
|
-
Answer up front, without the prompt — the agent-friendly path:
|
|
271
|
-
|
|
272
|
-
```sh
|
|
273
|
-
hacklab sync --share-prompt-sync # metadata only
|
|
274
|
-
hacklab sync --share-prompt-sync=full # metadata + prompt sample
|
|
275
|
-
hacklab sync --no-share-prompt-sync # refuse
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
Change or revoke it any time:
|
|
16
|
+
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.
|
|
279
17
|
|
|
280
18
|
```sh
|
|
281
|
-
hacklab
|
|
282
|
-
hacklab config # show the current tier
|
|
19
|
+
hacklab onboard --file /path/to/profile.json
|
|
283
20
|
```
|
|
284
21
|
|
|
285
|
-
|
|
286
|
-
never answered uploads token counts only.
|
|
287
|
-
|
|
288
|
-
## IDE and agent-chat coverage
|
|
289
|
-
|
|
290
|
-
**Copilot and Antigravity are token harnesses**, like Claude Code and Codex.
|
|
291
|
-
They contribute to total tokens, daily usage, model breakdowns, rank, and arena.
|
|
292
|
-
A Claude model used through Copilot counts under Copilot, not Claude Code.
|
|
293
|
-
Model totals are another view of the same tokens, never an extra addition.
|
|
294
|
-
No tokens are estimated from prompts, quota, subscription spend, or context size.
|
|
295
|
-
|
|
296
|
-
| Source | Token usage | Consented prompts |
|
|
297
|
-
| ------ | ----------- | ----------------- |
|
|
298
|
-
| GitHub Copilot CLI | Persisted `session.shutdown.modelMetrics` in `${COPILOT_HOME:-~/.copilot}/session-state/<session-id>/events.jsonl`; available after shutdown. Supported local trace exports can supply completed calls before the next shutdown. | User messages in the same events file; synthetic user events excluded. |
|
|
299
|
-
| GitHub Copilot in VS Code Stable / Insiders | Native Copilot `chat` spans in `User/globalStorage/github.copilot-chat/agent-traces.db` (including user profiles) or configured local OTel JSONL exports. Telemetry must already be enabled; ordinary chat transcripts do not contain usage. | Agent JSONL in `User/workspaceStorage/<workspace>/github.copilot-chat/transcripts/`; only the Copilot producer and user messages count. |
|
|
300
|
-
| Antigravity CLI / GUI | `~/.gemini/{antigravity-cli,antigravity,antigravity-ide}/conversations/<UUID>.db`, where the supported SQLite `steps.metadata` usage schema exists. | Readable `brain/<UUID>/.system_generated/logs/transcript_full.jsonl` or `transcript.jsonl` under those roots, when present in the explicit-user-input format. Full transcript preferred; copies not counted together. |
|
|
301
|
-
| Android Studio's built-in Gemini | Not supported: no reliable local, attributable usage schema was established. API/account dashboards can include other clients and cannot safely be counted as Studio usage. | Not supported. Saved Prompt Library entries are templates, not chat activity. |
|
|
302
|
-
|
|
303
|
-
### Accounting and deduplication
|
|
304
|
-
|
|
305
|
-
- Copilot shutdown counters are cumulative across resumed sessions. Count the
|
|
306
|
-
first snapshot and then only positive growth, dated to each shutdown.
|
|
307
|
-
`inputTokens` includes cache reads/writes; `outputTokens` includes reasoning.
|
|
308
|
-
Never add nested `agentMetrics` to the same root totals.
|
|
309
|
-
- Copilot trace exports count only completed `chat` calls with an identifiable
|
|
310
|
-
native Copilot owning agent. Root `invoke_agent` totals, metrics, and duplicate
|
|
311
|
-
log events are not extra usage. File/SQLite copies share trace/span IDs and are
|
|
312
|
-
deduplicated. Calls already covered by a session/model shutdown are suppressed;
|
|
313
|
-
later resumed calls remain until the next shutdown covers them.
|
|
314
|
-
- Claude/Codex agent delegates are excluded from Copilot telemetry so their native
|
|
315
|
-
scanners own that usage. This is about the **agent**, not the model: BYOK Claude
|
|
316
|
-
calls under a native Copilot agent still count as Copilot.
|
|
317
|
-
- Antigravity counts uncached input + cached input + total output. Thinking and
|
|
318
|
-
visible-answer fields are output subsets, not additions. Response IDs identify
|
|
319
|
-
duplicate generations; conversation UUID + step index is the fallback when no
|
|
320
|
-
response ID exists. Numeric model enums remain `antigravity-model-<id>` rather
|
|
321
|
-
than being assigned an unverified model name.
|
|
322
|
-
|
|
323
|
-
### Local telemetry and coverage limits
|
|
324
|
-
|
|
325
|
-
Hacklab never enables telemetry or overwrites harness settings. For VS Code,
|
|
326
|
-
`github.copilot.chat.otel.dbSpanExporter.enabled` enables the local trace DB;
|
|
327
|
-
alternatively use the documented OTel file exporter with an absolute
|
|
328
|
-
`github.copilot.chat.otel.outfile`. The scanner reads that setting from default
|
|
329
|
-
user/profile JSONC files, `${COPILOT_HOME:-~/.copilot}/otel/**/*.jsonl`, and
|
|
330
|
-
`COPILOT_OTEL_FILE_EXPORTER_PATH` when supplied to Hacklab. Prompt/response content
|
|
331
|
-
capture is **not needed**. Broken exporter records containing only `{}` cannot
|
|
332
|
-
provide usage; prefer the local trace DB on affected VS Code versions.
|
|
333
|
-
JSONL parsing supports the direct ReadableSpan form emitted by VS Code. The
|
|
334
|
-
standalone CLI's exact exporter serialization is not a documented stable
|
|
335
|
-
contract; unrecognized record forms are ignored, not interpreted as estimates.
|
|
336
|
-
|
|
337
|
-
VS Code roots are `%APPDATA%/Code[ - Insiders]` on Windows,
|
|
338
|
-
`~/Library/Application Support/Code[ - Insiders]` on macOS, and
|
|
339
|
-
`${XDG_CONFIG_HOME:-~/.config}/Code[ - Insiders]` on Linux. Custom user-data roots,
|
|
340
|
-
remote hosts, other Copilot IDEs, and generic chat exports are not swept.
|
|
341
|
-
Trace calls wait for an identifiable completed owning-agent span. Unknown
|
|
342
|
-
schemas/owners are not guessed. The read-only SQLite reader observes the main
|
|
343
|
-
database; recent WAL-only writes become visible after the harness checkpoints.
|
|
344
|
-
Antigravity's DB schema is reverse-engineered, not a vendor stability guarantee;
|
|
345
|
-
opaque `.pb` histories and unsupported transcript formats remain unreadable.
|
|
346
|
-
|
|
347
|
-
Only retained history is available. VS Code prompt transcripts normally retain
|
|
348
|
-
20 sessions per workspace plus active sessions; trace DB retention is also
|
|
349
|
-
bounded. Missing prompt timestamps allow a histogram and `full`-tier sample,
|
|
350
|
-
but **never** dated activity. Token records need their own real timestamp.
|
|
351
|
-
The minutely tick fingerprints snapshot sources and replaces changed aggregates,
|
|
352
|
-
not adds the whole snapshot again. Full syncs rebuild the same sources. Prompt
|
|
353
|
-
sharing remains separately consented; incremental state contains no prompt text.
|
|
354
|
-
|
|
355
|
-
Storage evidence:
|
|
356
|
-
[Copilot usage schema](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/session-events.ts),
|
|
357
|
-
[shutdown/cache semantics](https://ccusage.com/guide/copilot/),
|
|
358
|
-
[VS Code telemetry](https://code.visualstudio.com/docs/agents/guides/monitoring-agents),
|
|
359
|
-
[VS Code trace DB](https://github.com/microsoft/vscode/blob/main/extensions/copilot/src/platform/otel/node/sqlite/otelSqliteStore.ts),
|
|
360
|
-
[VS Code transcript writer](https://github.com/microsoft/vscode/blob/main/extensions/copilot/src/extension/chat/vscode-node/sessionTranscriptService.ts),
|
|
361
|
-
[Antigravity DB metadata](https://github.com/Eneasf/antigravity-token-dashboard/blob/main/docs/TELEMETRY_SPEC.md),
|
|
362
|
-
[CLI/GUI schema audit](https://github.com/mjacobs/agy-reader/blob/main/COMPATIBILITY.md),
|
|
363
|
-
[readable Antigravity exporter](https://github.com/mehdawimohamed/antigravity-conversation-exporter/blob/main/export_chat.py),
|
|
364
|
-
[Android Studio API-key usage](https://developer.android.com/studio/gemini/add-api-key).
|
|
365
|
-
|
|
366
|
-
## Choosing a backend
|
|
22
|
+
This validates and previews locally without making a network request.
|
|
367
23
|
|
|
368
|
-
|
|
24
|
+
## Upload after approval
|
|
369
25
|
|
|
370
|
-
|
|
371
|
-
2. **`HACKLAB_APP_URL`** set in the environment;
|
|
372
|
-
3. **the backend you logged into** (saved in the session file);
|
|
373
|
-
4. **production** (`https://hacklab.so`) by default.
|
|
26
|
+
Review the exact bio and links first. Agents must ask the user for approval before using `--confirm-upload`.
|
|
374
27
|
|
|
375
|
-
```
|
|
376
|
-
hacklab
|
|
377
|
-
hacklab drop "hi" --env dev # the override applies to every command, not just login
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
`--env` accepts the named backends `production` and `development` (any
|
|
381
|
-
unambiguous abbreviation — `prod`, `dev` — plus the aliases `local`/`localhost`
|
|
382
|
-
for development). Under the hood it just sets `HACKLAB_APP_URL` for that run, so
|
|
383
|
-
an explicit `--env` overrides an inherited `HACKLAB_APP_URL`. To target any other
|
|
384
|
-
backend, set `HACKLAB_APP_URL` to its base URL directly:
|
|
385
|
-
|
|
386
|
-
```bash
|
|
387
|
-
HACKLAB_APP_URL=https://your-backend.example.com hacklab login
|
|
28
|
+
```sh
|
|
29
|
+
hacklab onboard --file /path/to/profile.json --confirm-upload
|
|
388
30
|
```
|
|
389
31
|
|
|
390
|
-
|
|
391
|
-
you logged into. Overriding `--env` (or `HACKLAB_APP_URL`) to a backend you
|
|
392
|
-
haven't authenticated against will 401 until you `hacklab login` there.
|
|
393
|
-
`hacklab whoami` shows the effective backend and warns when it differs from where
|
|
394
|
-
you logged in.
|
|
395
|
-
|
|
396
|
-
## Environment
|
|
397
|
-
|
|
398
|
-
- `HACKLAB_APP_URL` — explicit app base URL (read from the real environment; the
|
|
399
|
-
CLI does not load any `.env` file). A command-line `--env` sets this for the
|
|
400
|
-
run, so `--env` wins over an inherited value. See **Choosing a backend** for
|
|
401
|
-
the full precedence.
|
|
402
|
-
- `HACKLAB_SESSION_PATH` — custom path for the session file
|
|
403
|
-
(default `~/.hacklab/session.json`).
|
|
404
|
-
- `HACKLAB_DEV` — set to `1` to mark this as a developer run. `hacklab whoami`
|
|
405
|
-
then always prints the backend host, including production (which it otherwise
|
|
406
|
-
leaves out as noise). `0`, `false`, empty and unset are off.
|
|
407
|
-
- `HACKLAB_NO_UPDATE_CHECK` — set to any value to disable the once-a-day
|
|
408
|
-
"newer version available" nudge. The check is already skipped for piped /
|
|
409
|
-
scripted / `--json` runs; this turns it off for interactive runs too.
|
|
410
|
-
- `POSTHOG_API_KEY` / `POSTHOG_HOST` — override the built-in (public) analytics
|
|
411
|
-
project/host. Set `HACKLAB_NO_TELEMETRY` (or the cross-tool `DO_NOT_TRACK`) to
|
|
412
|
-
disable anonymous usage analytics entirely.
|
|
413
|
-
|
|
414
|
-
## Signing in
|
|
415
|
-
|
|
416
|
-
`hacklab login` uses a **device flow**, everywhere (desktop or headless): it
|
|
417
|
-
prints a short code and a URL like `https://hacklab.so/cli/login?code=857F-61CE`.
|
|
418
|
-
Open that on any device with a browser; the code is prefilled. Approve the login —
|
|
419
|
-
you sign in to Hacklab there with GitHub
|
|
420
|
-
or Google, and if you don't have an account yet you create one on the spot. The
|
|
421
|
-
terminal picks up the session as soon as you approve. No local server, no port
|
|
422
|
-
forwarding, no localhost/app URL.
|
|
32
|
+
Only the bio and social links are uploaded. The CLI does not scan projects or upload source code, local paths, session transcripts, or usage statistics.
|
|
423
33
|
|
|
424
34
|
## Local development
|
|
425
35
|
|
|
426
|
-
|
|
36
|
+
From the Hacklab repository:
|
|
427
37
|
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
`pnpm dev` is `tsx src/index.ts`, so it defaults to the **production** backend
|
|
436
|
-
like the published package. Point it elsewhere per-command with `--env
|
|
437
|
-
development` (localhost) or `HACKLAB_APP_URL=<url>`. Use a throwaway session to
|
|
438
|
-
avoid touching a real account:
|
|
439
|
-
|
|
440
|
-
```bash
|
|
441
|
-
HACKLAB_SESSION_PATH=/tmp/hl-test.json pnpm dev login --env development
|
|
38
|
+
```sh
|
|
39
|
+
bun install --frozen-lockfile
|
|
40
|
+
bunx turbo run build --filter=hacklab
|
|
41
|
+
cd packages/cli
|
|
42
|
+
bun link
|
|
43
|
+
hacklab onboard --file /path/to/profile.json --url http://localhost:3000
|
|
442
44
|
```
|
|
443
45
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
The CLI sends anonymous usage analytics to a dedicated, public PostHog project
|
|
447
|
-
(the write-only project key ships in the published package). It is opt-out: set
|
|
448
|
-
`HACKLAB_NO_TELEMETRY=1` or the cross-tool `DO_NOT_TRACK=1` to disable it. See
|
|
449
|
-
`src/posthog.ts`.
|
|
450
|
-
|
|
451
|
-
## License
|
|
452
|
-
|
|
453
|
-
[MIT](./LICENSE) © Homebrew Hackers Club inc
|
|
46
|
+
Use instructions and a credential from the local onboarding dialog. Credentials are bound to their issuing origin and cannot be used interchangeably between localhost and production.
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,133 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { parseArgs } from "util";
|
|
6
|
+
// package.json
|
|
7
|
+
var version = "26.921.1";
|
|
8
|
+
|
|
9
|
+
// src/onboard.ts
|
|
10
|
+
function normalizeOrigin(value) {
|
|
11
|
+
const url = new URL(value);
|
|
12
|
+
const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
13
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== "/" || url.protocol !== "https:" && !(local && url.protocol === "http:"))
|
|
14
|
+
throw new Error("Use an HTTPS Hacklab origin, or HTTP localhost for development.");
|
|
15
|
+
return url.origin;
|
|
16
|
+
}
|
|
17
|
+
async function onboard(options) {
|
|
18
|
+
const origin = normalizeOrigin(options.origin);
|
|
19
|
+
const file = Bun.file(options.file);
|
|
20
|
+
if (file.size > 8192)
|
|
21
|
+
throw new Error("Profile must be smaller than 8 KB.");
|
|
22
|
+
const profile = await file.json();
|
|
23
|
+
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\u2013160 characters) and a socials object, with no other fields.");
|
|
25
|
+
const hosts = {
|
|
26
|
+
github: ["github.com"],
|
|
27
|
+
linkedin: ["linkedin.com"],
|
|
28
|
+
x: ["x.com", "twitter.com"],
|
|
29
|
+
instagram: ["instagram.com"],
|
|
30
|
+
youtube: ["youtube.com"],
|
|
31
|
+
huggingface: ["huggingface.co"],
|
|
32
|
+
producthunt: ["producthunt.com"],
|
|
33
|
+
goodreads: ["goodreads.com"]
|
|
34
|
+
};
|
|
35
|
+
for (const [platform, value] of Object.entries(profile.socials)) {
|
|
36
|
+
if (!Object.hasOwn(hosts, platform))
|
|
37
|
+
throw new Error(`Unsupported social platform: ${platform}`);
|
|
38
|
+
if (value === null)
|
|
39
|
+
continue;
|
|
40
|
+
if (typeof value !== "string" || value.length > 500)
|
|
41
|
+
throw new Error(`Invalid ${platform} profile URL.`);
|
|
42
|
+
const url = new URL(value);
|
|
43
|
+
if (url.protocol !== "https:" || url.username || url.password || url.port || url.pathname === "/" || !hosts[platform].includes(url.hostname.replace(/^www\./, ""))) {
|
|
44
|
+
throw new Error(`Use an HTTPS ${platform} profile URL.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const payload = JSON.stringify({ bio: profile.bio.trim(), socials: profile.socials }, null, 2);
|
|
48
|
+
console.log(`Hacklab profile preview for ${origin}:
|
|
49
|
+
${payload}`);
|
|
50
|
+
if (!options.confirmUpload) {
|
|
51
|
+
console.log("Nothing uploaded. Ask the user to approve this exact profile, then run again with --confirm-upload.");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const token = Bun.env.HACKLAB_ONBOARDING_TOKEN;
|
|
55
|
+
if (!token)
|
|
56
|
+
throw new Error("Set HACKLAB_ONBOARDING_TOKEN from the signed-in onboarding dialog.");
|
|
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
|
+
}
|
|
66
|
+
const response = await fetch(`${origin}/api/onboarding`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
69
|
+
body: payload,
|
|
70
|
+
redirect: "error",
|
|
71
|
+
signal: AbortSignal.timeout(30000)
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`Hacklab rejected the profile (HTTP ${response.status}). Check the profile and token before retrying.`);
|
|
75
|
+
}
|
|
76
|
+
const result = await response.json();
|
|
77
|
+
if (result.ok !== true)
|
|
78
|
+
throw new Error("Hacklab did not confirm the update. Check the website before retrying.");
|
|
79
|
+
console.log("Your Hacklab bio and social links were saved.");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/index.ts
|
|
83
|
+
var help = `Hacklab CLI
|
|
84
|
+
|
|
85
|
+
Usage:
|
|
86
|
+
hacklab onboard --file <profile.json> [--url <origin>] [--confirm-upload]
|
|
87
|
+
hacklab --help
|
|
88
|
+
hacklab --version
|
|
89
|
+
|
|
90
|
+
Onboarding:
|
|
91
|
+
--file Local JSON containing bio and socials
|
|
92
|
+
--url Hacklab origin (default: https://beta.hacklab.so)
|
|
93
|
+
--confirm-upload Upload only after the user approves the exact profile
|
|
94
|
+
|
|
95
|
+
Without --confirm-upload, onboard validates and previews locally without uploading.
|
|
96
|
+
Uploads require HACKLAB_ONBOARDING_TOKEN from the signed-in onboarding dialog.
|
|
97
|
+
Bun 1.4.2 or newer is required.`;
|
|
98
|
+
try {
|
|
99
|
+
const { values, positionals } = parseArgs({
|
|
100
|
+
args: Bun.argv.slice(2),
|
|
101
|
+
options: {
|
|
102
|
+
help: { type: "boolean", short: "h" },
|
|
103
|
+
version: { type: "boolean", short: "v" },
|
|
104
|
+
file: { type: "string" },
|
|
105
|
+
url: { type: "string" },
|
|
106
|
+
"confirm-upload": { type: "boolean" }
|
|
107
|
+
},
|
|
108
|
+
allowPositionals: true
|
|
109
|
+
});
|
|
110
|
+
if (positionals.length > 1 || positionals[0] && positionals[0] !== "onboard") {
|
|
111
|
+
throw new Error("Unknown command. Use hacklab onboard or hacklab --help.");
|
|
112
|
+
}
|
|
113
|
+
if (values.help) {
|
|
114
|
+
console.log(help);
|
|
115
|
+
} else if (values.version) {
|
|
116
|
+
console.log(version);
|
|
117
|
+
} else if (positionals[0] === "onboard") {
|
|
118
|
+
if (!values.file)
|
|
119
|
+
throw new Error("Pass --file with the local profile JSON to preview or upload.");
|
|
120
|
+
await onboard({
|
|
121
|
+
file: values.file,
|
|
122
|
+
origin: values.url ?? "https://beta.hacklab.so",
|
|
123
|
+
confirmUpload: values["confirm-upload"] ?? false
|
|
124
|
+
});
|
|
125
|
+
} else if (values.file || values.url || values["confirm-upload"]) {
|
|
126
|
+
throw new Error("Profile options require the onboard command.");
|
|
127
|
+
} else {
|
|
128
|
+
console.log(help);
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(error instanceof Error ? error.message : "Could not run Hacklab CLI.");
|
|
132
|
+
process.exitCode = 1;
|
|
17
133
|
}
|
|
18
|
-
// Defer the real CLI (and its Node 20+ import graph) until after the guard.
|
|
19
|
-
import('./cli.js').catch((err) => {
|
|
20
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
21
|
-
process.exit(1);
|
|
22
|
-
});
|
|
23
|
-
export {};
|
|
24
|
-
//# sourceMappingURL=index.js.map
|