lastgen 1.2.0 → 1.3.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/bin/lastgen ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env -S node --disable-warning=ExperimentalWarning
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ const { run } = await import(join(__dirname, '..', 'dist', 'cli.mjs'));
10
+
11
+ try {
12
+ await run(process.argv.slice(2));
13
+ } catch (err) {
14
+ const message = err instanceof Error ? err.message : String(err);
15
+ process.stderr.write(`Error: ${message}\n`);
16
+ process.exitCode = 1;
17
+ }
package/dist/cli.mjs ADDED
@@ -0,0 +1,672 @@
1
+ import { createRequire } from "node:module";
2
+ import { parseArgs, styleText } from "node:util";
3
+ import { createHash } from "node:crypto";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { createServer } from "node:http";
6
+ import { dirname, extname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ //#region src/core/types.ts
9
+ /**
10
+ * @fileoverview Shared interfaces, constants, and type definitions for lastgen.
11
+ */
12
+ const CUTOFF_DATE = "2025-02-21T00:00:00Z";
13
+ const ERAS = {
14
+ LAST_GEN: {
15
+ title: "Last Generation Coder",
16
+ description: "Wrote code before AI agents shipped"
17
+ },
18
+ AI_NATIVE: {
19
+ title: "AI Native Coder",
20
+ description: "First verifiable commit after AI agents shipped"
21
+ }
22
+ };
23
+ const CERTIFICATE_SALT = "lastgen_v1";
24
+ const THIRTY_DAYS_MS = 720 * 60 * 60 * 1e3;
25
+ //#endregion
26
+ //#region src/core/github.ts
27
+ const GITHUB_API = "https://api.github.com";
28
+ const USER_AGENT = "lastgen";
29
+ function buildHeaders(token) {
30
+ const headers = {
31
+ Accept: "application/vnd.github.v3+json",
32
+ "User-Agent": USER_AGENT
33
+ };
34
+ if (token) headers["Authorization"] = `token ${token}`;
35
+ return headers;
36
+ }
37
+ async function githubFetch(url, token, extraHeaders) {
38
+ const response = await fetch(url, { headers: {
39
+ ...buildHeaders(token),
40
+ ...extraHeaders
41
+ } });
42
+ if (response.status === 403) {
43
+ if (response.headers.get("x-ratelimit-remaining") === "0") {
44
+ const resetTimestamp = response.headers.get("x-ratelimit-reset");
45
+ const resetDate = resetTimestamp ? (/* @__PURE__ */ new Date(Number(resetTimestamp) * 1e3)).toLocaleTimeString() : "soon";
46
+ throw new Error(`GitHub API rate limit exceeded. Resets at ${resetDate}.`);
47
+ }
48
+ }
49
+ if (response.status === 404) {
50
+ const userMatch = url.match(/\/users\/([^/?]+)/);
51
+ if (userMatch?.[1]) throw new Error(`GitHub user '${decodeURIComponent(userMatch[1])}' not found. Check the spelling?`);
52
+ throw new Error(`Not found: ${url}`);
53
+ }
54
+ if (!response.ok) throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
55
+ return response;
56
+ }
57
+ async function fetchUser(username, token) {
58
+ const data = await (await githubFetch(`${GITHUB_API}/users/${encodeURIComponent(username)}`, token)).json();
59
+ return {
60
+ login: data.login,
61
+ id: data.id,
62
+ name: data.name ?? null,
63
+ createdAt: data.created_at
64
+ };
65
+ }
66
+ async function fetchFirstCommit(username, token) {
67
+ const commit = await searchFirstCommit(username, token);
68
+ if (commit?.repo) commit.repoCreatedAt = await fetchRepoCreatedAt(commit.repo, token);
69
+ return commit;
70
+ }
71
+ async function fetchRepoCreatedAt(repoFullName, token) {
72
+ try {
73
+ return (await (await githubFetch(`${GITHUB_API}/repos/${repoFullName}`, token)).json()).created_at ?? void 0;
74
+ } catch {
75
+ return;
76
+ }
77
+ }
78
+ async function searchFirstCommitByQuery(query, token, order = "asc") {
79
+ try {
80
+ const item = (await (await githubFetch(`${GITHUB_API}/search/commits?q=${encodeURIComponent(query)}&sort=committer-date&order=${order}&per_page=1`, token, { Accept: "application/vnd.github.cloak-preview+json" })).json()).items?.[0];
81
+ if (!item) return null;
82
+ const commit = item.commit;
83
+ const author = commit.author;
84
+ const commitCommitter = commit.committer;
85
+ const repo = item.repository;
86
+ return {
87
+ date: author.date,
88
+ repo: repo.full_name ?? "",
89
+ sha: item.sha,
90
+ message: (commit.message ?? "").split("\n")[0] ?? "",
91
+ committerDate: commitCommitter?.date ?? void 0
92
+ };
93
+ } catch (err) {
94
+ if (err instanceof Error && err.message.includes("rate limit")) throw err;
95
+ return null;
96
+ }
97
+ }
98
+ async function searchFirstCommit(username, token) {
99
+ const cutoffDate = CUTOFF_DATE.slice(0, 10);
100
+ return await searchFirstCommitByQuery(`author:${username} user:${username} committer-date:<${cutoffDate}`, token, "desc") ?? await searchFirstCommitByQuery(`author:${username} committer-date:<${cutoffDate}`, token, "desc") ?? await searchFirstCommitByQuery(`author:${username}`, token);
101
+ }
102
+ async function fetchCommit(repoFullName, sha, token) {
103
+ const data = await (await githubFetch(`${GITHUB_API}/repos/${repoFullName}/commits/${sha}`, token)).json();
104
+ const commit = data.commit;
105
+ const commitAuthor = commit.author;
106
+ const commitCommitter = commit.committer;
107
+ const verification = commit.verification;
108
+ const author = data.author;
109
+ const committer = data.committer;
110
+ const parents = data.parents;
111
+ return {
112
+ sha: data.sha,
113
+ authorLogin: author?.login ?? null,
114
+ committerLogin: committer?.login ?? null,
115
+ authorEmail: commitAuthor.email ?? null,
116
+ authorDate: commitAuthor.date ?? null,
117
+ committerDate: commitCommitter?.date ?? null,
118
+ authorId: author?.id ?? null,
119
+ verificationReason: verification?.reason ?? null,
120
+ isRootCommit: Array.isArray(parents) && parents.length === 0,
121
+ message: (commit.message ?? "").split("\n")[0] ?? "",
122
+ verified: Boolean(verification?.verified)
123
+ };
124
+ }
125
+ //#endregion
126
+ //#region src/core/proof.ts
127
+ function classifyEra(proofDate) {
128
+ const cutoff = new Date(CUTOFF_DATE).getTime();
129
+ return new Date(proofDate).getTime() < cutoff ? "LAST_GEN" : "AI_NATIVE";
130
+ }
131
+ function resolveProofDate(user, firstCommit) {
132
+ if (!firstCommit) return (/* @__PURE__ */ new Date()).toISOString();
133
+ const effectiveDate = getEffectiveCommitDate(firstCommit);
134
+ const commitTime = new Date(effectiveDate).getTime();
135
+ const accountTime = new Date(user.createdAt).getTime();
136
+ if (commitTime < (firstCommit.repoCreatedAt ? new Date(firstCommit.repoCreatedAt).getTime() : Infinity)) return firstCommit.repoCreatedAt ?? user.createdAt;
137
+ return commitTime < accountTime ? effectiveDate : user.createdAt;
138
+ }
139
+ function getEffectiveCommitDate(commit) {
140
+ if (!commit.committerDate) return commit.date;
141
+ const authorTime = new Date(commit.date).getTime();
142
+ if (new Date(commit.committerDate).getTime() - authorTime > 2592e6) return commit.committerDate;
143
+ return commit.date;
144
+ }
145
+ async function generateCertificateHash(hashFn, username, githubId, proofDate, era) {
146
+ return hashFn(JSON.stringify({
147
+ username,
148
+ githubId,
149
+ proofDate,
150
+ era,
151
+ salt: CERTIFICATE_SALT
152
+ }));
153
+ }
154
+ function generateCertificateNumber(hash) {
155
+ const prefix = hash.slice(0, 4).toUpperCase();
156
+ const numericPart = parseInt(hash.slice(4, 12), 16) % 1e6;
157
+ return `LGC-${prefix}-${String(numericPart).padStart(6, "0")}`;
158
+ }
159
+ async function createCertificate(hashFn, user, firstCommit) {
160
+ const proofDate = resolveProofDate(user, firstCommit);
161
+ const era = classifyEra(proofDate);
162
+ const hash = await generateCertificateHash(hashFn, user.login, user.id, proofDate, era);
163
+ const certificateNumber = generateCertificateNumber(hash);
164
+ return {
165
+ version: "1.0",
166
+ type: "LASTGEN_CERTIFICATE",
167
+ identity: {
168
+ username: user.login,
169
+ githubId: user.id,
170
+ name: user.name
171
+ },
172
+ proof: {
173
+ accountCreated: user.createdAt,
174
+ firstCommit: firstCommit ?? {
175
+ date: user.createdAt,
176
+ repo: "",
177
+ sha: "",
178
+ message: "(no public commits found - using account creation date)"
179
+ },
180
+ proofDate
181
+ },
182
+ era,
183
+ verification: {
184
+ hash: `sha256:${hash}`,
185
+ salt: CERTIFICATE_SALT
186
+ },
187
+ certificateNumber,
188
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString()
189
+ };
190
+ }
191
+ //#endregion
192
+ //#region src/hash.ts
193
+ /**
194
+ * @fileoverview Node.js SHA-256 hash implementation using node:crypto.
195
+ */
196
+ const nodeHash = async (data) => {
197
+ return createHash("sha256").update(data).digest("hex");
198
+ };
199
+ //#endregion
200
+ //#region src/display.ts
201
+ /**
202
+ * @fileoverview Terminal output formatting with ASCII box-drawing for certificates and verification.
203
+ */
204
+ function shouldUseColor() {
205
+ if (process.env["NO_COLOR"] !== void 0) return false;
206
+ if (process.env["TERM"] === "dumb") return false;
207
+ if (process.argv.includes("--no-color")) return false;
208
+ return process.stdout.isTTY === true;
209
+ }
210
+ function style(format, text) {
211
+ if (!shouldUseColor()) return text;
212
+ return styleText(format, text);
213
+ }
214
+ function boxRule() {
215
+ return style("dim", "+" + "-".repeat(52) + "+");
216
+ }
217
+ function boxLine(content, rawLength) {
218
+ const pad = 50 - rawLength;
219
+ return style("dim", "|") + " " + content + " ".repeat(Math.max(pad, 0)) + " " + style("dim", "|");
220
+ }
221
+ function boxEmpty() {
222
+ return boxLine("", 0);
223
+ }
224
+ const LABEL_WIDTH = 13;
225
+ function labelLine(label, value, styledValue, lines) {
226
+ const max = 50 - LABEL_WIDTH;
227
+ if (value.length <= max) {
228
+ lines.push(boxLine(style("dim", label) + styledValue, LABEL_WIDTH + value.length));
229
+ return;
230
+ }
231
+ const indent = " ".repeat(LABEL_WIDTH);
232
+ let remaining = value;
233
+ let isFirst = true;
234
+ while (remaining.length > 0) {
235
+ const chunk = remaining.slice(0, max);
236
+ remaining = remaining.slice(max);
237
+ const prefix = isFirst ? style("dim", label) : indent;
238
+ lines.push(boxLine(prefix + chunk, LABEL_WIDTH + chunk.length));
239
+ isFirst = false;
240
+ }
241
+ }
242
+ function displayCertificate(cert) {
243
+ const out = process.stdout;
244
+ const isLastGen = cert.era === "LAST_GEN";
245
+ const eraInfo = ERAS[cert.era];
246
+ const lines = [];
247
+ lines.push(boxRule());
248
+ const title = style("bold", "LASTGEN CERTIFICATE");
249
+ const titlePadL = Math.floor(31 / 2);
250
+ const titlePadR = 31 - titlePadL;
251
+ lines.push(boxLine(" ".repeat(titlePadL) + title + " ".repeat(titlePadR), 50));
252
+ lines.push(boxRule());
253
+ labelLine("Certificate ", cert.certificateNumber, cert.certificateNumber, lines);
254
+ const issuedDate = new Date(cert.issuedAt).toISOString().slice(0, 10);
255
+ labelLine("Issued ", issuedDate, issuedDate, lines);
256
+ lines.push(boxEmpty());
257
+ const devValue = cert.identity.name ? `${cert.identity.username} (${cert.identity.name})` : cert.identity.username;
258
+ labelLine("Developer ", devValue, devValue, lines);
259
+ const eraColor = isLastGen ? "green" : "cyan";
260
+ labelLine("Era ", eraInfo.title, style(eraColor, eraInfo.title), lines);
261
+ labelLine(" ", eraInfo.description, style("dim", eraInfo.description), lines);
262
+ if (cert.proof.firstCommit.sha) {
263
+ lines.push(boxEmpty());
264
+ const commitDate = new Date(cert.proof.firstCommit.date).toISOString().slice(0, 10);
265
+ const repo = cert.proof.firstCommit.repo;
266
+ labelLine("Proof Commit ", repo, repo, lines);
267
+ const commitMsg = `${cert.proof.firstCommit.sha.slice(0, 7)} ${cert.proof.firstCommit.message.replace(/\n/g, " ")}`;
268
+ labelLine(" ", commitMsg, style("dim", commitMsg), lines);
269
+ labelLine("Commit Date ", commitDate, commitDate, lines);
270
+ }
271
+ lines.push(boxEmpty());
272
+ const hash = cert.verification.hash;
273
+ labelLine("Hash ", hash, style("dim", hash), lines);
274
+ lines.push(boxRule());
275
+ out.write("\n" + lines.join("\n") + "\n\n");
276
+ }
277
+ function displayBadgeMarkdown(cert) {
278
+ const out = process.stdout;
279
+ const badgeUrl = `https://img.shields.io/badge/lastgen-${cert.era === "LAST_GEN" ? "Last%20Gen" : "AI%20Native"}-${cert.era === "LAST_GEN" ? "blue" : "brightgreen"}?style=for-the-badge`;
280
+ out.write("\n");
281
+ out.write(style("bold", " Add to your GitHub README:") + "\n");
282
+ out.write("\n");
283
+ out.write(` [![Last Gen Coder](${badgeUrl})](https://github.com/pgagnidze/lastgen)\n`);
284
+ out.write("\n");
285
+ }
286
+ function displayJson(cert) {
287
+ process.stdout.write(JSON.stringify(cert, null, 2) + "\n");
288
+ }
289
+ function info(message) {
290
+ process.stderr.write(style("dim", ` ${message}`) + "\n");
291
+ }
292
+ function error(message) {
293
+ process.stderr.write(style("red", ` ${message}`) + "\n");
294
+ }
295
+ //#endregion
296
+ //#region src/core/verify.ts
297
+ function isValidCertificate(data) {
298
+ if (typeof data !== "object" || data === null) return false;
299
+ const cert = data;
300
+ return cert.type === "LASTGEN_CERTIFICATE" && typeof cert.version === "string" && typeof cert.identity === "object" && typeof cert.proof === "object" && typeof cert.verification === "object" && typeof cert.certificateNumber === "string";
301
+ }
302
+ function matchesNoreplyEmail(email, username) {
303
+ const lower = email.toLowerCase();
304
+ const user = username.toLowerCase();
305
+ return new RegExp(`^(\\d+\\+)?${escapeRegex(user)}@users\\.noreply\\.github\\.com$`).test(lower);
306
+ }
307
+ function escapeRegex(str) {
308
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
309
+ }
310
+ async function verifyCertificateData(cert, hashFn, token) {
311
+ const results = [];
312
+ const expectedHash = await generateCertificateHash(hashFn, cert.identity.username, cert.identity.githubId, cert.proof.proofDate, cert.era);
313
+ const actualHash = cert.verification.hash.replace("sha256:", "");
314
+ results.push({
315
+ check: "Hash integrity",
316
+ passed: expectedHash === actualHash,
317
+ detail: expectedHash === actualHash ? "Certificate hash is valid" : "Certificate hash does not match - data may have been tampered with"
318
+ });
319
+ const proofDate = new Date(cert.proof.proofDate);
320
+ const cutoff = new Date(CUTOFF_DATE);
321
+ const expectedEra = proofDate.getTime() < cutoff.getTime() ? "LAST_GEN" : "AI_NATIVE";
322
+ results.push({
323
+ check: "Era classification",
324
+ passed: cert.era === expectedEra,
325
+ detail: cert.era === expectedEra ? `Era ${cert.era} is correct for proof date ${cert.proof.proofDate}` : `Era should be ${expectedEra} but certificate claims ${cert.era}`
326
+ });
327
+ const expectedProofDate = resolveProofDate({
328
+ login: cert.identity.username,
329
+ id: cert.identity.githubId,
330
+ name: cert.identity.name,
331
+ createdAt: cert.proof.accountCreated
332
+ }, cert.proof.firstCommit.sha ? cert.proof.firstCommit : null);
333
+ const proofDateMatch = Math.abs(new Date(expectedProofDate).getTime() - proofDate.getTime()) < 6e4;
334
+ results.push({
335
+ check: "Proof date",
336
+ passed: proofDateMatch,
337
+ detail: proofDateMatch ? `Proof date ${cert.proof.proofDate} is consistent with commit and account data` : `Proof date should be ${expectedProofDate} but certificate claims ${cert.proof.proofDate}`
338
+ });
339
+ if (cert.proof.firstCommit.sha) try {
340
+ const commitDetail = await fetchCommit(cert.proof.firstCommit.repo, cert.proof.firstCommit.sha, token);
341
+ const username = cert.identity.username.toLowerCase();
342
+ const authorMatch = (commitDetail.authorLogin ?? "").toLowerCase() === username;
343
+ const committerMatch = (commitDetail.committerLogin ?? "").toLowerCase() === username;
344
+ const emailMatch = commitDetail.authorEmail ? matchesNoreplyEmail(commitDetail.authorEmail, cert.identity.username) : false;
345
+ const identityMatch = authorMatch || committerMatch || emailMatch;
346
+ const matchMethods = [];
347
+ if (authorMatch) matchMethods.push("author login");
348
+ if (committerMatch) matchMethods.push("committer login");
349
+ if (emailMatch) matchMethods.push("noreply email");
350
+ results.push({
351
+ check: "Identity",
352
+ passed: identityMatch,
353
+ detail: identityMatch ? `Matched via: ${matchMethods.join(", ")}` : `Commit author (${commitDetail.authorLogin}) does not match ${cert.identity.username}`
354
+ });
355
+ const isSelfOwned = (cert.proof.firstCommit.repo.split("/")[0] ?? "").toLowerCase() === cert.identity.username.toLowerCase();
356
+ results.push({
357
+ check: "Repo ownership",
358
+ passed: true,
359
+ detail: isSelfOwned ? `Commit is in a repo owned by ${cert.identity.username}` : `Commit is in a third-party repo (${cert.proof.firstCommit.repo})`
360
+ });
361
+ if (commitDetail.authorId !== null) {
362
+ const idMatch = commitDetail.authorId === cert.identity.githubId;
363
+ results.push({
364
+ check: "GitHub ID",
365
+ passed: idMatch,
366
+ detail: idMatch ? `GitHub ID ${commitDetail.authorId} matches certificate` : `Commit author ID ${commitDetail.authorId} does not match certificate ID ${cert.identity.githubId}`
367
+ });
368
+ }
369
+ const commitDate = new Date(commitDetail.authorDate ?? "");
370
+ const certDate = new Date(cert.proof.firstCommit.date);
371
+ const datesClose = Math.abs(commitDate.getTime() - certDate.getTime()) < 6e4;
372
+ results.push({
373
+ check: "Commit date",
374
+ passed: datesClose,
375
+ detail: datesClose ? `Commit date matches certificate (${commitDetail.authorDate})` : `Commit date ${commitDetail.authorDate} differs from certificate ${cert.proof.firstCommit.date}`
376
+ });
377
+ if (commitDetail.authorDate && commitDetail.committerDate) {
378
+ const authorTime = new Date(commitDetail.authorDate).getTime();
379
+ const committerTime = new Date(commitDetail.committerDate).getTime();
380
+ const driftMs = Math.abs(committerTime - authorTime);
381
+ const driftDays = Math.round(driftMs / (1440 * 60 * 1e3));
382
+ const consistent = driftMs <= THIRTY_DAYS_MS;
383
+ results.push({
384
+ check: "Date consistency",
385
+ passed: consistent,
386
+ detail: consistent ? `Author/committer date drift: ${driftDays}d (within 30d threshold)` : `Author/committer date drift: ${driftDays}d exceeds 30d - author date may be forged`
387
+ });
388
+ }
389
+ if (commitDetail.isRootCommit) results.push({
390
+ check: "Root commit",
391
+ passed: true,
392
+ detail: "Commit has no parents (first commit in repo - higher trust)"
393
+ });
394
+ if (commitDetail.verified) {
395
+ const reason = commitDetail.verificationReason;
396
+ const reasonDetail = reason && reason !== "valid" ? ` (${reason})` : "";
397
+ results.push({
398
+ check: "GPG signature",
399
+ passed: true,
400
+ detail: `Commit is GPG-signed${reasonDetail}`
401
+ });
402
+ }
403
+ } catch (fetchError) {
404
+ const message = fetchError instanceof Error ? fetchError.message : String(fetchError);
405
+ results.push({
406
+ check: "Commit verification",
407
+ passed: false,
408
+ detail: `Could not fetch commit from GitHub: ${message}`
409
+ });
410
+ }
411
+ return {
412
+ valid: results.every((r) => r.passed),
413
+ results,
414
+ certificateNumber: cert.certificateNumber,
415
+ username: cert.identity.username
416
+ };
417
+ }
418
+ //#endregion
419
+ //#region src/verify-cli.ts
420
+ /**
421
+ * @fileoverview CLI wrapper for certificate verification. Handles file I/O and display.
422
+ */
423
+ async function verifyCertificate(filePath, hashFn, token) {
424
+ let raw;
425
+ try {
426
+ raw = readFileSync(filePath, "utf-8");
427
+ } catch {
428
+ error(`Cannot read file: ${filePath}`);
429
+ return false;
430
+ }
431
+ let cert;
432
+ try {
433
+ cert = JSON.parse(raw);
434
+ } catch {
435
+ error("Invalid JSON in certificate file.");
436
+ return false;
437
+ }
438
+ if (!isValidCertificate(cert)) {
439
+ error("File is not a valid lastgen certificate.");
440
+ return false;
441
+ }
442
+ info(`Verifying certificate ${cert.certificateNumber}...`);
443
+ info(`Developer: ${cert.identity.username}`);
444
+ info("");
445
+ if (cert.proof.firstCommit.sha) info(`Fetching commit ${cert.proof.firstCommit.sha.slice(0, 7)} from GitHub...`);
446
+ const { valid, results } = await verifyCertificateData(cert, hashFn, token);
447
+ const out = process.stdout;
448
+ const lines = [];
449
+ lines.push(boxRule());
450
+ const title = style("bold", "VERIFICATION");
451
+ const titlePadL = Math.floor(38 / 2);
452
+ const titlePadR = 38 - titlePadL;
453
+ lines.push(boxLine(" ".repeat(titlePadL) + title + " ".repeat(titlePadR), 50));
454
+ lines.push(boxRule());
455
+ for (const result of results) {
456
+ const checkLine = `${result.passed ? style("green", "PASS") : style("red", "FAIL")} ${style("bold", result.check)}`;
457
+ lines.push(boxLine(checkLine, 6 + result.check.length));
458
+ const indent = 6;
459
+ const maxLen = 50 - indent;
460
+ let remaining = result.detail;
461
+ while (remaining.length > 0) {
462
+ const chunk = remaining.slice(0, maxLen);
463
+ remaining = remaining.slice(maxLen);
464
+ const line = " ".repeat(indent) + style("dim", chunk);
465
+ lines.push(boxLine(line, indent + chunk.length));
466
+ }
467
+ }
468
+ lines.push(boxRule());
469
+ if (valid) {
470
+ const msg = style("green", "Certificate is valid.");
471
+ lines.push(boxLine(msg, 21));
472
+ } else {
473
+ const msg = style("red", "Certificate verification failed.");
474
+ lines.push(boxLine(msg, 32));
475
+ }
476
+ lines.push(boxRule());
477
+ out.write("\n" + lines.join("\n") + "\n\n");
478
+ return valid;
479
+ }
480
+ //#endregion
481
+ //#region src/serve.ts
482
+ /**
483
+ * @fileoverview Static HTTP server for the pre-built web frontend.
484
+ * Zero dependencies — uses node:http and node:fs.
485
+ */
486
+ const MIME_TYPES = {
487
+ ".html": "text/html; charset=utf-8",
488
+ ".css": "text/css; charset=utf-8",
489
+ ".js": "application/javascript; charset=utf-8",
490
+ ".json": "application/json; charset=utf-8",
491
+ ".svg": "image/svg+xml",
492
+ ".png": "image/png",
493
+ ".ico": "image/x-icon"
494
+ };
495
+ function serve(port = 3e3) {
496
+ const distDir = join(dirname(fileURLToPath(import.meta.url)), "..", "web", "dist");
497
+ if (!existsSync(distDir)) {
498
+ process.stderr.write(` Web assets not found at ${distDir}\n Run "cd web && npm install && npm run build" first.
499
+ `);
500
+ process.exitCode = 1;
501
+ return;
502
+ }
503
+ createServer((req, res) => {
504
+ let urlPath = req.url ?? "/";
505
+ const qIndex = urlPath.indexOf("?");
506
+ if (qIndex !== -1) urlPath = urlPath.slice(0, qIndex);
507
+ if (urlPath.startsWith("/lastgen/")) urlPath = urlPath.slice(8);
508
+ if (urlPath === "/" || urlPath === "") urlPath = "/index.html";
509
+ const filePath = join(distDir, urlPath);
510
+ if (!filePath.startsWith(distDir)) {
511
+ res.writeHead(403);
512
+ res.end("Forbidden");
513
+ return;
514
+ }
515
+ try {
516
+ const content = readFileSync(filePath);
517
+ const contentType = MIME_TYPES[extname(filePath)] ?? "application/octet-stream";
518
+ res.writeHead(200, { "Content-Type": contentType });
519
+ res.end(content);
520
+ } catch {
521
+ try {
522
+ const indexContent = readFileSync(join(distDir, "index.html"));
523
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
524
+ res.end(indexContent);
525
+ } catch {
526
+ res.writeHead(404);
527
+ res.end("Not found");
528
+ }
529
+ }
530
+ }).listen(port, "127.0.0.1", () => {
531
+ process.stderr.write(`\n lastgen web UI running at http://localhost:${port}/\n\n`);
532
+ });
533
+ }
534
+ //#endregion
535
+ //#region src/cli.ts
536
+ /**
537
+ * @fileoverview CLI argument parsing and command routing using built-in parseArgs.
538
+ */
539
+ const pkg = createRequire(import.meta.url)("../package.json");
540
+ const buildInfo = [];
541
+ buildInfo.push(`commit: ${"389e4cd7746c6add8cd1a5fae255a7875a3a036d".substring(0, 7)}`);
542
+ buildInfo.push(`built: 2026-03-10T23:44:21+04:00`);
543
+ const buildString = buildInfo.length > 0 ? ` (${buildInfo.join(", ")})` : "";
544
+ const VERSION = `lastgen ${pkg.version}${buildString}`;
545
+ const HELP_BRIEF = `
546
+ _ _
547
+ | | __ _ ___ | |_ __ _ ___ _ __
548
+ | |/ _\` / __|| __/ _\` |/ _ \\ '_ \\
549
+ | | (_| \\__ \\| || (_| | __/ | | |
550
+ |_|\\__,_|___/ \\__\\__, |\\___|_| |_|
551
+ |___/
552
+ Check if you started coding before or after AI agents.
553
+
554
+ Usage:
555
+ lastgen <username> Classify a GitHub user
556
+ lastgen verify <file.json> Verify a saved certificate
557
+ lastgen serve [--port <port>] Launch web UI
558
+
559
+ Options:
560
+ --token <token> GitHub personal access token
561
+ --json Output as JSON
562
+ --badge Output as README badge markdown
563
+ --port <port> Port for web UI (default: 3000)
564
+ --no-color Disable colors
565
+ -h, --help Show this help
566
+ -v, --version Show version
567
+
568
+ Environment:
569
+ GITHUB_TOKEN GitHub token (alternative to --token)
570
+ NO_COLOR Disable colors (any value)
571
+
572
+ Examples:
573
+ npx lastgen torvalds
574
+ npx lastgen --json torvalds > proof.json
575
+ npx lastgen verify proof.json
576
+ npx lastgen --badge torvalds
577
+ npx lastgen serve
578
+ `;
579
+ function parseCli(argv) {
580
+ const { values, positionals } = parseArgs({
581
+ args: argv,
582
+ options: {
583
+ token: { type: "string" },
584
+ json: {
585
+ type: "boolean",
586
+ default: false
587
+ },
588
+ badge: {
589
+ type: "boolean",
590
+ default: false
591
+ },
592
+ port: { type: "string" },
593
+ help: {
594
+ type: "boolean",
595
+ short: "h",
596
+ default: false
597
+ },
598
+ version: {
599
+ type: "boolean",
600
+ short: "v",
601
+ default: false
602
+ },
603
+ "no-color": {
604
+ type: "boolean",
605
+ default: false
606
+ }
607
+ },
608
+ allowPositionals: true,
609
+ strict: false
610
+ });
611
+ const first = positionals[0] ?? "";
612
+ const isVerify = first === "verify";
613
+ return {
614
+ command: isVerify ? "verify" : first === "serve" ? "serve" : first ? "lookup" : "",
615
+ target: isVerify ? positionals[1] ?? "" : first,
616
+ token: values.token ?? process.env["GITHUB_TOKEN"],
617
+ port: Number(values.port) || 3e3,
618
+ json: Boolean(values.json),
619
+ badge: Boolean(values.badge),
620
+ help: Boolean(values.help),
621
+ version: Boolean(values.version)
622
+ };
623
+ }
624
+ async function run(argv) {
625
+ const opts = parseCli(argv);
626
+ if (opts.version) {
627
+ process.stdout.write(VERSION + "\n");
628
+ return;
629
+ }
630
+ if (opts.help || !opts.command) {
631
+ process.stdout.write(HELP_BRIEF);
632
+ return;
633
+ }
634
+ switch (opts.command) {
635
+ case "lookup":
636
+ await handleLookup(opts);
637
+ break;
638
+ case "verify":
639
+ await handleVerify(opts);
640
+ break;
641
+ case "serve":
642
+ serve(opts.port);
643
+ break;
644
+ default:
645
+ error(`Unknown command: ${opts.command}`);
646
+ process.stdout.write(HELP_BRIEF);
647
+ process.exitCode = 2;
648
+ }
649
+ }
650
+ async function handleLookup(opts) {
651
+ if (!opts.target) {
652
+ error("Username required. Usage: lastgen <username>");
653
+ process.exitCode = 2;
654
+ return;
655
+ }
656
+ if (!opts.json && !opts.badge) info(`Looking up ${opts.target} on GitHub...`);
657
+ const [user, firstCommit] = await Promise.all([fetchUser(opts.target, opts.token), fetchFirstCommit(opts.target, opts.token)]);
658
+ const cert = await createCertificate(nodeHash, user, firstCommit);
659
+ if (opts.badge) displayBadgeMarkdown(cert);
660
+ else if (opts.json) displayJson(cert);
661
+ else displayCertificate(cert);
662
+ }
663
+ async function handleVerify(opts) {
664
+ if (!opts.target) {
665
+ error("Certificate file required. Usage: lastgen verify <file.json>");
666
+ process.exitCode = 2;
667
+ return;
668
+ }
669
+ if (!await verifyCertificate(opts.target, nodeHash, opts.token)) process.exitCode = 1;
670
+ }
671
+ //#endregion
672
+ export { parseCli, run };