hacklab 0.6.2 → 0.6.6
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 +26 -7
- package/dist/commands/join.d.ts.map +1 -1
- package/dist/commands/join.js +76 -21
- package/dist/commands/join.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +4 -0
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +5 -0
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/org.d.ts +20 -1
- package/dist/commands/org.d.ts.map +1 -1
- package/dist/commands/org.js +369 -43
- package/dist/commands/org.js.map +1 -1
- package/dist/commands/sync.d.ts +7 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +102 -6
- package/dist/commands/sync.js.map +1 -1
- package/dist/daily-sync.d.ts +40 -0
- package/dist/daily-sync.d.ts.map +1 -0
- package/dist/daily-sync.js +253 -0
- package/dist/daily-sync.js.map +1 -0
- package/dist/index.js +23 -17
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +5 -3
- package/dist/registry.js.map +1 -1
- package/dist/session.d.ts +7 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +14 -0
- package/dist/session.js.map +1 -1
- package/dist/sync.d.ts +23 -4
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +89 -30
- package/dist/sync.js.map +1 -1
- package/dist/utils/updateCheck.d.ts +10 -0
- package/dist/utils/updateCheck.d.ts.map +1 -0
- package/dist/utils/updateCheck.js +106 -0
- package/dist/utils/updateCheck.js.map +1 -0
- package/package.json +6 -1
package/dist/sync.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatBytes, formatTokens, scanAllTools, } from './scanners/index.js';
|
|
2
|
-
import { resolveAppUrl } from './session.js';
|
|
2
|
+
import { getSessionExpiresAt, resolveAppUrl, saveSession, } from './session.js';
|
|
3
3
|
import { dim } from './ui.js';
|
|
4
4
|
// Token scanning lives in one place — the scanners/ module (used by both `join`
|
|
5
5
|
// and `sync`). This file is just the `sync` command's API layer: session
|
|
@@ -106,19 +106,28 @@ export async function syncGithubRepos(session) {
|
|
|
106
106
|
* endpoint. Uses the exact same scan path as `join`, so the two commands report
|
|
107
107
|
* identical numbers for the same machine.
|
|
108
108
|
*/
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
109
|
+
/** The six tools the backend tallies, with explicit zeros so the payload shape
|
|
110
|
+
* is stable regardless of which tools the scan actually found. */
|
|
111
|
+
function toolTotalsRecord(scan) {
|
|
112
|
+
return {
|
|
113
|
+
claude_code: scan.toolTotals.claude_code ?? 0,
|
|
114
|
+
codex: scan.toolTotals.codex ?? 0,
|
|
115
|
+
cursor: scan.toolTotals.cursor ?? 0,
|
|
116
|
+
openclaw: scan.toolTotals.openclaw ?? 0,
|
|
117
|
+
hermes: scan.toolTotals.hermes ?? 0,
|
|
118
|
+
opencode: scan.toolTotals.opencode ?? 0,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Upload a scan's token totals to the backend (the `/api/claim/sync` capsule).
|
|
123
|
+
* The single place the upload payload is built and the response is checked, so
|
|
124
|
+
* the `sync` command, the `join` ritual's background upload, and the daily
|
|
125
|
+
* background job all push the exact same shape. Throws LOGIN_EXPIRED_MESSAGE on
|
|
126
|
+
* 401 so callers can tell an expired session apart from a transient failure.
|
|
127
|
+
* Pass `timeoutMs` to hard-cap a slow upload (join uses this so the ritual can
|
|
128
|
+
* never hang); omit it for an unbounded one-shot.
|
|
129
|
+
*/
|
|
130
|
+
export async function uploadTokenScan(session, scan, opts = {}) {
|
|
122
131
|
const res = await fetchApi(session, '/api/claim/sync', {
|
|
123
132
|
method: 'POST',
|
|
124
133
|
headers: {
|
|
@@ -126,18 +135,12 @@ export async function runSync(session) {
|
|
|
126
135
|
Authorization: `Bearer ${session.token}`,
|
|
127
136
|
},
|
|
128
137
|
body: JSON.stringify({
|
|
129
|
-
dailyTotals:
|
|
130
|
-
toolTotals:
|
|
131
|
-
claude_code: claudeTotal,
|
|
132
|
-
codex: codexTotal,
|
|
133
|
-
cursor: cursorTotal,
|
|
134
|
-
openclaw: openclawTotal,
|
|
135
|
-
hermes: hermesTotal,
|
|
136
|
-
opencode: opencodeTotal,
|
|
137
|
-
},
|
|
138
|
+
dailyTotals: scan.dailyTotals,
|
|
139
|
+
toolTotals: toolTotalsRecord(scan),
|
|
138
140
|
modelTotals: scan.modelTotals,
|
|
139
141
|
hourlyTotals: scan.hourlyTotals,
|
|
140
142
|
}),
|
|
143
|
+
...(opts.timeoutMs ? { signal: AbortSignal.timeout(opts.timeoutMs) } : {}),
|
|
141
144
|
});
|
|
142
145
|
if (res.status === 401) {
|
|
143
146
|
throw new Error(LOGIN_EXPIRED_MESSAGE);
|
|
@@ -147,14 +150,70 @@ export async function runSync(session) {
|
|
|
147
150
|
throw new Error(data?.error ??
|
|
148
151
|
`sync failed (${res.status})`);
|
|
149
152
|
}
|
|
150
|
-
|
|
153
|
+
return (await res.json());
|
|
154
|
+
}
|
|
155
|
+
// Refresh within the last week of the 30-day session window — frequent enough to
|
|
156
|
+
// never lapse on a daily job, rare enough to barely touch the endpoint.
|
|
157
|
+
const REFRESH_WHEN_WITHIN_MS = 7 * 24 * 60 * 60 * 1000;
|
|
158
|
+
/**
|
|
159
|
+
* Ask the backend to slide this session's expiry forward (token unchanged) and
|
|
160
|
+
* persist the result. Returns the updated session, or null if the refresh failed
|
|
161
|
+
* (e.g. the session already expired — that needs a fresh `hacklab login`).
|
|
162
|
+
*/
|
|
163
|
+
export async function refreshSession(session) {
|
|
164
|
+
let res;
|
|
165
|
+
try {
|
|
166
|
+
res = await fetchApi(session, '/api/cli/session/refresh', {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: { Authorization: `Bearer ${session.token}` },
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
if (!res.ok)
|
|
175
|
+
return null;
|
|
176
|
+
const data = (await res.json().catch(() => null));
|
|
177
|
+
if (!data?.token || !data.expiresAt)
|
|
178
|
+
return null;
|
|
179
|
+
const updated = {
|
|
180
|
+
...session,
|
|
181
|
+
token: data.token,
|
|
182
|
+
expiresAt: data.expiresAt,
|
|
183
|
+
savedAt: new Date().toISOString(),
|
|
184
|
+
};
|
|
185
|
+
await saveSession(updated);
|
|
186
|
+
return updated;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Proactively refresh the session when it's nearing expiry, so a long-running
|
|
190
|
+
* daily background sync never lapses. A no-op (returns the session unchanged)
|
|
191
|
+
* when there's plenty of time left or the refresh fails.
|
|
192
|
+
*/
|
|
193
|
+
export async function ensureFreshSession(session) {
|
|
194
|
+
const expiresAt = getSessionExpiresAt(session);
|
|
195
|
+
if (!expiresAt)
|
|
196
|
+
return session;
|
|
197
|
+
if (expiresAt.getTime() - Date.now() > REFRESH_WHEN_WITHIN_MS)
|
|
198
|
+
return session;
|
|
199
|
+
return (await refreshSession(session)) ?? session;
|
|
200
|
+
}
|
|
201
|
+
export async function runSync(session) {
|
|
202
|
+
const scan = await scanAllTools();
|
|
203
|
+
const result = await uploadTokenScan(session, scan);
|
|
204
|
+
const totals = toolTotalsRecord(scan);
|
|
205
|
+
const allEntries = scan.dailyTotals;
|
|
206
|
+
const messagesTotal = allEntries.reduce((s, e) => s + (e.messages ?? 0), 0);
|
|
207
|
+
const models = Object.entries(scan.modelTotals)
|
|
208
|
+
.map(([name, tokens]) => ({ name, tokens }))
|
|
209
|
+
.sort((a, b) => b.tokens - a.tokens);
|
|
151
210
|
return {
|
|
152
|
-
claudeTotal,
|
|
153
|
-
codexTotal,
|
|
154
|
-
cursorTotal,
|
|
155
|
-
openclawTotal,
|
|
156
|
-
hermesTotal,
|
|
157
|
-
opencodeTotal,
|
|
211
|
+
claudeTotal: totals.claude_code,
|
|
212
|
+
codexTotal: totals.codex,
|
|
213
|
+
cursorTotal: totals.cursor,
|
|
214
|
+
openclawTotal: totals.openclaw,
|
|
215
|
+
hermesTotal: totals.hermes,
|
|
216
|
+
opencodeTotal: totals.opencode,
|
|
158
217
|
messagesTotal,
|
|
159
218
|
allEntries,
|
|
160
219
|
cursorStats: scan.cursorStats,
|
package/dist/sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Numeric-dotted compare: is published version `a` newer than running `b`? */
|
|
2
|
+
export declare function isNewerVersion(a: string, b: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Print a one-line "newer version available" nudge to stderr. No-ops (instantly,
|
|
5
|
+
* no network) when stdout isn't a TTY (piped / CI / `--json` consumers) or when
|
|
6
|
+
* HACKLAB_NO_UPDATE_CHECK is set. Wrapped so it can never throw or wedge the CLI;
|
|
7
|
+
* the registry lookup is timeout-guarded and runs at most once per day.
|
|
8
|
+
*/
|
|
9
|
+
export declare function notifyIfOutdated(current: string): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=updateCheck.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updateCheck.d.ts","sourceRoot":"","sources":["../../src/utils/updateCheck.ts"],"names":[],"mappings":"AAsBA,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAc5D;AA+CD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBrE"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { getSessionPath } from '../session.js';
|
|
4
|
+
import { dim } from '../ui.js';
|
|
5
|
+
// A globally-installed `hacklab` (what the installer now does) is convenient but
|
|
6
|
+
// can silently rot — the user who hit the 0.5.0 bug proved how confusing a stale
|
|
7
|
+
// CLI is. This nudge turns "silently old" into one visible line, checked at most
|
|
8
|
+
// once a day so it never slows the CLI down or hammers the registry.
|
|
9
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
10
|
+
// Tight bound: this runs before command output once a day on an interactive run,
|
|
11
|
+
// so cap the worst-case stall on a slow network. The npm registry answers in
|
|
12
|
+
// well under this; a miss just defers the nudge to the next run.
|
|
13
|
+
const FETCH_TIMEOUT_MS = 800;
|
|
14
|
+
const PACKAGE = 'hacklab';
|
|
15
|
+
// Cache lives next to the session file (~/.hacklab/), honoring HACKLAB_SESSION_PATH.
|
|
16
|
+
function cachePath() {
|
|
17
|
+
return join(dirname(getSessionPath()), 'update-check.json');
|
|
18
|
+
}
|
|
19
|
+
/** Numeric-dotted compare: is published version `a` newer than running `b`? */
|
|
20
|
+
export function isNewerVersion(a, b) {
|
|
21
|
+
// Strict Number (not parseInt): "3-beta" -> NaN, so a prerelease segment trips
|
|
22
|
+
// the guard below instead of being read as 3.
|
|
23
|
+
const pa = a.split('.').map((n) => Number(n));
|
|
24
|
+
const pb = b.split('.').map((n) => Number(n));
|
|
25
|
+
const len = Math.max(pa.length, pb.length);
|
|
26
|
+
for (let i = 0; i < len; i++) {
|
|
27
|
+
const x = pa[i] ?? 0;
|
|
28
|
+
const y = pb[i] ?? 0;
|
|
29
|
+
// Non-numeric segment (prerelease tag, etc.) — don't guess, treat as not-newer.
|
|
30
|
+
if (Number.isNaN(x) || Number.isNaN(y))
|
|
31
|
+
return false;
|
|
32
|
+
if (x !== y)
|
|
33
|
+
return x > y;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
async function readCache() {
|
|
38
|
+
try {
|
|
39
|
+
const raw = JSON.parse(await fs.readFile(cachePath(), 'utf8'));
|
|
40
|
+
if (typeof raw.latest === 'string' && typeof raw.checkedAt === 'number') {
|
|
41
|
+
return {
|
|
42
|
+
latest: raw.latest,
|
|
43
|
+
fresh: Date.now() - raw.checkedAt < CACHE_TTL_MS,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// No cache yet / unreadable — treat as a miss.
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
async function fetchLatest() {
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE}/latest`, {
|
|
55
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
56
|
+
// Abbreviated metadata: a few hundred bytes instead of the full packument.
|
|
57
|
+
headers: { accept: 'application/vnd.npm.install-v1+json' },
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok)
|
|
60
|
+
return undefined;
|
|
61
|
+
const data = (await res.json());
|
|
62
|
+
return typeof data.version === 'string' ? data.version : undefined;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function writeCache(latest) {
|
|
69
|
+
try {
|
|
70
|
+
await fs.mkdir(dirname(cachePath()), { recursive: true });
|
|
71
|
+
await fs.writeFile(cachePath(), JSON.stringify({ latest, checkedAt: Date.now() }));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Best-effort: a failed write just means we re-check next run.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Print a one-line "newer version available" nudge to stderr. No-ops (instantly,
|
|
79
|
+
* no network) when stdout isn't a TTY (piped / CI / `--json` consumers) or when
|
|
80
|
+
* HACKLAB_NO_UPDATE_CHECK is set. Wrapped so it can never throw or wedge the CLI;
|
|
81
|
+
* the registry lookup is timeout-guarded and runs at most once per day.
|
|
82
|
+
*/
|
|
83
|
+
export async function notifyIfOutdated(current) {
|
|
84
|
+
try {
|
|
85
|
+
if (process.env.HACKLAB_NO_UPDATE_CHECK)
|
|
86
|
+
return;
|
|
87
|
+
if (!process.stdout.isTTY)
|
|
88
|
+
return;
|
|
89
|
+
const cached = await readCache();
|
|
90
|
+
let latest = cached?.latest;
|
|
91
|
+
if (!cached || !cached.fresh) {
|
|
92
|
+
const fetched = await fetchLatest();
|
|
93
|
+
if (fetched) {
|
|
94
|
+
latest = fetched;
|
|
95
|
+
await writeCache(fetched);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (latest && isNewerVersion(latest, current)) {
|
|
99
|
+
console.error(dim(`↑ hacklab ${latest} is available (you have ${current}) · update: npm i -g hacklab@latest`));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// An update check must never break or delay the actual command.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=updateCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updateCheck.js","sourceRoot":"","sources":["../../src/utils/updateCheck.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hacklab",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/homebrew-hackers-club/hacklab.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
8
13
|
"type": "module",
|
|
9
14
|
"engines": {
|
|
10
15
|
"node": ">=20"
|