hacklab 0.7.4 → 0.8.5
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 +16 -1
- package/dist/api-client.d.ts +18 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +40 -0
- package/dist/api-client.js.map +1 -0
- package/dist/commands/book.d.ts +14 -0
- package/dist/commands/book.d.ts.map +1 -0
- package/dist/commands/book.js +90 -0
- package/dist/commands/book.js.map +1 -0
- package/dist/commands/chat.d.ts +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +3 -0
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/drop.d.ts +1 -1
- package/dist/commands/drop.js +1 -1
- package/dist/commands/hacker.d.ts.map +1 -1
- package/dist/commands/hacker.js +24 -30
- package/dist/commands/hacker.js.map +1 -1
- package/dist/commands/join.d.ts +4 -2
- package/dist/commands/join.d.ts.map +1 -1
- package/dist/commands/join.js +78 -45
- package/dist/commands/join.js.map +1 -1
- package/dist/commands/keys.d.ts +2 -0
- package/dist/commands/keys.d.ts.map +1 -0
- package/dist/commands/keys.js +138 -0
- package/dist/commands/keys.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +3 -6
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/org.d.ts +7 -7
- package/dist/commands/org.d.ts.map +1 -1
- package/dist/commands/org.js +407 -32
- package/dist/commands/org.js.map +1 -1
- package/dist/commands/profile.d.ts +60 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +474 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/commands/project.d.ts +5 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +387 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/scout.d.ts +18 -0
- package/dist/commands/scout.d.ts.map +1 -0
- package/dist/commands/scout.js +232 -0
- package/dist/commands/scout.js.map +1 -0
- package/dist/field-normalize.d.ts +4 -0
- package/dist/field-normalize.d.ts.map +1 -0
- package/dist/field-normalize.js +8 -0
- package/dist/field-normalize.js.map +1 -0
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/machine.d.ts +15 -0
- package/dist/machine.d.ts.map +1 -0
- package/dist/machine.js +64 -0
- package/dist/machine.js.map +1 -0
- package/dist/org-fields.d.ts +57 -0
- package/dist/org-fields.d.ts.map +1 -0
- package/dist/org-fields.js +167 -0
- package/dist/org-fields.js.map +1 -0
- package/dist/posthog.d.ts +34 -5
- package/dist/posthog.d.ts.map +1 -1
- package/dist/posthog.js +217 -13
- package/dist/posthog.js.map +1 -1
- package/dist/project-infer.d.ts +50 -0
- package/dist/project-infer.d.ts.map +1 -0
- package/dist/project-infer.js +211 -0
- package/dist/project-infer.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +37 -9
- package/dist/registry.js.map +1 -1
- package/dist/sync.d.ts +3 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +7 -0
- package/dist/sync.js.map +1 -1
- package/dist/ui.d.ts +1 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +5 -0
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
- package/dist/commands/brag.d.ts +0 -5
- package/dist/commands/brag.d.ts.map +0 -1
- package/dist/commands/brag.js +0 -92
- package/dist/commands/brag.js.map +0 -1
- package/dist/project-brag.d.ts +0 -58
- package/dist/project-brag.d.ts.map +0 -1
- package/dist/project-brag.js +0 -270
- package/dist/project-brag.js.map +0 -1
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import * as clack from '@clack/prompts';
|
|
2
|
+
import { captureEvent } from '../posthog.js';
|
|
3
|
+
import { findRepoRoot, inferProject, slugFromName, } from '../project-infer.js';
|
|
4
|
+
import { resolveCommand } from '../resolve-command.js';
|
|
5
|
+
import { loadSession, resolveAppUrl, unauthorizedHint, } from '../session.js';
|
|
6
|
+
import { fetchApi } from '../sync.js';
|
|
7
|
+
import { bold, dim, error, info, success } from '../ui.js';
|
|
8
|
+
// `hacklab project` — publish and manage the projects on your profile. The
|
|
9
|
+
// old `brag` flow asked you to transcribe your repo into a YAML manifest; this
|
|
10
|
+
// one reads the repo instead (git remote, README, package.json) and only asks
|
|
11
|
+
// for confirmation. `add` is idempotent per repo: the slug derives from the
|
|
12
|
+
// repo name, and a re-run refreshes the same project without touching its
|
|
13
|
+
// publish date or losing screenshots.
|
|
14
|
+
const SUBCOMMANDS = ['add', 'list', 'delete'];
|
|
15
|
+
const PROJECTS_PATH = '/api/projects';
|
|
16
|
+
function printJson(data) {
|
|
17
|
+
console.log(JSON.stringify(data, null, 2));
|
|
18
|
+
}
|
|
19
|
+
function emitJsonError(code, message) {
|
|
20
|
+
console.log(JSON.stringify({ schemaVersion: 1, error: { code, message } }));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
function usage() {
|
|
24
|
+
error('usage: hacklab project [add|list|delete]');
|
|
25
|
+
info(` hacklab project ${dim('add [path] [--yes] [--json]')} publish the repo you're in (re-run to refresh)`);
|
|
26
|
+
info(` hacklab project ${dim('add --title/--description/--live/--tags/--slug')} override any inferred field`);
|
|
27
|
+
info(` hacklab project ${dim('list [--json]')} your projects`);
|
|
28
|
+
info(` hacklab project ${dim('delete <slug> [--yes] [--json]')} remove one`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
async function requireSession(json) {
|
|
32
|
+
const session = await loadSession();
|
|
33
|
+
if (!session) {
|
|
34
|
+
if (json)
|
|
35
|
+
emitJsonError('unauthorized', 'not logged in');
|
|
36
|
+
error('not logged in');
|
|
37
|
+
info(`run ${dim('hacklab login')} first`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
return session;
|
|
41
|
+
}
|
|
42
|
+
async function readEnvelopeError(res, session) {
|
|
43
|
+
if (res.status === 401)
|
|
44
|
+
return unauthorizedHint(session);
|
|
45
|
+
const data = (await res.json().catch(() => null));
|
|
46
|
+
if (typeof data?.error === 'string')
|
|
47
|
+
return data.error;
|
|
48
|
+
return data?.error?.message ?? `request failed (${res.status})`;
|
|
49
|
+
}
|
|
50
|
+
async function fetchProjects(session) {
|
|
51
|
+
const res = await fetchApi(session, PROJECTS_PATH, {
|
|
52
|
+
headers: { Authorization: `Bearer ${session.token}` },
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
throw new Error(await readEnvelopeError(res, session));
|
|
56
|
+
const data = (await res.json().catch(() => null));
|
|
57
|
+
if (!data?.projects)
|
|
58
|
+
throw new Error('got a malformed response from hacklab');
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
function flagValue(args, ...names) {
|
|
62
|
+
for (const name of names) {
|
|
63
|
+
const i = args.indexOf(name);
|
|
64
|
+
if (i !== -1 && args[i + 1])
|
|
65
|
+
return args[i + 1];
|
|
66
|
+
const eq = args.find((a) => a.startsWith(`${name}=`));
|
|
67
|
+
if (eq)
|
|
68
|
+
return eq.slice(name.length + 1);
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
/** Split a `--tags a,b,c` value into clean tag names. */
|
|
73
|
+
export function parseTags(value) {
|
|
74
|
+
return value
|
|
75
|
+
.split(',')
|
|
76
|
+
.map((t) => t.trim().toLowerCase())
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.slice(0, 20);
|
|
79
|
+
}
|
|
80
|
+
// og:image discovery: one GET of the live page, one regex. Both content-first
|
|
81
|
+
// and property-first attribute orders appear in the wild.
|
|
82
|
+
const OG_IMAGE_PATTERNS = [
|
|
83
|
+
/<meta[^>]+(?:property|name)=["']og:image["'][^>]*content=["']([^"']+)["']/i,
|
|
84
|
+
/<meta[^>]+content=["']([^"']+)["'][^>]*(?:property|name)=["']og:image["']/i,
|
|
85
|
+
];
|
|
86
|
+
export function extractOgImage(html, baseUrl) {
|
|
87
|
+
for (const pattern of OG_IMAGE_PATTERNS) {
|
|
88
|
+
const match = html.match(pattern);
|
|
89
|
+
if (match?.[1]) {
|
|
90
|
+
try {
|
|
91
|
+
return new URL(match[1], baseUrl).toString();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const SCREENSHOT_MAX_BYTES = 4 * 1024 * 1024;
|
|
101
|
+
/**
|
|
102
|
+
* Best-effort screenshot from the live site's og:image. Any failure —
|
|
103
|
+
* unreachable site, no tag, oversized or non-png/jpeg image, upload error —
|
|
104
|
+
* returns null and costs nothing but the attempt.
|
|
105
|
+
*/
|
|
106
|
+
async function captureOgScreenshot(session, liveUrl) {
|
|
107
|
+
try {
|
|
108
|
+
const page = await fetch(liveUrl, {
|
|
109
|
+
signal: AbortSignal.timeout(8000),
|
|
110
|
+
headers: { 'User-Agent': 'HacklabCLI (+https://hacklab.so)' },
|
|
111
|
+
});
|
|
112
|
+
if (!page.ok)
|
|
113
|
+
return null;
|
|
114
|
+
const imageUrl = extractOgImage(await page.text(), liveUrl);
|
|
115
|
+
if (!imageUrl)
|
|
116
|
+
return null;
|
|
117
|
+
const image = await fetch(imageUrl, {
|
|
118
|
+
signal: AbortSignal.timeout(8000),
|
|
119
|
+
headers: { 'User-Agent': 'HacklabCLI (+https://hacklab.so)' },
|
|
120
|
+
});
|
|
121
|
+
if (!image.ok)
|
|
122
|
+
return null;
|
|
123
|
+
const contentType = image.headers.get('content-type')?.split(';')[0];
|
|
124
|
+
if (contentType !== 'image/png' && contentType !== 'image/jpeg')
|
|
125
|
+
return null;
|
|
126
|
+
const bytes = Buffer.from(await image.arrayBuffer());
|
|
127
|
+
if (bytes.length === 0 || bytes.length > SCREENSHOT_MAX_BYTES)
|
|
128
|
+
return null;
|
|
129
|
+
const upload = await fetchApi(session, '/api/screenshots', {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': 'application/json',
|
|
133
|
+
Authorization: `Bearer ${session.token}`,
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
image: bytes.toString('base64'),
|
|
137
|
+
filename: contentType === 'image/png' ? 'og.png' : 'og.jpg',
|
|
138
|
+
contentType,
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
const data = (await upload.json().catch(() => null));
|
|
142
|
+
if (!upload.ok || !data?.url)
|
|
143
|
+
return null;
|
|
144
|
+
return { url: data.url, caption: '' };
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function summarize(value, max = 72) {
|
|
151
|
+
if (!value)
|
|
152
|
+
return dim('(none)');
|
|
153
|
+
const oneLine = value.replace(/\s+/g, ' ').trim();
|
|
154
|
+
return oneLine.length > max ? `${oneLine.slice(0, max - 1)}…` : oneLine;
|
|
155
|
+
}
|
|
156
|
+
async function projectAdd(args) {
|
|
157
|
+
const json = args.includes('--json');
|
|
158
|
+
const yes = args.includes('--yes');
|
|
159
|
+
const overrides = {
|
|
160
|
+
title: flagValue(args, '--title'),
|
|
161
|
+
description: flagValue(args, '--description', '--desc'),
|
|
162
|
+
liveUrl: flagValue(args, '--live'),
|
|
163
|
+
repoUrl: flagValue(args, '--repo'),
|
|
164
|
+
slug: flagValue(args, '--slug'),
|
|
165
|
+
tags: flagValue(args, '--tags'),
|
|
166
|
+
};
|
|
167
|
+
const flagValues = new Set(Object.values(overrides).filter((v) => v !== undefined));
|
|
168
|
+
const pathArg = args.find((a) => !a.startsWith('-') && !flagValues.has(a));
|
|
169
|
+
const root = await findRepoRoot(pathArg ?? process.cwd());
|
|
170
|
+
if (!root) {
|
|
171
|
+
const message = `not a git repository: ${pathArg ?? process.cwd()}`;
|
|
172
|
+
if (json)
|
|
173
|
+
emitJsonError('not_a_repo', message);
|
|
174
|
+
error(message);
|
|
175
|
+
info('run from inside the repo you want to publish');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const inferred = await inferProject(root);
|
|
179
|
+
const draft = {
|
|
180
|
+
...inferred,
|
|
181
|
+
title: overrides.title ?? inferred.title,
|
|
182
|
+
description: overrides.description ?? inferred.description,
|
|
183
|
+
liveUrl: overrides.liveUrl ?? inferred.liveUrl,
|
|
184
|
+
repoUrl: overrides.repoUrl ?? inferred.repoUrl,
|
|
185
|
+
slug: overrides.slug ? slugFromName(overrides.slug) : inferred.slug,
|
|
186
|
+
tags: overrides.tags ? parseTags(overrides.tags) : inferred.tags,
|
|
187
|
+
};
|
|
188
|
+
const session = await requireSession(json);
|
|
189
|
+
// The existing row (if any) decides create-vs-refresh, keeps the original
|
|
190
|
+
// publish date, and provides fallback screenshots.
|
|
191
|
+
let existing;
|
|
192
|
+
try {
|
|
193
|
+
existing = (await fetchProjects(session)).projects.find((p) => p.slug === draft.slug);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
197
|
+
if (json)
|
|
198
|
+
emitJsonError('error', message);
|
|
199
|
+
error(message);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
if (!json) {
|
|
203
|
+
console.log(` ${bold(existing ? 'refreshing' : 'publishing')} ${bold(draft.title)} ${dim(`(${draft.slug})`)}`);
|
|
204
|
+
console.log(` ${dim('description')} ${summarize(draft.description)}`);
|
|
205
|
+
console.log(` ${dim('repo')} ${summarize(draft.repoUrl)}`);
|
|
206
|
+
console.log(` ${dim('live')} ${summarize(draft.liveUrl)}`);
|
|
207
|
+
console.log(` ${dim('tags')} ${draft.tags.length ? draft.tags.join(', ') : dim('(none)')}`);
|
|
208
|
+
console.log(` ${dim('content')} ${draft.content ? `README (${draft.content.length} chars)` : dim('(none)')}`);
|
|
209
|
+
}
|
|
210
|
+
if (!json && !yes && process.stdout.isTTY) {
|
|
211
|
+
const go = await clack.confirm({ message: 'publish it?' });
|
|
212
|
+
if (clack.isCancel(go) || !go) {
|
|
213
|
+
clack.outro(dim('cancelled.'));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Fresh og:image capture when the site has one; otherwise whatever the
|
|
218
|
+
// project already shows keeps showing (POST overwrites screenshots).
|
|
219
|
+
let screenshots = existing?.screenshots ?? [];
|
|
220
|
+
if (draft.liveUrl) {
|
|
221
|
+
if (!json)
|
|
222
|
+
info(dim('looking for a screenshot (og:image)…'));
|
|
223
|
+
const shot = await captureOgScreenshot(session, draft.liveUrl);
|
|
224
|
+
if (shot)
|
|
225
|
+
screenshots = [shot];
|
|
226
|
+
}
|
|
227
|
+
const payload = {
|
|
228
|
+
title: draft.title,
|
|
229
|
+
slug: draft.slug,
|
|
230
|
+
description: draft.description ?? undefined,
|
|
231
|
+
tags: draft.tags,
|
|
232
|
+
repoUrl: draft.repoUrl ?? undefined,
|
|
233
|
+
liveUrl: draft.liveUrl ?? undefined,
|
|
234
|
+
screenshots,
|
|
235
|
+
content: draft.content ?? undefined,
|
|
236
|
+
publishedAt: existing?.publishedAt ?? new Date().toISOString(),
|
|
237
|
+
};
|
|
238
|
+
let res;
|
|
239
|
+
try {
|
|
240
|
+
res = await fetchApi(session, PROJECTS_PATH, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: {
|
|
243
|
+
'Content-Type': 'application/json',
|
|
244
|
+
Authorization: `Bearer ${session.token}`,
|
|
245
|
+
},
|
|
246
|
+
body: JSON.stringify(payload),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
251
|
+
if (json)
|
|
252
|
+
emitJsonError('network', message);
|
|
253
|
+
error(message);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
if (!res.ok) {
|
|
257
|
+
const message = await readEnvelopeError(res, session);
|
|
258
|
+
if (json)
|
|
259
|
+
emitJsonError('error', message);
|
|
260
|
+
error(message);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
await captureEvent(session.handle, 'cli_project_added', {
|
|
264
|
+
slug: draft.slug,
|
|
265
|
+
refreshed: Boolean(existing),
|
|
266
|
+
has_live_url: Boolean(draft.liveUrl),
|
|
267
|
+
has_screenshot: screenshots.length > 0,
|
|
268
|
+
tag_count: draft.tags.length,
|
|
269
|
+
});
|
|
270
|
+
const path = `/${session.handle}/${draft.slug}`;
|
|
271
|
+
if (json) {
|
|
272
|
+
printJson({
|
|
273
|
+
schemaVersion: 1,
|
|
274
|
+
[existing ? 'refreshed' : 'published']: true,
|
|
275
|
+
slug: draft.slug,
|
|
276
|
+
path,
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
success(`${existing ? 'refreshed' : 'published'} ${bold(draft.title)}`);
|
|
281
|
+
info(`${resolveAppUrl(session)}${path}`);
|
|
282
|
+
}
|
|
283
|
+
async function projectList(args) {
|
|
284
|
+
const json = args.includes('--json');
|
|
285
|
+
const session = await requireSession(json);
|
|
286
|
+
let list;
|
|
287
|
+
try {
|
|
288
|
+
list = await fetchProjects(session);
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
292
|
+
if (json)
|
|
293
|
+
emitJsonError('error', message);
|
|
294
|
+
error(message);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
if (json) {
|
|
298
|
+
printJson({ schemaVersion: 1, ...list });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (list.projects.length === 0) {
|
|
302
|
+
info('no projects yet');
|
|
303
|
+
info(`run ${dim('hacklab project add')} inside a repo to publish one`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const width = Math.max(...list.projects.map((p) => p.slug.length));
|
|
307
|
+
for (const p of list.projects) {
|
|
308
|
+
console.log(` ${bold(p.slug.padEnd(width))} ${summarize(p.title, 48)} ${dim(`(${p.source})`)}`);
|
|
309
|
+
}
|
|
310
|
+
console.log('');
|
|
311
|
+
info(dim(`${resolveAppUrl(session)}/${list.handle}`));
|
|
312
|
+
}
|
|
313
|
+
async function projectDelete(args) {
|
|
314
|
+
const json = args.includes('--json');
|
|
315
|
+
const yes = args.includes('--yes');
|
|
316
|
+
const slug = args.find((a) => !a.startsWith('-'));
|
|
317
|
+
if (!slug)
|
|
318
|
+
usage();
|
|
319
|
+
const session = await requireSession(json);
|
|
320
|
+
if (!json && !yes && process.stdout.isTTY) {
|
|
321
|
+
const go = await clack.confirm({
|
|
322
|
+
message: `delete ${bold(slug)} from your profile?`,
|
|
323
|
+
});
|
|
324
|
+
if (clack.isCancel(go) || !go) {
|
|
325
|
+
clack.outro(dim('cancelled.'));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
let res;
|
|
330
|
+
try {
|
|
331
|
+
res = await fetchApi(session, `${PROJECTS_PATH}/${encodeURIComponent(slug)}`, {
|
|
332
|
+
method: 'DELETE',
|
|
333
|
+
headers: { Authorization: `Bearer ${session.token}` },
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
338
|
+
if (json)
|
|
339
|
+
emitJsonError('network', message);
|
|
340
|
+
error(message);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
if (!res.ok) {
|
|
344
|
+
const message = await readEnvelopeError(res, session);
|
|
345
|
+
if (json)
|
|
346
|
+
emitJsonError('error', message);
|
|
347
|
+
error(message);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
const data = (await res.json().catch(() => null));
|
|
351
|
+
await captureEvent(session.handle, 'cli_project_deleted', {
|
|
352
|
+
slug,
|
|
353
|
+
source: data?.deleted?.source,
|
|
354
|
+
});
|
|
355
|
+
if (json) {
|
|
356
|
+
printJson({ schemaVersion: 1, deleted: data?.deleted ?? { slug } });
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
success(`deleted ${bold(slug)}`);
|
|
360
|
+
if (data?.deleted?.source === 'github') {
|
|
361
|
+
info(`it's synced from a pinned GitHub repo — unpin it or the next ${dim('hacklab sync')} brings it back`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
export async function project(args) {
|
|
365
|
+
const [subToken, ...rest] = args;
|
|
366
|
+
// Bare `hacklab project` inside a repo means "publish this" — the verb the
|
|
367
|
+
// command exists for. `list` is one word away.
|
|
368
|
+
if (!subToken || subToken.startsWith('-')) {
|
|
369
|
+
return projectAdd(args);
|
|
370
|
+
}
|
|
371
|
+
const resolved = resolveCommand(subToken, SUBCOMMANDS);
|
|
372
|
+
if (resolved.kind === 'ambiguous') {
|
|
373
|
+
error(`ambiguous: project ${subToken} (${resolved.matches.join(', ')})`);
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
if (resolved.kind === 'unknown') {
|
|
377
|
+
error(`unknown subcommand: project ${subToken}`);
|
|
378
|
+
usage();
|
|
379
|
+
}
|
|
380
|
+
if (resolved.name === 'add')
|
|
381
|
+
return projectAdd(rest);
|
|
382
|
+
if (resolved.name === 'list')
|
|
383
|
+
return projectList(rest);
|
|
384
|
+
if (resolved.name === 'delete')
|
|
385
|
+
return projectDelete(rest);
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The `scout` namespace dispatcher: routes to search or picks. */
|
|
2
|
+
export declare function scout(args: string[]): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Monday 00:00 UTC of the current week, for `--new-this-week`. Mirrors
|
|
5
|
+
* @hacklab/db's mondayUtcWeekOf (the week owner for picks); duplicated here
|
|
6
|
+
* because the CLI doesn't depend on the db package — this is client-side
|
|
7
|
+
* sugar over the real contract param, `newSince`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mondayUtcStartOfWeek(now: Date): string;
|
|
10
|
+
export type SearchFlags = {
|
|
11
|
+
json: boolean;
|
|
12
|
+
query: URLSearchParams;
|
|
13
|
+
};
|
|
14
|
+
/** Flag→param mapping is 1:1 with the API contract; the server validates. */
|
|
15
|
+
export declare function parseSearchArgs(args: string[]): SearchFlags | {
|
|
16
|
+
usage: string;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=scout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scout.d.ts","sourceRoot":"","sources":["../../src/commands/scout.ts"],"names":[],"mappings":"AAiCA,mEAAmE;AACnE,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBzD;AA8BD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,IAAI,GAAG,MAAM,CAUtD;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,eAAe,CAAA;CACvB,CAAA;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EAAE,GACb,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAoCjC"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { resolveCommand } from '../resolve-command.js';
|
|
2
|
+
import { formatTokens } from '../scanners/util.js';
|
|
3
|
+
import { loadSession } from '../session.js';
|
|
4
|
+
import { ensureFreshSession, fetchApi, LOGIN_EXPIRED_MESSAGE } from '../sync.js';
|
|
5
|
+
import { bold, dim, error, hint, info, rankColor, stripControl } from '../ui.js';
|
|
6
|
+
// `hacklab scout` is a namespace over two products, kept together so the pair is
|
|
7
|
+
// obvious and neither is privileged by a bare command:
|
|
8
|
+
// scout search — the client filters the pool themselves (their criteria, their
|
|
9
|
+
// ranking); flags map 1:1 to the API.
|
|
10
|
+
// scout picks — our curated shortlist, pre-ranked with a thesis per hacker.
|
|
11
|
+
// Subcommands resolve by shortest unambiguous prefix (shared resolveCommand), so
|
|
12
|
+
// `hacklab scout se` / `hacklab scout p` work. Access is gated by the API
|
|
13
|
+
// (key scope / grant), never by which subcommand is typed.
|
|
14
|
+
const SUBCOMMANDS = ['search', 'picks'];
|
|
15
|
+
// Exit-code contract for cron consumers (v2 design doc, step 4): 0 = success
|
|
16
|
+
// (including an empty list), 2 = auth expired/missing, 3 = not entitled,
|
|
17
|
+
// 1 = anything else. Errors and hints go to stderr, NEVER onto --json stdout.
|
|
18
|
+
const EXIT_AUTH = 2;
|
|
19
|
+
const EXIT_NOT_ENTITLED = 3;
|
|
20
|
+
const EXIT_OTHER = 1;
|
|
21
|
+
const GATE_MESSAGE = 'scout is invite-only. talk to marin.';
|
|
22
|
+
const BROKEN_MESSAGE = "broken. we're on it.";
|
|
23
|
+
function usage() {
|
|
24
|
+
error('usage: hacklab scout <search|picks> [flags]');
|
|
25
|
+
hint('scout search filter the hacker pool yourself, or --json for crons');
|
|
26
|
+
hint("scout picks this week's curated shortlist");
|
|
27
|
+
process.exit(EXIT_OTHER);
|
|
28
|
+
}
|
|
29
|
+
/** The `scout` namespace dispatcher: routes to search or picks. */
|
|
30
|
+
export async function scout(args) {
|
|
31
|
+
const [subToken, ...rest] = args;
|
|
32
|
+
if (!subToken || subToken.startsWith('-'))
|
|
33
|
+
usage();
|
|
34
|
+
const resolved = resolveCommand(subToken, SUBCOMMANDS);
|
|
35
|
+
if (resolved.kind === 'ambiguous') {
|
|
36
|
+
error(`ambiguous: scout ${subToken} (${resolved.matches.join(', ')})`);
|
|
37
|
+
process.exit(EXIT_OTHER);
|
|
38
|
+
}
|
|
39
|
+
if (resolved.kind === 'unknown') {
|
|
40
|
+
error(`unknown subcommand: scout ${subToken}`);
|
|
41
|
+
usage();
|
|
42
|
+
}
|
|
43
|
+
if (resolved.name === 'search')
|
|
44
|
+
return scoutSearch(rest);
|
|
45
|
+
if (resolved.name === 'picks')
|
|
46
|
+
return scoutPicks(rest);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Monday 00:00 UTC of the current week, for `--new-this-week`. Mirrors
|
|
50
|
+
* @hacklab/db's mondayUtcWeekOf (the week owner for picks); duplicated here
|
|
51
|
+
* because the CLI doesn't depend on the db package — this is client-side
|
|
52
|
+
* sugar over the real contract param, `newSince`.
|
|
53
|
+
*/
|
|
54
|
+
export function mondayUtcStartOfWeek(now) {
|
|
55
|
+
const day = now.getUTCDay();
|
|
56
|
+
const daysSinceMonday = day === 0 ? 6 : day - 1;
|
|
57
|
+
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - daysSinceMonday)).toISOString();
|
|
58
|
+
}
|
|
59
|
+
/** Flag→param mapping is 1:1 with the API contract; the server validates. */
|
|
60
|
+
export function parseSearchArgs(args) {
|
|
61
|
+
const query = new URLSearchParams();
|
|
62
|
+
let json = false;
|
|
63
|
+
let newThisWeek = false;
|
|
64
|
+
let newSince = null;
|
|
65
|
+
for (let i = 0; i < args.length; i++) {
|
|
66
|
+
const arg = args[i];
|
|
67
|
+
if (arg === '--json')
|
|
68
|
+
json = true;
|
|
69
|
+
else if (arg === '--new-this-week')
|
|
70
|
+
newThisWeek = true;
|
|
71
|
+
else if (arg === '--new-since') {
|
|
72
|
+
newSince = args[++i] ?? '';
|
|
73
|
+
}
|
|
74
|
+
else if (arg === '--open-to-work')
|
|
75
|
+
query.set('openToWork', 'true');
|
|
76
|
+
else if (arg === '--sort') {
|
|
77
|
+
const value = args[++i] ?? '';
|
|
78
|
+
query.set('sort', value);
|
|
79
|
+
}
|
|
80
|
+
else if (arg === '--limit') {
|
|
81
|
+
const value = args[++i] ?? '';
|
|
82
|
+
query.set('limit', value);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
return { usage: `unknown flag ${arg}` };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (newThisWeek && newSince !== null) {
|
|
89
|
+
return { usage: 'use --new-this-week or --new-since, not both' };
|
|
90
|
+
}
|
|
91
|
+
if (newSince !== null) {
|
|
92
|
+
if (Number.isNaN(new Date(newSince).getTime())) {
|
|
93
|
+
return { usage: '--new-since must be an ISO 8601 timestamp' };
|
|
94
|
+
}
|
|
95
|
+
query.set('newSince', newSince);
|
|
96
|
+
}
|
|
97
|
+
if (newThisWeek)
|
|
98
|
+
query.set('newSince', mondayUtcStartOfWeek(new Date()));
|
|
99
|
+
return { json, query };
|
|
100
|
+
}
|
|
101
|
+
function parseJson(body) {
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(body);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function fetchScout(path, json) {
|
|
110
|
+
const stored = await loadSession();
|
|
111
|
+
if (!stored) {
|
|
112
|
+
error('not logged in');
|
|
113
|
+
hint(`run ${dim('hacklab login')} to authenticate`);
|
|
114
|
+
process.exit(EXIT_AUTH);
|
|
115
|
+
}
|
|
116
|
+
const session = await ensureFreshSession(stored);
|
|
117
|
+
let res;
|
|
118
|
+
try {
|
|
119
|
+
res = await fetchApi(session, path, {
|
|
120
|
+
headers: { Authorization: `Bearer ${session.token}` },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
error(err instanceof Error ? err.message : String(err));
|
|
125
|
+
process.exit(EXIT_OTHER);
|
|
126
|
+
}
|
|
127
|
+
if (res.status === 401) {
|
|
128
|
+
error(LOGIN_EXPIRED_MESSAGE);
|
|
129
|
+
process.exit(EXIT_AUTH);
|
|
130
|
+
}
|
|
131
|
+
if (res.status === 403) {
|
|
132
|
+
error(GATE_MESSAGE);
|
|
133
|
+
process.exit(EXIT_NOT_ENTITLED);
|
|
134
|
+
}
|
|
135
|
+
const body = await res.text();
|
|
136
|
+
if (res.status === 400) {
|
|
137
|
+
const data = parseJson(body);
|
|
138
|
+
error(data?.error ?? 'invalid request');
|
|
139
|
+
process.exit(EXIT_OTHER);
|
|
140
|
+
}
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
error(BROKEN_MESSAGE);
|
|
143
|
+
process.exit(EXIT_OTHER);
|
|
144
|
+
}
|
|
145
|
+
// --json stdout purity: the server envelope, verbatim, nothing else.
|
|
146
|
+
if (json) {
|
|
147
|
+
process.stdout.write(`${body}\n`);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
// Human path: a 200 with a non-JSON body (proxy/CDN error page) is a broken
|
|
151
|
+
// upstream, not renderable — fail cleanly instead of throwing on parse.
|
|
152
|
+
const data = parseJson(body);
|
|
153
|
+
if (!data) {
|
|
154
|
+
error(BROKEN_MESSAGE);
|
|
155
|
+
process.exit(EXIT_OTHER);
|
|
156
|
+
}
|
|
157
|
+
return { body, data };
|
|
158
|
+
}
|
|
159
|
+
// URLs and free text reach the terminal from user-controlled columns; strip
|
|
160
|
+
// control chars so an embedded ANSI escape can't hijack the viewer's terminal.
|
|
161
|
+
function formatLinks(links) {
|
|
162
|
+
return [links.profile, links.github, links.website]
|
|
163
|
+
.filter((l) => Boolean(l))
|
|
164
|
+
.map((l) => stripControl(l).replace(/^https:\/\//, ''))
|
|
165
|
+
.join(' · ');
|
|
166
|
+
}
|
|
167
|
+
function renderHacker(h) {
|
|
168
|
+
const name = h.displayName ? ` ${stripControl(h.displayName)}` : '';
|
|
169
|
+
const otw = h.openToWork ? dim(' · open to work') : '';
|
|
170
|
+
console.log(` ${rankColor(h.level, `@${stripControl(h.handle)}`)}${name} ${dim(`${h.belt} lv${h.level}`)}${otw}`);
|
|
171
|
+
if (h.bio)
|
|
172
|
+
console.log(dim(` ${stripControl(h.bio)}`));
|
|
173
|
+
console.log(dim(` ${formatTokens(h.tokens30d)} tokens (30d) · ${h.activeDays30} active days · ${h.counts.projects} projects · ${h.counts.essays} essays · ${h.counts.followers} followers`));
|
|
174
|
+
const top = h.projects[0];
|
|
175
|
+
if (top) {
|
|
176
|
+
const desc = top.description ? `, ${stripControl(top.description)}` : '';
|
|
177
|
+
console.log(dim(` building: ${stripControl(top.title)}${desc}`));
|
|
178
|
+
}
|
|
179
|
+
console.log(dim(` ${formatLinks(h.links)}`));
|
|
180
|
+
console.log('');
|
|
181
|
+
}
|
|
182
|
+
async function scoutSearch(args) {
|
|
183
|
+
const parsed = parseSearchArgs(args);
|
|
184
|
+
if ('usage' in parsed) {
|
|
185
|
+
error(parsed.usage);
|
|
186
|
+
hint(`usage: hacklab scout search [--new-this-week | --new-since <iso>] [--open-to-work] [--sort <key>] [--limit <n>] [--json]`);
|
|
187
|
+
process.exit(EXIT_OTHER);
|
|
188
|
+
}
|
|
189
|
+
const qs = parsed.query.size > 0 ? `?${parsed.query}` : '';
|
|
190
|
+
const result = await fetchScout(`/api/scout/hackers${qs}`, parsed.json);
|
|
191
|
+
if (!result)
|
|
192
|
+
return;
|
|
193
|
+
const list = (result.data.hackers ?? []);
|
|
194
|
+
if (list.length === 0) {
|
|
195
|
+
info('no hackers match. loosen the filters?');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
console.log('');
|
|
199
|
+
console.log(bold(` ${list.length} hacker${list.length === 1 ? '' : 's'}`));
|
|
200
|
+
console.log('');
|
|
201
|
+
for (const h of list)
|
|
202
|
+
renderHacker(h);
|
|
203
|
+
}
|
|
204
|
+
async function scoutPicks(args) {
|
|
205
|
+
const json = args.includes('--json');
|
|
206
|
+
const unknown = args.find((a) => a !== '--json');
|
|
207
|
+
if (unknown) {
|
|
208
|
+
error(`unknown flag ${unknown}`);
|
|
209
|
+
hint('usage: hacklab scout picks [--json]');
|
|
210
|
+
process.exit(EXIT_OTHER);
|
|
211
|
+
}
|
|
212
|
+
const result = await fetchScout('/api/scout/picks', json);
|
|
213
|
+
if (!result)
|
|
214
|
+
return;
|
|
215
|
+
const picks = (result.data.picks ?? []);
|
|
216
|
+
if (picks.length === 0) {
|
|
217
|
+
info('no picks yet this week. check back friday.');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
console.log('');
|
|
221
|
+
// DESIGN.md Voice: no em-dashes in product copy (hard rule).
|
|
222
|
+
console.log(bold(` scout picks: week of ${result.data.weekOf}`));
|
|
223
|
+
console.log('');
|
|
224
|
+
for (const p of picks) {
|
|
225
|
+
const name = p.displayName ? ` ${stripControl(p.displayName)}` : '';
|
|
226
|
+
console.log(` ${p.position}. ${rankColor(p.level, `@${stripControl(p.handle)}`)}${name} ${dim(`${p.belt} lv${p.level}`)}`);
|
|
227
|
+
console.log(` ${stripControl(p.thesis)}`);
|
|
228
|
+
console.log(dim(` ${formatLinks(p.links)}`));
|
|
229
|
+
console.log('');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=scout.js.map
|