GitHub profile summary
What a developer works with, measured instead of guessed: their public GitHub profile and repository list are read in a cloud browser, then their top repositories are cloned in a session's shell and measured there (lines of code per language, frameworks and libraries from the manifest files, CI, containers and infrastructure).
When to use it: You want what a developer works with, measured from their repositories, not guessed.
| Uses | extract over two pages, a shell session (exec, files) |
|---|---|
| Needs | shell sessions and a model on the API (no keys of your own) |
| Site | github.com (public profile and repositories only) |
| Output | output/summary.md, output/result.json |
| Languages | Node and Python |
How it works:
- In a sandboxed browser: one
extractcall reads the profile and the repository list sorted by stars: name, bio, location, followers and each repository's name, address, description, language, stars and whether it is a fork. - On the machine:
stack.pyshallow-clones the topTOP_REPOSof the user's own repositories (forks left out) and counts non-empty lines per language by file type, readspackage.json,requirements.txt,pyproject.toml,go.mod,Cargo.toml,Gemfileandcomposer.jsonfor well-known frameworks, and notes GitHub Actions, Docker, Terraform and Make. Nothing runs on your computer.
Use it on public profiles for things like knowing a project's maintainers better or summing up your own work; it reads what anyone can see and clones only public repositories.
The code
The example as it is in its folder. Both versions do the same job and write the same output/, so one result check covers both.
/** * GitHub profile summary: what a developer works with, measured, not guessed. Their public profile and repository * list are read in a sandboxed browser; their top repositories are cloned in the session's shell and measured there * (lines of code per language, frameworks from the manifests, CI and containers). * * npx tsx node/index.ts (BOXLINE_API_KEY; GITHUB_USER, TOP_REPOS; a plan with shell sessions) * * Public data only: the profile page, and repositories anyone can clone. Writes output/result.json and output/summary.md. */import { mkdirSync, readFileSync, writeFileSync } from "node:fs";import { dirname, join } from "node:path";import { fileURLToPath } from "node:url";import { Boxline } from "@boxline/sdk";const here = dirname(fileURLToPath(import.meta.url));const out = process.env.OUTPUT_DIR ?? "output";const user = process.env.GITHUB_USER ?? "octocat";const base = (process.env.GITHUB_URL ?? "https://github.com").replace(/\/$/, "");const topN = Number(process.env.TOP_REPOS ?? 3);const bx = new Boxline();type Repo = { name: string; url: string; description: string | null; language: string | null; stars: number; fork: boolean };type Profile = { name: string | null; bio: string | null; location: string | null; followers: number | null; repos: Repo[] };type Measured = { url: string; name: string; error?: string; linesByLanguage?: Record<string, number>; frameworks?: string[]; tools?: string[]; manifests?: string[] };// 1. The profile and its repositories, sorted by stars (two pages read in one call).const r = await bx.extract<Profile>({ urls: [`${base}/${user}`, `${base}/${user}?tab=repositories&type=source&sort=stargazers`], prompt: `The GitHub user ${user}: their name, bio, location and followers from the profile, and the repositories listed (name, full https URL, description, main language, stars, whether it is a fork).`, schema: { type: "object", properties: { name: { type: ["string", "null"] }, bio: { type: ["string", "null"] }, location: { type: ["string", "null"] }, followers: { type: ["integer", "null"] }, repos: { type: "array", items: { type: "object", properties: { name: { type: "string" }, url: { type: "string" }, description: { type: ["string", "null"] }, language: { type: ["string", "null"] }, stars: { type: "integer" }, fork: { type: "boolean" } }, required: ["name", "url", "description", "language", "stars", "fork"], }, }, }, required: ["name", "bio", "location", "followers", "repos"], },});const profile = r.data;// Their own work: forks left out, most stars first; only addresses of this user's repositories are cloned.const own = profile.repos.filter((x) => !x.fork && x.url.startsWith(`${base}/${user}/`)).sort((a, b) => b.stars - a.stars);const top = own.slice(0, topN);console.log(`${profile.name ?? user}: ${profile.repos.length} repositories listed, ${own.length} of their own; measuring ${top.map((x) => x.name).join(", ")}`);if (!top.length) throw new Error(`no public repositories of ${user} to measure`);// 2. Clone and measure them on the machine (nothing runs on this computer).const session = await bx.sessions.create({ browser: false, shell: true, timeout: 600, idleTimeout: 300, userMetadata: { example: "github-profile-summary" } });console.log(`Session: ${session.id}`);let stack: { repos: Measured[]; linesByLanguage: Record<string, number> };try { await session.files.write("stack.py", readFileSync(join(here, "../stack.py"))); const run = await session.exec("python stack.py", { env: { REPOS: top.map((x) => x.url).join(",") }, timeoutMs: 600_000 }); if (run.exitCode !== 0) throw new Error(`stack.py failed: ${run.stderr.trim()}`); console.log(run.stdout.trim()); stack = JSON.parse(await session.files.readText("stack.json"));} finally { await session.stop();}const total = Object.values(stack.linesByLanguage).reduce((a, b) => a + b, 0);const languages = Object.entries(stack.linesByLanguage).map(([language, lines]) => ({ language, lines, share: total ? Math.round((lines / total) * 1000) / 10 : 0 }));const frameworks = [...new Set(stack.repos.flatMap((x) => x.frameworks ?? []))].sort();const tools = [...new Set(stack.repos.flatMap((x) => x.tools ?? []))].sort();const repos = top.map((t) => ({ ...t, measured: stack.repos.find((m) => m.url === t.url) ?? null }));const md = [ `# ${profile.name ?? user} (${user})`, "", [profile.bio, profile.location, profile.followers !== null ? `${profile.followers} followers` : null].filter(Boolean).join(" · "), "", `## Languages (lines of code in ${repos.length} top repositories)`, "", ...(languages.length ? languages.map((l) => `- ${l.language}: ${l.lines} lines (${l.share}%)`) : ["- no code files"]), "", `**Frameworks and libraries:** ${frameworks.join(", ") || "none found in manifests"}`, "", `**Tools:** ${tools.join(", ") || "none found"}`, "", "## Repositories", "", ...repos.map((x) => `- [${x.name}](${x.url}) (${x.stars} stars): ${x.description ?? "no description"}${x.measured?.error ? ` (could not be cloned)` : ""}`), "",].join("\n");mkdirSync(out, { recursive: true });writeFileSync(join(out, "summary.md"), md);writeFileSync(join(out, "result.json"), JSON.stringify({ user, profile: { ...profile, repos: undefined }, listed: profile.repos.length, languages, frameworks, tools, repos, usage: { modelUsd: r.usage.costUsd } }, null, 2));console.log(`\n${md}`);"""GitHub profile summary: what a developer works with, measured, not guessed. Their public profile and repositorylist are read in a sandboxed browser; their top repositories are cloned in the session's shell and measured there(lines of code per language, frameworks from the manifests, CI and containers). python python/main.py (BOXLINE_API_KEY; GITHUB_USER, TOP_REPOS; a plan with shell sessions)Public data only: the profile page, and repositories anyone can clone. Writes output/result.json and output/summary.md."""import jsonimport osfrom pathlib import Pathfrom boxline import Boxlinehere = Path(__file__).resolve().parentout = Path(os.environ.get("OUTPUT_DIR", "output"))user = os.environ.get("GITHUB_USER", "octocat")base = os.environ.get("GITHUB_URL", "https://github.com").rstrip("/")top_n = int(os.environ.get("TOP_REPOS", "3"))bx = Boxline()# 1. The profile and its repositories, sorted by stars (two pages read in one call).r = bx.extract( urls=[f"{base}/{user}", f"{base}/{user}?tab=repositories&type=source&sort=stargazers"], prompt=f"The GitHub user {user}: their name, bio, location and followers from the profile, and the repositories listed (name, full https URL, description, main language, stars, whether it is a fork).", schema={ "type": "object", "properties": { "name": {"type": ["string", "null"]}, "bio": {"type": ["string", "null"]}, "location": {"type": ["string", "null"]}, "followers": {"type": ["integer", "null"]}, "repos": { "type": "array", "items": { "type": "object", "properties": {"name": {"type": "string"}, "url": {"type": "string"}, "description": {"type": ["string", "null"]}, "language": {"type": ["string", "null"]}, "stars": {"type": "integer"}, "fork": {"type": "boolean"}}, "required": ["name", "url", "description", "language", "stars", "fork"], }, }, }, "required": ["name", "bio", "location", "followers", "repos"], },)profile = r["data"]# Their own work: forks left out, most stars first; only addresses of this user's repositories are cloned.own = sorted((x for x in profile["repos"] if not x["fork"] and x["url"].startswith(f"{base}/{user}/")), key=lambda x: -x["stars"])top = own[:top_n]print(f"{profile['name'] or user}: {len(profile['repos'])} repositories listed, {len(own)} of their own; measuring {', '.join(x['name'] for x in top)}")if not top: raise SystemExit(f"no public repositories of {user} to measure")# 2. Clone and measure them on the machine (nothing runs on this computer).with bx.sessions.create(browser=False, shell=True, timeout=600, idle_timeout=300, user_metadata={"example": "github-profile-summary"}) as session: print(f"Session: {session.id}", flush=True) session.files.write("stack.py", (here.parent / "stack.py").read_bytes()) run = session.exec("python stack.py", env={"REPOS": ",".join(x["url"] for x in top)}, timeout_ms=600_000) if run["exitCode"] != 0: raise SystemExit(f"stack.py failed: {run['stderr'].strip()}") print(run["stdout"].strip()) stack = json.loads(session.files.read_text("stack.json"))total = sum(stack["linesByLanguage"].values())languages = [{"language": k, "lines": v, "share": round(v / total * 100, 1) if total else 0} for k, v in stack["linesByLanguage"].items()]frameworks = sorted({f for x in stack["repos"] for f in x.get("frameworks", [])})tools = sorted({t for x in stack["repos"] for t in x.get("tools", [])})repos = [{**t, "measured": next((m for m in stack["repos"] if m["url"] == t["url"]), None)} for t in top]about = " · ".join(str(v) for v in [profile["bio"], profile["location"], f"{profile['followers']} followers" if profile["followers"] is not None else None] if v)md = [f"# {profile['name'] or user} ({user})", "", about, "", f"## Languages (lines of code in {len(repos)} top repositories)", ""]md += [f"- {l['language']}: {l['lines']} lines ({l['share']}%)" for l in languages] or ["- no code files"]md += ["", f"**Frameworks and libraries:** {', '.join(frameworks) or 'none found in manifests'}", "", f"**Tools:** {', '.join(tools) or 'none found'}", "", "## Repositories", ""]md += [f"- [{x['name']}]({x['url']}) ({x['stars']} stars): {x['description'] or 'no description'}" + (" (could not be cloned)" if (x["measured"] or {}).get("error") else "") for x in repos] + [""]md = "\n".join(md)out.mkdir(parents=True, exist_ok=True)(out / "summary.md").write_text(md)result = {"user": user, "profile": {k: v for k, v in profile.items() if k != "repos"}, "listed": len(profile["repos"]), "languages": languages, "frameworks": frameworks, "tools": tools, "repos": repos, "usage": {"modelUsd": r["usage"]["costUsd"]}}(out / "result.json").write_text(json.dumps(result, indent=2, ensure_ascii=False))print(f"\n{md}")"""Runs in the session's shell: clones each repository in REPOS (comma-separated https URLs, shallow) and measures whatit is really made of: lines of code per language (by file type), frameworks and libraries from its manifest files,and tools (CI, containers, infrastructure). Writes /workspace/stack.json."""import jsonimport osimport reimport subprocessfrom collections import Counterfrom pathlib import PathLANG = { ".py": "Python", ".ts": "TypeScript", ".tsx": "TypeScript", ".js": "JavaScript", ".jsx": "JavaScript", ".mjs": "JavaScript", ".cjs": "JavaScript", ".go": "Go", ".rs": "Rust", ".java": "Java", ".kt": "Kotlin", ".rb": "Ruby", ".php": "PHP", ".c": "C", ".h": "C", ".cc": "C++", ".cpp": "C++", ".hpp": "C++", ".cs": "C#", ".swift": "Swift", ".m": "Objective-C", ".scala": "Scala", ".sh": "Shell", ".html": "HTML", ".css": "CSS", ".scss": "CSS", ".vue": "Vue", ".svelte": "Svelte", ".dart": "Dart", ".ex": "Elixir", ".exs": "Elixir", ".lua": "Lua", ".r": "R", ".jl": "Julia", ".zig": "Zig", ".sql": "SQL",}# Libraries worth naming when a manifest lists them (the rest are counted, not named).KNOWN = { "react", "next", "vue", "nuxt", "svelte", "@sveltejs/kit", "angular", "@angular/core", "express", "fastify", "koa", "nestjs", "@nestjs/core", "electron", "vite", "webpack", "jest", "vitest", "mocha", "playwright", "@playwright/test", "puppeteer", "tailwindcss", "prisma", "typeorm", "django", "flask", "fastapi", "pandas", "numpy", "torch", "tensorflow", "scikit-learn", "pytest", "requests", "sqlalchemy", "pydantic", "rails", "sinatra", "rspec", "laravel/framework", "symfony/symfony", "spring-boot", "gin-gonic/gin", "gorilla/mux", "tokio", "serde", "actix-web", "axum", "clap",}SKIP = {".git", "node_modules", "vendor", "dist", "build", ".venv", "venv", "__pycache__", "target"}def files(root): for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = [d for d in dirnames if d not in SKIP] for f in filenames: yield Path(dirpath) / fdef deps(path): name, text = path.name, path.read_text(errors="ignore") if name == "package.json": try: j = json.loads(text) return [*j.get("dependencies", {}), *j.get("devDependencies", {})] except ValueError: return [] if name in ("requirements.txt", "requirements-dev.txt"): return [re.split(r"[<>=~!\[ ;]", line.strip())[0].lower() for line in text.splitlines() if line.strip() and not line.startswith(("#", "-"))] if name == "pyproject.toml": return [m.lower() for m in re.findall(r'^\s*"?([A-Za-z0-9_.-]+)\s*[<>=~!"\[]', text, re.M)] if name == "go.mod": return [m.split("/", 1)[1] if m.startswith("github.com/") else m for m in re.findall(r"^\s*(?:require\s+)?([\w.\-/]+)\s+v\d", text, re.M)] if name == "Cargo.toml": block = text.split("[dependencies]", 1)[1].split("\n[", 1)[0] if "[dependencies]" in text else "" return re.findall(r"^\s*([A-Za-z0-9_-]+)\s*=", block, re.M) if name == "Gemfile": return re.findall(r"""^\s*gem\s+['"]([^'"]+)""", text, re.M) if name == "composer.json": try: return list(json.loads(text).get("require", {})) except ValueError: return [] return []repos = []for url in [u.strip() for u in os.environ.get("REPOS", "").split(",") if u.strip()]: name = url.rstrip("/").split("/")[-1].removesuffix(".git") dest = Path("/workspace/repos") / name r = subprocess.run(["git", "clone", "--depth", "1", "--quiet", url, str(dest)], capture_output=True, text=True, timeout=180) if r.returncode != 0: repos.append({"url": url, "name": name, "error": r.stderr.strip()[-300:]}) continue lines, libs, tools, manifests = Counter(), set(), set(), [] for f in files(dest): rel = f.relative_to(dest) lang = LANG.get(f.suffix.lower()) if lang and f.stat().st_size < 2_000_000: lines[lang] += sum(1 for line in f.open(errors="ignore") if line.strip()) if f.name in ("package.json", "requirements.txt", "requirements-dev.txt", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "composer.json") and "node_modules" not in rel.parts: manifests.append(str(rel)) libs |= {d for d in deps(f) if d.lower() in KNOWN} if rel.parts[:2] == (".github", "workflows"): tools.add("GitHub Actions") if f.name in ("Dockerfile", "docker-compose.yml", "compose.yaml"): tools.add("Docker") if f.suffix == ".tf": tools.add("Terraform") if f.name == "Makefile": tools.add("Make") repos.append({"url": url, "name": name, "linesByLanguage": dict(lines.most_common()), "frameworks": sorted(libs), "tools": sorted(tools), "manifests": manifests})total = Counter()for r in repos: total.update(r.get("linesByLanguage", {}))json.dump({"repos": repos, "linesByLanguage": dict(total.most_common())}, open("/workspace/stack.json", "w"), indent=1)for r in repos: print(f"{r['name']}: {r.get('error') or ', '.join(f'{k} {v}' for k, v in list(r['linesByLanguage'].items())[:4]) or 'no code files'}")Inputs
Environment variables the example reads:
GITHUB_USER(defaultoctocat),TOP_REPOS(default 3).
Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/github-profile-summary. Put your API key in BOXLINE_API_KEY (and BOXLINE_API_URL for another API), then run either version from the example’s folder. Both write to output/ and stop their sessions when they finish.
# Node 18 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keynpm install @boxline/sdk tsxnpx tsx node/index.ts# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdkpython python/main.pyThen check the result, from the same folder: npx tsx check.ts output. It exits with 0 only when the result is right.
What its check proves
check.ts reads what the example wrote and passes only when the result is right, not merely when the example ran.
Every measured repository is the user's own (not a fork), at least one was cloned, lines of code were counted and the language shares add up to 100%. For octocat: HTML and CSS, as their top repositories are web pages.
Source
github.com/Boxline-dev/examples/github-profile-summary on GitHub, with its README.md, check.ts and .env.example. The examples are open source. See all examples.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.