hacklab 0.3.0 → 0.4.0
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 +60 -91
- package/dist/belt.d.ts +25 -0
- package/dist/belt.d.ts.map +1 -0
- package/dist/belt.js +113 -0
- package/dist/belt.js.map +1 -0
- package/dist/commands/{signup.d.ts → join.d.ts} +4 -4
- package/dist/commands/join.d.ts.map +1 -0
- package/dist/commands/{signup.js → join.js} +109 -42
- package/dist/commands/join.js.map +1 -0
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/share-card.d.ts +3 -0
- package/dist/share-card.d.ts.map +1 -1
- package/dist/share-card.js +327 -290
- package/dist/share-card.js.map +1 -1
- package/dist/share.d.ts +16 -0
- package/dist/share.d.ts.map +1 -0
- package/dist/share.js +59 -0
- package/dist/share.js.map +1 -0
- package/package.json +1 -1
- package/dist/commands/claim.d.ts +0 -2
- package/dist/commands/claim.d.ts.map +0 -1
- package/dist/commands/claim.js +0 -534
- package/dist/commands/claim.js.map +0 -1
- package/dist/commands/dojo-init.d.ts +0 -2
- package/dist/commands/dojo-init.d.ts.map +0 -1
- package/dist/commands/dojo-init.js +0 -75
- package/dist/commands/dojo-init.js.map +0 -1
- package/dist/commands/signup.d.ts.map +0 -1
- package/dist/commands/signup.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,107 +1,76 @@
|
|
|
1
|
-
#
|
|
1
|
+
# hacklab
|
|
2
2
|
|
|
3
|
-
hacklab
|
|
3
|
+
The terminal-native way to join [Hacklab](https://hacklab.so) — a social network
|
|
4
|
+
for AI-native hackers. Scan your local AI token usage, see where you rank, claim
|
|
5
|
+
your profile with GitHub.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Install & join
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- `hacklab whoami` - show current logged in user.
|
|
9
|
-
- `hacklab init` - create or initialize the current repo as your lab repository.
|
|
10
|
-
- `hacklab brag` - read `.hacklab/project.yaml`, upload local screenshots, and sync the project to Hacklab.
|
|
11
|
-
- `hacklab new` - interactive content creation (`essay`, `guide`, `course`, `research paper`, `project`).
|
|
12
|
-
- `hacklab publish` - push the current lab repo to github.
|
|
13
|
-
- `hacklab explore` - open hacklab discovery in browser.
|
|
14
|
-
- `hacklab admin waitlist` - admin-only waitlist inspection (visible only for DEV/GOD accounts).
|
|
15
|
-
|
|
16
|
-
## repository layout
|
|
17
|
-
|
|
18
|
-
`hacklab init` creates:
|
|
19
|
-
|
|
20
|
-
- `.hacklab/config.json`
|
|
21
|
-
- `.hacklab/README.mdx`
|
|
22
|
-
- `assets/`
|
|
23
|
-
- `home.mdx`
|
|
24
|
-
- `readme.mdx`
|
|
25
|
-
- `profile/`
|
|
26
|
-
- `profile/bio.mdx`
|
|
27
|
-
- `profile/cv.mdx`
|
|
28
|
-
- `projects/`
|
|
29
|
-
- `guides/`
|
|
30
|
-
- `courses/`
|
|
31
|
-
- `essays/`
|
|
32
|
-
- `research/`
|
|
33
|
-
- `docs/profile.mdx`
|
|
34
|
-
- `docs/content-model.mdx`
|
|
35
|
-
- `docs/frontmatter.mdx`
|
|
36
|
-
|
|
37
|
-
it also creates sample `.mdx` content in each content directory.
|
|
38
|
-
|
|
39
|
-
identity fields in `.hacklab/config.json`:
|
|
40
|
-
|
|
41
|
-
- `labId` (optional)
|
|
42
|
-
- `labSlug`
|
|
43
|
-
- `labName`
|
|
44
|
-
- `owner`
|
|
45
|
-
- `workspaceType`
|
|
46
|
-
|
|
47
|
-
## local usage
|
|
48
|
-
|
|
49
|
-
from repo root:
|
|
9
|
+
One command:
|
|
50
10
|
|
|
51
11
|
```bash
|
|
52
|
-
|
|
53
|
-
node packages/cli/dist/index.js login --token <cli-access-token>
|
|
54
|
-
node packages/cli/dist/index.js login --as human
|
|
55
|
-
node packages/cli/dist/index.js login --as agent
|
|
56
|
-
node packages/cli/dist/index.js brag
|
|
57
|
-
node packages/cli/dist/index.js brag path/to/project.yaml
|
|
58
|
-
node packages/cli/dist/index.js init --name matts-lab
|
|
59
|
-
node packages/cli/dist/index.js new
|
|
60
|
-
node packages/cli/dist/index.js new guide api-design-primer
|
|
61
|
-
node packages/cli/dist/index.js new research-paper llm-evals-2026
|
|
62
|
-
node packages/cli/dist/index.js publish
|
|
63
|
-
node packages/cli/dist/index.js explore
|
|
12
|
+
curl hacklab.so/install.sh | sh
|
|
64
13
|
```
|
|
65
14
|
|
|
66
|
-
|
|
15
|
+
It checks for Node 20+ and runs `npx hacklab@latest join`. If you have Node
|
|
16
|
+
already (incl. via a version manager), you can skip the script:
|
|
67
17
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
credentials are stored at `~/.hacklab/session.json`.
|
|
18
|
+
```bash
|
|
19
|
+
npx hacklab@latest join
|
|
20
|
+
```
|
|
72
21
|
|
|
73
|
-
##
|
|
22
|
+
## The join ritual
|
|
74
23
|
|
|
75
|
-
|
|
24
|
+
```
|
|
25
|
+
scan local AI usage → see your rank → pick a username → sign in with GitHub → claim
|
|
26
|
+
```
|
|
76
27
|
|
|
77
|
-
|
|
28
|
+
1. **Scan** — reads your local AI token usage from Claude Code, Codex, Cursor,
|
|
29
|
+
OpenClaw, Hermes, and OpenCode. Nothing leaves your machine yet.
|
|
30
|
+
2. **Rank** — shows the rank that usage would hold (`you'd be #15 of N`), with no
|
|
31
|
+
account required.
|
|
32
|
+
3. **Username** — pick your Hacklab handle (checked for availability live).
|
|
33
|
+
4. **GitHub** — sign in to authenticate and link your profile + repos.
|
|
34
|
+
5. **Claim** — your profile goes live at `hacklab.so/<username>`. Your usage
|
|
35
|
+
uploads in the background while you get a shareable stats card.
|
|
36
|
+
6. **Share** — a stats thumbnail (belt, level, rank, token breakdown) is rendered
|
|
37
|
+
locally, copied to your clipboard, and you can post it to X in one keystroke.
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
- `hacklab join` — the join ritual above. Running bare `hacklab` does this for
|
|
42
|
+
new users (and `sync` for returning ones).
|
|
43
|
+
- `hacklab sync` — re-scan local AI usage and sync it to your profile.
|
|
44
|
+
- `hacklab whoami` — show who you're logged in as.
|
|
45
|
+
- `hacklab drop "message"` — post a drop to your feed (`-u <url>` to attach a link).
|
|
46
|
+
- `hacklab login` — re-authenticate with GitHub.
|
|
47
|
+
- `hacklab exam [--pyro|--hacker]` — run your belt exam (token usage + GitHub code).
|
|
48
|
+
- `hacklab scan` — build your full profile (skills, repos, blog).
|
|
49
|
+
- `hacklab config <key> <value>` — set config (`cursor-api-key`, `cursor-email`).
|
|
50
|
+
- `hacklab brag [path]` — sync a `.hacklab/project.yaml` to your profile.
|
|
51
|
+
- `hacklab --version` / `hacklab --help`.
|
|
52
|
+
|
|
53
|
+
## Environment
|
|
54
|
+
|
|
55
|
+
- `HACKLAB_APP_URL` — app base URL (default `http://localhost:3000`).
|
|
56
|
+
- `HACKLAB_SESSION_PATH` — custom path for the session file
|
|
57
|
+
(default `~/.hacklab/session.json`).
|
|
58
|
+
|
|
59
|
+
## Local development
|
|
60
|
+
|
|
61
|
+
From the repo root, run the CLI from source against a local app:
|
|
78
62
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- `liveUrl` or `url`
|
|
85
|
-
- `publishedAt`, `published_at`, or `date`
|
|
86
|
-
- `content` - inline markdown
|
|
87
|
-
- `contentFile` - relative path to markdown content
|
|
88
|
-
- `screenshots` - up to 5 entries, each either a URL, a relative file path, or `{ path, caption }` / `{ url, caption }`
|
|
63
|
+
```bash
|
|
64
|
+
HACKLAB_APP_URL=http://localhost:3000 \
|
|
65
|
+
HACKLAB_SESSION_PATH=/tmp/hl-test.json \
|
|
66
|
+
pnpm --filter hacklab dev join
|
|
67
|
+
```
|
|
89
68
|
|
|
90
|
-
|
|
69
|
+
`pnpm --filter hacklab dev <command>` runs `src/index.ts` via tsx (no build).
|
|
70
|
+
`pnpm --filter hacklab build` compiles to `dist/`.
|
|
91
71
|
|
|
92
|
-
|
|
72
|
+
To reset a test account so its GitHub identity can join fresh:
|
|
93
73
|
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
description: Fast deployment previews for side projects.
|
|
97
|
-
tags:
|
|
98
|
-
- next.js
|
|
99
|
-
- postgres
|
|
100
|
-
repo: https://github.com/acme/shipyard
|
|
101
|
-
url: https://shipyard.dev
|
|
102
|
-
contentFile: README.md
|
|
103
|
-
screenshots:
|
|
104
|
-
- screenshots/home.png
|
|
105
|
-
- path: screenshots/dashboard.jpg
|
|
106
|
-
caption: Dashboard
|
|
74
|
+
```bash
|
|
75
|
+
pnpm --filter @hacklab/db delete-account <handle-or-github-username>
|
|
107
76
|
```
|
package/dist/belt.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type BeltColor = 'white' | 'orange' | 'red' | 'blue' | 'cyan' | 'yellow' | 'lime' | 'green' | 'pink' | 'purple' | 'black';
|
|
2
|
+
export type Title = 'gaijin' | 'deshi' | 'ronin' | 'shinobi' | 'samurai' | 'senpai' | 'tatsujin' | 'kensei' | 'oni' | 'ryujin';
|
|
3
|
+
export type DanTitle = 'shodan' | 'nidan' | 'sandan' | 'yondan' | 'godan' | 'rokudan' | 'nanadan' | 'hachidan' | 'kudan' | 'judan';
|
|
4
|
+
export type GrandMasterTitle = 'shihan' | 'hanshi' | 'meijin' | 'oyama';
|
|
5
|
+
/** Convert raw token count to Pyro XP. 1,000 tokens = 1 XP. */
|
|
6
|
+
export declare function tokensToXp(tokens: number): number;
|
|
7
|
+
/** Convert total XP to level (fractional). */
|
|
8
|
+
export declare function xpToLevel(xp: number): number;
|
|
9
|
+
/** Convert level to minimum XP required. */
|
|
10
|
+
export declare function levelToMinXp(level: number): number;
|
|
11
|
+
/** Get the integer level (floor of fractional level). */
|
|
12
|
+
export declare function getLevel(xp: number): number;
|
|
13
|
+
/** Get belt color for a given level. */
|
|
14
|
+
export declare function getBeltColor(level: number): BeltColor;
|
|
15
|
+
/** Get title for a given level. */
|
|
16
|
+
export declare function getTitle(level: number): Title | DanTitle | GrandMasterTitle;
|
|
17
|
+
export type BeltProgress = {
|
|
18
|
+
level: number;
|
|
19
|
+
title: Title | DanTitle | GrandMasterTitle;
|
|
20
|
+
beltColor: BeltColor;
|
|
21
|
+
progressPercent: number;
|
|
22
|
+
};
|
|
23
|
+
/** Belt info for a raw token total (pyro-only — correct for a fresh join). */
|
|
24
|
+
export declare function beltForTokens(tokensTotal: number): BeltProgress;
|
|
25
|
+
//# sourceMappingURL=belt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"belt.d.ts","sourceRoot":"","sources":["../src/belt.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,QAAQ,GACR,KAAK,GACL,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,OAAO,GACP,MAAM,GACN,QAAQ,GACR,OAAO,CAAA;AAEX,MAAM,MAAM,KAAK,GACb,QAAQ,GACR,OAAO,GACP,OAAO,GACP,SAAS,GACT,SAAS,GACT,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,KAAK,GACL,QAAQ,CAAA;AAEZ,MAAM,MAAM,QAAQ,GAChB,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,SAAS,GACT,SAAS,GACT,UAAU,GACV,OAAO,GACP,OAAO,CAAA;AAEX,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;AAqCvE,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAM5C;AAED,4CAA4C;AAC5C,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED,yDAAyD;AACzD,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAMrD;AAED,mCAAmC;AACnC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,gBAAgB,CAkB3E;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,KAAK,GAAG,QAAQ,GAAG,gBAAgB,CAAA;IAC1C,SAAS,EAAE,SAAS,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,CAgB/D"}
|
package/dist/belt.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Belt / level math — ported verbatim from @hacklab/db `levels.ts` so the CLI
|
|
2
|
+
// can render the share card instantly (locally) without waiting on the server.
|
|
3
|
+
// The CLI is a standalone published package and must not pull in @hacklab/db
|
|
4
|
+
// (drizzle/pg/etc.), so we copy the small, stable curve here.
|
|
5
|
+
//
|
|
6
|
+
// One curve: xp(level) = 23 × level^3.32 up to level 100, exponential above.
|
|
7
|
+
// Pyro XP = tokens / 1000. For a brand-new join, hacker + mason XP are 0, so
|
|
8
|
+
// belt XP == pyro XP — which is exactly what the server computes right after
|
|
9
|
+
// the first sync. Kept in sync via belt.test.ts (anchored to the known points).
|
|
10
|
+
const K = 3.32;
|
|
11
|
+
const A = 23;
|
|
12
|
+
const LEVEL_100_XP = 100_000_000; // 100M XP
|
|
13
|
+
const BELT_TIERS = [
|
|
14
|
+
{ minLevel: 0, color: 'white', title: 'gaijin' },
|
|
15
|
+
{ minLevel: 10, color: 'orange', title: 'deshi' },
|
|
16
|
+
{ minLevel: 20, color: 'red', title: 'ronin' },
|
|
17
|
+
{ minLevel: 30, color: 'blue', title: 'shinobi' },
|
|
18
|
+
{ minLevel: 40, color: 'cyan', title: 'samurai' },
|
|
19
|
+
{ minLevel: 50, color: 'yellow', title: 'senpai' },
|
|
20
|
+
{ minLevel: 60, color: 'lime', title: 'tatsujin' },
|
|
21
|
+
{ minLevel: 70, color: 'green', title: 'kensei' },
|
|
22
|
+
{ minLevel: 80, color: 'pink', title: 'oni' },
|
|
23
|
+
{ minLevel: 90, color: 'purple', title: 'ryujin' },
|
|
24
|
+
];
|
|
25
|
+
const DAN_TITLES = [
|
|
26
|
+
'shodan',
|
|
27
|
+
'nidan',
|
|
28
|
+
'sandan',
|
|
29
|
+
'yondan',
|
|
30
|
+
'godan',
|
|
31
|
+
'rokudan',
|
|
32
|
+
'nanadan',
|
|
33
|
+
'hachidan',
|
|
34
|
+
'kudan',
|
|
35
|
+
'judan',
|
|
36
|
+
];
|
|
37
|
+
const GRAND_MASTER_TIERS = [
|
|
38
|
+
{ minLevel: 200, title: 'shihan' },
|
|
39
|
+
{ minLevel: 300, title: 'hanshi' },
|
|
40
|
+
{ minLevel: 400, title: 'meijin' },
|
|
41
|
+
{ minLevel: 500, title: 'oyama' },
|
|
42
|
+
];
|
|
43
|
+
/** Convert raw token count to Pyro XP. 1,000 tokens = 1 XP. */
|
|
44
|
+
export function tokensToXp(tokens) {
|
|
45
|
+
return Math.floor(tokens / 1_000);
|
|
46
|
+
}
|
|
47
|
+
/** Convert total XP to level (fractional). */
|
|
48
|
+
export function xpToLevel(xp) {
|
|
49
|
+
if (xp <= 0)
|
|
50
|
+
return 0;
|
|
51
|
+
if (xp >= LEVEL_100_XP) {
|
|
52
|
+
return 100 + 100 * Math.log10(xp / LEVEL_100_XP);
|
|
53
|
+
}
|
|
54
|
+
return (xp / A) ** (1 / K);
|
|
55
|
+
}
|
|
56
|
+
/** Convert level to minimum XP required. */
|
|
57
|
+
export function levelToMinXp(level) {
|
|
58
|
+
if (level <= 0)
|
|
59
|
+
return 0;
|
|
60
|
+
if (level >= 100) {
|
|
61
|
+
return Math.round(LEVEL_100_XP * 10 ** ((level - 100) / 100));
|
|
62
|
+
}
|
|
63
|
+
return Math.round(A * level ** K);
|
|
64
|
+
}
|
|
65
|
+
/** Get the integer level (floor of fractional level). */
|
|
66
|
+
export function getLevel(xp) {
|
|
67
|
+
return Math.floor(xpToLevel(xp));
|
|
68
|
+
}
|
|
69
|
+
/** Get belt color for a given level. */
|
|
70
|
+
export function getBeltColor(level) {
|
|
71
|
+
if (level >= 100)
|
|
72
|
+
return 'black';
|
|
73
|
+
for (let i = BELT_TIERS.length - 1; i >= 0; i--) {
|
|
74
|
+
if (level >= BELT_TIERS[i].minLevel)
|
|
75
|
+
return BELT_TIERS[i].color;
|
|
76
|
+
}
|
|
77
|
+
return 'white';
|
|
78
|
+
}
|
|
79
|
+
/** Get title for a given level. */
|
|
80
|
+
export function getTitle(level) {
|
|
81
|
+
if (level >= 200) {
|
|
82
|
+
for (let i = GRAND_MASTER_TIERS.length - 1; i >= 0; i--) {
|
|
83
|
+
if (level >= GRAND_MASTER_TIERS[i].minLevel)
|
|
84
|
+
return GRAND_MASTER_TIERS[i].title;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (level >= 100) {
|
|
88
|
+
const danIndex = Math.min(Math.floor((level - 100) / 10), DAN_TITLES.length - 1);
|
|
89
|
+
return DAN_TITLES[danIndex];
|
|
90
|
+
}
|
|
91
|
+
for (let i = BELT_TIERS.length - 1; i >= 0; i--) {
|
|
92
|
+
if (level >= BELT_TIERS[i].minLevel)
|
|
93
|
+
return BELT_TIERS[i].title;
|
|
94
|
+
}
|
|
95
|
+
return 'gaijin';
|
|
96
|
+
}
|
|
97
|
+
/** Belt info for a raw token total (pyro-only — correct for a fresh join). */
|
|
98
|
+
export function beltForTokens(tokensTotal) {
|
|
99
|
+
const xp = tokensToXp(tokensTotal);
|
|
100
|
+
const level = getLevel(xp);
|
|
101
|
+
const currentLevelXp = levelToMinXp(level);
|
|
102
|
+
const nextLevelXp = levelToMinXp(level + 1);
|
|
103
|
+
const xpIntoLevel = xp - currentLevelXp;
|
|
104
|
+
const xpNeeded = nextLevelXp - currentLevelXp;
|
|
105
|
+
const progressPercent = xpNeeded > 0 ? Math.min(Math.round((xpIntoLevel / xpNeeded) * 100), 100) : 0;
|
|
106
|
+
return {
|
|
107
|
+
level,
|
|
108
|
+
title: getTitle(level),
|
|
109
|
+
beltColor: getBeltColor(level),
|
|
110
|
+
progressPercent,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=belt.js.map
|
package/dist/belt.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"belt.js","sourceRoot":"","sources":["../src/belt.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The hero command: the guided
|
|
2
|
+
* The hero command: the guided join ritual.
|
|
3
3
|
*
|
|
4
4
|
* scan → anonymous rank → pick username + fields → GitHub OAuth → claim
|
|
5
5
|
*
|
|
6
6
|
* Value (rank) is shown before any account exists; GitHub auth comes last.
|
|
7
7
|
* Each stage degrades on its own terms (see the per-stage handling below) so a
|
|
8
|
-
* network blip on the vanity rank never blocks the actual
|
|
8
|
+
* network blip on the vanity rank never blocks the actual join.
|
|
9
9
|
*/
|
|
10
|
-
export declare function
|
|
11
|
-
//# sourceMappingURL=
|
|
10
|
+
export declare function join(): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=join.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join.d.ts","sourceRoot":"","sources":["../../src/commands/join.ts"],"names":[],"mappings":"AAiCA;;;;;;;;GAQG;AACH,wBAAsB,IAAI,kBA0NzB"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as clack from '@clack/prompts';
|
|
2
|
+
import { beltForTokens } from '../belt.js';
|
|
2
3
|
import { formatTokens, scanAllTools, } from '../scanners/index.js';
|
|
3
4
|
import { getAppUrl, loadSession } from '../session.js';
|
|
5
|
+
import { promptShareOnX, renderShareCard, } from '../share.js';
|
|
4
6
|
import { bold, dim, info } from '../ui.js';
|
|
5
7
|
import { login } from './login.js';
|
|
6
8
|
const TOOL_LABELS = {
|
|
@@ -12,15 +14,15 @@ const TOOL_LABELS = {
|
|
|
12
14
|
opencode: 'OpenCode',
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
15
|
-
* The hero command: the guided
|
|
17
|
+
* The hero command: the guided join ritual.
|
|
16
18
|
*
|
|
17
19
|
* scan → anonymous rank → pick username + fields → GitHub OAuth → claim
|
|
18
20
|
*
|
|
19
21
|
* Value (rank) is shown before any account exists; GitHub auth comes last.
|
|
20
22
|
* Each stage degrades on its own terms (see the per-stage handling below) so a
|
|
21
|
-
* network blip on the vanity rank never blocks the actual
|
|
23
|
+
* network blip on the vanity rank never blocks the actual join.
|
|
22
24
|
*/
|
|
23
|
-
export async function
|
|
25
|
+
export async function join() {
|
|
24
26
|
clack.intro(bold('hacklab'));
|
|
25
27
|
const appUrl = getAppUrl();
|
|
26
28
|
// ── Stage 1: scan ────────────────────────────────────────────────────────
|
|
@@ -54,7 +56,11 @@ export async function signup() {
|
|
|
54
56
|
process.exit(0);
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
|
-
// ── Stage 2: anonymous rank (best-effort; never blocks
|
|
59
|
+
// ── Stage 2: anonymous rank (best-effort; never blocks the join) ──────────
|
|
60
|
+
// Captured for the share card later — the preview rank equals the post-sync
|
|
61
|
+
// rank (rank counts users with MORE tokens, so the user joining the set
|
|
62
|
+
// doesn't change it), so the card can show it without waiting on the upload.
|
|
63
|
+
let previewRank = null;
|
|
58
64
|
try {
|
|
59
65
|
const res = await fetch(`${appUrl}/api/rank/preview`, {
|
|
60
66
|
method: 'POST',
|
|
@@ -63,16 +69,17 @@ export async function signup() {
|
|
|
63
69
|
});
|
|
64
70
|
if (res.ok) {
|
|
65
71
|
const { rank, ofTotal } = (await res.json());
|
|
72
|
+
previewRank = rank;
|
|
66
73
|
clack.note(`${bold(`you'd be #${rank}`)} of ${ofTotal} hackers`, 'your rank');
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
catch {
|
|
70
|
-
// Rank is a hook, not a gate. Skip silently and continue
|
|
77
|
+
// Rank is a hook, not a gate. Skip silently and continue the join.
|
|
71
78
|
}
|
|
72
79
|
// ── Stage 3: pick username + fields ───────────────────────────────────────
|
|
73
80
|
const username = await promptAvailableUsername(appUrl);
|
|
74
81
|
if (username === null) {
|
|
75
|
-
clack.outro(dim('
|
|
82
|
+
clack.outro(dim('cancelled.'));
|
|
76
83
|
process.exit(0);
|
|
77
84
|
}
|
|
78
85
|
const bio = await optionalText('one line — what are you building?');
|
|
@@ -83,12 +90,12 @@ export async function signup() {
|
|
|
83
90
|
await login();
|
|
84
91
|
}
|
|
85
92
|
catch (err) {
|
|
86
|
-
clack.cancel(`GitHub sign-in failed: ${err instanceof Error ? err.message : String(err)}. run \`hacklab
|
|
93
|
+
clack.cancel(`GitHub sign-in failed: ${err instanceof Error ? err.message : String(err)}. run \`hacklab join\` again.`);
|
|
87
94
|
process.exit(1);
|
|
88
95
|
}
|
|
89
96
|
const session = await loadSession();
|
|
90
97
|
if (!session) {
|
|
91
|
-
clack.cancel('GitHub sign-in did not complete. run `hacklab
|
|
98
|
+
clack.cancel('GitHub sign-in did not complete. run `hacklab join` again.');
|
|
92
99
|
process.exit(1);
|
|
93
100
|
}
|
|
94
101
|
// ── Stage 5: claim (re-prompt username on a TOCTOU collision) ─────────────
|
|
@@ -117,7 +124,7 @@ export async function signup() {
|
|
|
117
124
|
if (res.status === 409) {
|
|
118
125
|
const reprompt = await promptAvailableUsername(appUrl, 'that name was just taken — pick another');
|
|
119
126
|
if (reprompt === null) {
|
|
120
|
-
clack.cancel('
|
|
127
|
+
clack.cancel('cancelled before claiming a handle.');
|
|
121
128
|
process.exit(1);
|
|
122
129
|
}
|
|
123
130
|
claimedHandle = reprompt;
|
|
@@ -127,43 +134,103 @@ export async function signup() {
|
|
|
127
134
|
clack.cancel(data?.error ?? `claim failed (${res.status})`);
|
|
128
135
|
process.exit(1);
|
|
129
136
|
}
|
|
130
|
-
// ── Stage 6:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
// ── Stage 6: kick the heavy token upload off in the BACKGROUND ────────────
|
|
138
|
+
// It uploads while the user looks at their card and shares on X, so the
|
|
139
|
+
// upload never feels blocking. `.catch` keeps a transient failure from
|
|
140
|
+
// becoming an unhandled rejection; we reconcile the result in Stage 8.
|
|
141
|
+
const syncPromise = fetch(`${appUrl}/api/claim/sync`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'application/json',
|
|
145
|
+
Authorization: `Bearer ${session.token}`,
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify({
|
|
148
|
+
dailyTotals: scan.dailyTotals,
|
|
149
|
+
toolTotals: scan.toolTotals,
|
|
150
|
+
modelTotals: scan.modelTotals,
|
|
151
|
+
hourlyTotals: scan.hourlyTotals,
|
|
152
|
+
}),
|
|
153
|
+
// Hard cap so a slow/stuck upload can never freeze the CLI indefinitely.
|
|
154
|
+
// On timeout the fetch rejects → .catch → null → "syncing" message.
|
|
155
|
+
signal: AbortSignal.timeout(120_000),
|
|
156
|
+
})
|
|
157
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
158
|
+
.catch(() => null);
|
|
159
|
+
// ── Stage 7: stats thumbnail + share (runs while the upload is in flight) ─
|
|
160
|
+
// Card is rendered from local data: scan totals, the captured preview rank,
|
|
161
|
+
// and the locally-computed belt (pyro-only, which matches the server for a
|
|
162
|
+
// fresh join). No dependency on the in-flight sync.
|
|
163
|
+
if (scan.grandTotal > 0) {
|
|
164
|
+
const belt = beltForTokens(scan.grandTotal);
|
|
165
|
+
const card = {
|
|
166
|
+
handle: claimedHandle,
|
|
167
|
+
level: belt.level,
|
|
168
|
+
title: belt.title,
|
|
169
|
+
beltColor: belt.beltColor,
|
|
170
|
+
tokensTotal: scan.grandTotal,
|
|
171
|
+
rank: previewRank ?? 0,
|
|
172
|
+
streak: 1,
|
|
173
|
+
longestStreak: 1,
|
|
174
|
+
progressPercent: belt.progressPercent,
|
|
175
|
+
estimatedCost: estimateCost(scan.toolTotals),
|
|
176
|
+
toolBreakdown: {
|
|
177
|
+
claudeCode: scan.toolTotals.claude_code ?? 0,
|
|
178
|
+
codex: scan.toolTotals.codex ?? 0,
|
|
179
|
+
cursor: scan.toolTotals.cursor ?? 0,
|
|
138
180
|
},
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (res.ok) {
|
|
147
|
-
const r = (await res.json());
|
|
148
|
-
spin.stop('usage synced');
|
|
149
|
-
if (r.title && r.level != null) {
|
|
150
|
-
info(`${bold(r.title)} lv.${r.level}${r.beltColor ? ` (${r.beltColor} belt)` : ''}`);
|
|
151
|
-
}
|
|
152
|
-
if (r.rankAfter)
|
|
153
|
-
info(`rank: #${r.rankAfter}`);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
spin.stop('usage sync deferred');
|
|
157
|
-
info(dim('your profile is live; usage will sync next run.'));
|
|
158
|
-
}
|
|
181
|
+
models: Object.entries(scan.modelTotals)
|
|
182
|
+
.map(([name, tokens]) => ({ name, tokens }))
|
|
183
|
+
.sort((a, b) => b.tokens - a.tokens),
|
|
184
|
+
dailyActivity: aggregateDailyActivity(scan.dailyTotals),
|
|
185
|
+
};
|
|
186
|
+
await renderShareCard(card);
|
|
187
|
+
await promptShareOnX(card);
|
|
159
188
|
}
|
|
160
|
-
|
|
189
|
+
// ── Stage 8: reconcile the background upload ──────────────────────────────
|
|
190
|
+
// It was uploading during the card + share, so it's usually done by now. Show
|
|
191
|
+
// a spinner for the remainder so a heavy upload reads as "working", never a
|
|
192
|
+
// silent freeze; the AbortSignal above guarantees this resolves.
|
|
193
|
+
spin.start('saving your usage');
|
|
194
|
+
const syncResult = await syncPromise;
|
|
195
|
+
const base = appUrl.replace(/\/$/, '');
|
|
196
|
+
if (syncResult?.title && syncResult.level != null) {
|
|
197
|
+
spin.stop('usage saved');
|
|
198
|
+
info(`${bold(syncResult.title)} lv.${syncResult.level}${syncResult.beltColor ? ` (${syncResult.beltColor} belt)` : ''}`);
|
|
199
|
+
if (syncResult.rankAfter)
|
|
200
|
+
info(`rank: #${syncResult.rankAfter}`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
161
203
|
spin.stop('usage sync deferred');
|
|
162
|
-
info(dim('your profile is live
|
|
204
|
+
info(dim('your profile is live — run `hacklab sync` if your stats look off.'));
|
|
163
205
|
}
|
|
164
|
-
const base = appUrl.replace(/\/$/, '');
|
|
165
206
|
clack.outro(`${bold('claimed.')} ${base}/${claimedHandle}`);
|
|
166
207
|
}
|
|
208
|
+
/** Blended cost estimate ($/M tokens) across the scanned tools, for the card. */
|
|
209
|
+
function estimateCost(toolTotals) {
|
|
210
|
+
const rate = {
|
|
211
|
+
claude_code: 0.6,
|
|
212
|
+
codex: 0.25,
|
|
213
|
+
cursor: 0.4,
|
|
214
|
+
openclaw: 0.5,
|
|
215
|
+
hermes: 0.5,
|
|
216
|
+
opencode: 0.5,
|
|
217
|
+
};
|
|
218
|
+
let cost = 0;
|
|
219
|
+
for (const [tool, tokens] of Object.entries(toolTotals)) {
|
|
220
|
+
cost += (tokens / 1_000_000) * (rate[tool] ?? 0.4);
|
|
221
|
+
}
|
|
222
|
+
return cost;
|
|
223
|
+
}
|
|
224
|
+
/** Collapse per-tool daily entries into one {date, tokens}[] for the card graph. */
|
|
225
|
+
function aggregateDailyActivity(dailyTotals) {
|
|
226
|
+
const byDate = new Map();
|
|
227
|
+
for (const entry of dailyTotals) {
|
|
228
|
+
byDate.set(entry.date, (byDate.get(entry.date) ?? 0) + entry.tokens);
|
|
229
|
+
}
|
|
230
|
+
return Array.from(byDate.entries())
|
|
231
|
+
.map(([date, tokens]) => ({ date, tokens }))
|
|
232
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
233
|
+
}
|
|
167
234
|
/**
|
|
168
235
|
* Prompt for a username and loop until it passes the availability check or the
|
|
169
236
|
* user cancels. Returns the chosen username, or null if cancelled.
|
|
@@ -209,7 +276,7 @@ async function optionalText(message) {
|
|
|
209
276
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
210
277
|
}
|
|
211
278
|
/** Discard a value that isn't a parseable URL, so an optional vanity field can
|
|
212
|
-
* never 400 the claim and abort
|
|
279
|
+
* never 400 the claim and abort the join at the final step. */
|
|
213
280
|
function asValidUrl(value) {
|
|
214
281
|
try {
|
|
215
282
|
return new URL(value).href;
|
|
@@ -239,4 +306,4 @@ function normalizeWebsite(raw) {
|
|
|
239
306
|
return undefined;
|
|
240
307
|
return asValidUrl(/^https?:\/\//i.test(v) ? v : `https://${v}`);
|
|
241
308
|
}
|
|
242
|
-
//# sourceMappingURL=
|
|
309
|
+
//# sourceMappingURL=join.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join.js","sourceRoot":"","sources":["../../src/commands/join.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,9 @@ import { brag, parseBragArgs } from './commands/brag.js';
|
|
|
3
3
|
import { configCommand } from './commands/config.js';
|
|
4
4
|
import { drop } from './commands/drop.js';
|
|
5
5
|
import { exam } from './commands/exam.js';
|
|
6
|
+
import { join } from './commands/join.js';
|
|
6
7
|
import { login } from './commands/login.js';
|
|
7
8
|
import { scanProfile } from './commands/scan-profile.js';
|
|
8
|
-
import { signup } from './commands/signup.js';
|
|
9
9
|
import { sync } from './commands/sync.js';
|
|
10
10
|
import { whoami } from './commands/whoami.js';
|
|
11
11
|
import { loadSession } from './session.js';
|
|
@@ -15,7 +15,7 @@ function printHelp() {
|
|
|
15
15
|
banner();
|
|
16
16
|
console.log('');
|
|
17
17
|
console.log(bold('usage:'));
|
|
18
|
-
console.log(` hacklab ${dim('
|
|
18
|
+
console.log(` hacklab ${dim('join')} join hacklab from your terminal`);
|
|
19
19
|
console.log(` hacklab ${dim('sync')} sync AI token usage to your profile`);
|
|
20
20
|
console.log(` hacklab ${dim('whoami')} check who you're logged in as`);
|
|
21
21
|
console.log(` hacklab ${dim('drop')} ${dim('"message"')} post a drop to your feed`);
|
|
@@ -46,10 +46,10 @@ async function main() {
|
|
|
46
46
|
process.exit(0);
|
|
47
47
|
}
|
|
48
48
|
if (cmd === '--version' || cmd === '-v') {
|
|
49
|
-
console.log('0.
|
|
49
|
+
console.log('0.4.0');
|
|
50
50
|
process.exit(0);
|
|
51
51
|
}
|
|
52
|
-
// Bare invocation (e.g. `npx hacklab@latest`):
|
|
52
|
+
// Bare invocation (e.g. `npx hacklab@latest`): join for newcomers, sync for
|
|
53
53
|
// a returning, already-authenticated user.
|
|
54
54
|
if (!cmd) {
|
|
55
55
|
const session = await loadSession();
|
|
@@ -57,12 +57,12 @@ async function main() {
|
|
|
57
57
|
await sync();
|
|
58
58
|
}
|
|
59
59
|
else {
|
|
60
|
-
await
|
|
60
|
+
await join();
|
|
61
61
|
}
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
-
if (cmd === '
|
|
65
|
-
await
|
|
64
|
+
if (cmd === 'join') {
|
|
65
|
+
await join();
|
|
66
66
|
}
|
|
67
67
|
else if (cmd === 'login') {
|
|
68
68
|
await login();
|