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 CHANGED
@@ -1,107 +1,76 @@
1
- # @hacklab/cli
1
+ # hacklab
2
2
 
3
- hacklab cli package.
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
- ## commands
7
+ ## Install & join
6
8
 
7
- - `hacklab login` - authenticate via browser oauth or non-interactive token (`--token`), with identity selection (`--as human|agent`).
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
- pnpm --filter @hacklab/cli build
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
- environment variables:
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
- - `HACKLAB_APP_URL` - app base url (default: `http://localhost:3000`)
69
- - `HACKLAB_SESSION_PATH` - custom path for cli session file
70
-
71
- credentials are stored at `~/.hacklab/session.json`.
18
+ ```bash
19
+ npx hacklab@latest join
20
+ ```
72
21
 
73
- ## brag manifest
22
+ ## The join ritual
74
23
 
75
- `hacklab brag` looks for `.hacklab/project.yaml` in the current directory by default.
24
+ ```
25
+ scan local AI usage → see your rank → pick a username → sign in with GitHub → claim
26
+ ```
76
27
 
77
- Supported fields:
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
- - `title` - required
80
- - `slug` - optional; defaults to the current directory name, slugified
81
- - `description`
82
- - `tags` - yaml list or comma-separated string
83
- - `repoUrl` or `repo`
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
- If neither `content` nor `contentFile` is provided, the CLI falls back to `README.md` or `README.mdx` in the repo root.
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
- Example:
72
+ To reset a test account so its GitHub identity can join fresh:
93
73
 
94
- ```yaml
95
- title: Shipyard
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
@@ -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 signup ritual.
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 signup.
8
+ * network blip on the vanity rank never blocks the actual join.
9
9
  */
10
- export declare function signup(): Promise<void>;
11
- //# sourceMappingURL=signup.d.ts.map
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 signup ritual.
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 signup.
23
+ * network blip on the vanity rank never blocks the actual join.
22
24
  */
23
- export async function signup() {
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 signup) ────────────
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 to signup.
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('signup cancelled.'));
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 signup\` again.`);
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 signup` again.');
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('signup cancelled before claiming a handle.');
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: sync token totals to the claimed profile ─────────────────────
131
- spin.start('syncing your usage');
132
- try {
133
- const res = await fetch(`${appUrl}/api/claim/sync`, {
134
- method: 'POST',
135
- headers: {
136
- 'Content-Type': 'application/json',
137
- Authorization: `Bearer ${session.token}`,
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
- body: JSON.stringify({
140
- dailyTotals: scan.dailyTotals,
141
- toolTotals: scan.toolTotals,
142
- modelTotals: scan.modelTotals,
143
- hourlyTotals: scan.hourlyTotals,
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
- catch {
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; usage will sync next run.'));
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 signup at the final step. */
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=signup.js.map
309
+ //# sourceMappingURL=join.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join.js","sourceRoot":"","sources":["../../src/commands/join.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAA;AAEvC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAEL,YAAY,EACZ,YAAY,GACb,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EACL,cAAc,EACd,eAAe,GAEhB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AASlC,MAAM,WAAW,GAA2B;IAC1C,WAAW,EAAE,aAAa;IAC1B,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;CACrB,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAA;IAC5B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAE1B,4EAA4E;IAC5E,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,CAAA;IAC5B,IAAI,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAA;IAC1C,IAAI,IAAmB,CAAA;IACvB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,YAAY,EAAE,CAAA;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QACxB,KAAK,CAAC,MAAM,CACV,iCAAiC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACpF,CAAA;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IAE1B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5D,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,IAAI,CACF,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,SAAS,CAC1E,CAAA;QACH,CAAC;IACH,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,UAAU,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAA;IAE5D,4EAA4E;IAC5E,8BAA8B;IAC9B,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC;YAC/B,OAAO,EACL,qFAAqF;YACvF,YAAY,EAAE,KAAK;SACpB,CAAC,CAAA;QACF,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAClC,KAAK,CAAC,KAAK,CACT,GAAG,CAAC,6DAA6D,CAAC,CACnE,CAAA;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,4EAA4E;IAC5E,wEAAwE;IACxE,6EAA6E;IAC7E,IAAI,WAAW,GAAkB,IAAI,CAAA;IACrC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,mBAAmB,EAAE;YACpD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;SACvD,CAAC,CAAA;QACF,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;YACX,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAG1C,CAAA;YACD,WAAW,GAAG,IAAI,CAAA;YAClB,KAAK,CAAC,IAAI,CACR,GAAG,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC,OAAO,OAAO,UAAU,EACpD,WAAW,CACZ,CAAA;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;IACrE,CAAC;IAED,6EAA6E;IAC7E,MAAM,QAAQ,GAAG,MAAM,uBAAuB,CAAC,MAAM,CAAC,CAAA;IACtD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAA;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,mCAAmC,CAAC,CAAA;IACnE,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,uCAAuC,CAAC,CAAA;IACxE,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,oBAAoB,CAAC,CAAA;IAE3D,6EAA6E;IAC7E,IAAI,CAAC;QACH,MAAM,KAAK,EAAE,CAAA;IACf,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,CAAC,MAAM,CACV,0BAA0B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,+BAA+B,CAC1G,CAAA;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAA;IACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,KAAK,CAAC,MAAM,CAAC,4DAA4D,CAAC,CAAA;QAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,6EAA6E;IAC7E,IAAI,aAAa,GAAG,QAAQ,CAAA;IAC5B,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,gBAAgB,EAAE;YACjD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,OAAO,CAAC,KAAK,EAAE;aACzC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,QAAQ,EAAE,aAAa;gBACvB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvB,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7D,GAAG,CAAC,gBAAgB,CAAC,UAAU,CAAC;oBAC9B,CAAC,CAAC,EAAE,UAAU,EAAE,gBAAgB,CAAC,UAAU,CAAC,EAAE;oBAC9C,CAAC,CAAC,EAAE,CAAC;aACR,CAAC;SACH,CAAC,CAAA;QAEF,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA2C,CAAA;YACzE,aAAa,GAAG,IAAI,CAAC,MAAM,CAAA;YAC3B,MAAK;QACP,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,QAAQ,GAAG,MAAM,uBAAuB,CAC5C,MAAM,EACN,yCAAyC,CAC1C,CAAA;YACD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,KAAK,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAA;gBACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,aAAa,GAAG,QAAQ,CAAA;YACxB,SAAQ;QACV,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAExC,CAAA;QACR,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,iBAAiB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAA;QAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,6EAA6E;IAC7E,wEAAwE;IACxE,uEAAuE;IACvE,uEAAuE;IACvE,MAAM,WAAW,GAA+B,KAAK,CACnD,GAAG,MAAM,iBAAiB,EAC1B;QACE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,OAAO,CAAC,KAAK,EAAE;SACzC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC;QACF,yEAAyE;QACzE,oEAAoE;QACpE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC;KACrC,CACF;SACE,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAE,GAAG,CAAC,IAAI,EAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SACpE,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IAEpB,6EAA6E;IAC7E,4EAA4E;IAC5E,2EAA2E;IAC3E,oDAAoD;IACpD,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC3C,MAAM,IAAI,GAAkB;YAC1B,MAAM,EAAE,aAAa;YACrB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,WAAW,EAAE,IAAI,CAAC,UAAU;YAC5B,IAAI,EAAE,WAAW,IAAI,CAAC;YACtB,MAAM,EAAE,CAAC;YACT,aAAa,EAAE,CAAC;YAChB,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,aAAa,EAAE,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;YAC5C,aAAa,EAAE;gBACb,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,WAAW,IAAI,CAAC;gBAC5C,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;gBACjC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC;aACpC;YACD,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;iBACrC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;iBAC3C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;YACtC,aAAa,EAAE,sBAAsB,CAAC,IAAI,CAAC,WAAW,CAAC;SACxD,CAAA;QACD,MAAM,eAAe,CAAC,IAAI,CAAC,CAAA;QAC3B,MAAM,cAAc,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;IAED,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,iEAAiE;IACjE,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAA;IAC/B,MAAM,UAAU,GAAG,MAAM,WAAW,CAAA;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IACtC,IAAI,UAAU,EAAE,KAAK,IAAI,UAAU,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QAClD,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QACxB,IAAI,CACF,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,KAAK,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CACnH,CAAA;QACD,IAAI,UAAU,CAAC,SAAS;YAAE,IAAI,CAAC,UAAU,UAAU,CAAC,SAAS,EAAE,CAAC,CAAA;IAClE,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAA;QAChC,IAAI,CACF,GAAG,CAAC,mEAAmE,CAAC,CACzE,CAAA;IACH,CAAC;IACD,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,IAAI,aAAa,EAAE,CAAC,CAAA;AAC7D,CAAC;AAED,iFAAiF;AACjF,SAAS,YAAY,CAAC,UAAkC;IACtD,MAAM,IAAI,GAA2B;QACnC,WAAW,EAAE,GAAG;QAChB,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,GAAG;QACX,QAAQ,EAAE,GAAG;QACb,MAAM,EAAE,GAAG;QACX,QAAQ,EAAE,GAAG;KACd,CAAA;IACD,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACxD,IAAI,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;IACpD,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,oFAAoF;AACpF,SAAS,sBAAsB,CAC7B,WAAyC;IAEzC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAA;IACtE,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;SAChC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;SAC3C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;AACjD,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,uBAAuB,CACpC,MAAc,EACd,YAAY,GAAG,4BAA4B;IAE3C,IAAI,OAAO,GAAG,YAAY,CAAA;IAC1B,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC;YAC7B,OAAO;YACP,WAAW,EAAE,mBAAmB;SACjC,CAAC,CAAA;QACF,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACtC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;QAEtC,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,IAAI,MAAM,GAAG,0CAA0C,CAAA;QACvD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CACrB,GAAG,MAAM,iCAAiC,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAC1E,CAAA;YACD,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;gBACX,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAG7B,CAAA;gBACD,SAAS,GAAG,IAAI,CAAC,SAAS,CAAA;gBAC1B,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM;oBAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;YACrD,CAAC;iBAAM,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC9B,MAAM,GAAG,+CAA+C,CAAA;YAC1D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;QACtE,CAAC;QAED,IAAI,SAAS;YAAE,OAAO,SAAS,CAAA;QAC/B,OAAO,GAAG,GAAG,MAAM,gCAAgC,CAAA;IACrD,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,KAAK,UAAU,YAAY,CAAC,OAAe;IACzC,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,CAAA;IACtE,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;IACpC,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;AACjD,CAAC;AAED;+DAC+D;AAC/D,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAA;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,SAAS,aAAa,CAAC,GAAuB;IAC5C,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;IACpB,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IACxB,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,UAAU,CAAC,CAAC,CAAC,CAAA;IACjD,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAClC,OAAO,UAAU,CAAC,iBAAiB,MAAM,EAAE,CAAC,CAAA;AAC9C,CAAC;AAED,iEAAiE;AACjE,SAAS,gBAAgB,CAAC,GAAuB;IAC/C,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;IACpB,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IACxB,OAAO,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;AACjE,CAAC"}
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('signup')} join hacklab from your terminal`);
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.3.0');
49
+ console.log('0.4.0');
50
50
  process.exit(0);
51
51
  }
52
- // Bare invocation (e.g. `npx hacklab@latest`): signup for newcomers, sync for
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 signup();
60
+ await join();
61
61
  }
62
62
  return;
63
63
  }
64
- if (cmd === 'signup') {
65
- await signup();
64
+ if (cmd === 'join') {
65
+ await join();
66
66
  }
67
67
  else if (cmd === 'login') {
68
68
  await login();