JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 42
  • Score
    100M100P100Q73984F
  • License MIT

Your portfolio answers questions. Define a KB, render a live persona.

Package Exports

  • livecv
  • livecv/styles.css
  • livecv/theme.css

Readme

livecv

livecv

Your portfolio answers questions.

Define a KB. Render a live persona.

npm version license

Demo · Example · npm · Issues


What is it

Static portfolios show every visitor the same page. livecv turns a typed config + markdown KB into a generative-UI portfolio: a visitor types a question, an LLM picks components from a Zod-typed catalog, and the page assembles itself live as the answer.

Same site, different shape per visitor. A recruiter sees the patent and the metrics. A friend sees the writing. A founding-team CEO sees the agent-infrastructure thinking.

Built on top of @json-render (the streaming renderer + Zod catalog runtime) and Anthropic's Claude.

Install

npm install livecv \
  @ai-sdk/anthropic @ai-sdk/react ai \
  @json-render/core @json-render/react @json-render/shadcn \
  motion lucide-react zod

Four files. That's the whole user surface.

my-portfolio/
├── livecv.config.ts          — typed config (identity, career, projects, blog)
├── content/kb.md             — prose KB (voice, principles, what you're focused on)
├── app/page.tsx              — <PersonaSite config={config} />
└── app/api/generate/route.ts — export const POST = createHandler({ config })

Quick start

1. Config — assemble your persona with the typed constructors:

// livecv.config.ts
import fs from "node:fs";
import path from "node:path";
import {
  defineConfig,
  defineIdentity,
  defineCareer,
  defineProjects,
  defineClusters,
  defineBlog,
} from "livecv";

const identity = defineIdentity({
  name: "Jane Doe",
  role: "Software Engineer",
  bio: "...",
  email: "jane@example.com",
  links: { github: "https://github.com/janedoe" },
});

const career = defineCareer([
  { start: "2024", end: "Present", title: "Senior Engineer", org: "Foo",
    detail: "...", tech: ["TypeScript"], status: "current" },
]);

const projects = defineProjects([
  { id: "thing", title: "thing", tldr: "...", description: "...",
    tech: ["TypeScript"], cluster: "tools",
    howItWorks: ["..."], keyInsight: "..." },
]);

const clusters = defineClusters([{ id: "tools", label: "Tools" }]);
const blog = defineBlog([]);

export default defineConfig({
  identity, career, projects, clusters, blog,
  kbContent: fs.readFileSync(path.join(process.cwd(), "content/kb.md"), "utf-8"),
  model: "claude-haiku-4-5",
});

2. Page — five lines:

// app/page.tsx
import { PersonaSite } from "livecv";
import config from "@/livecv.config";

export default function Page() {
  return <PersonaSite config={config} />;
}

3. API route — five lines:

// app/api/generate/route.ts
import { createHandler } from "livecv";
import config from "@/livecv.config";

export const POST = createHandler({ config });
export const maxDuration = 60;

4. Styles — three imports:

/* app/globals.css */
@import "tailwindcss";
@import "livecv/styles.css";
@import "livecv/theme.css";

5. Env — one variable:

# .env.local
ANTHROPIC_API_KEY=sk-ant-...

npm run dev. You have a live persona portfolio.

What renders

The LLM picks from this catalog per question:

Component When it renders
ProfileBlock "who are you", "about"
CareerTimeline "career", "background" — supports focus + highlightTech for topical lensing
ProjectGrid "show me your X work" — filtered by cluster
ProjectDeepDive specific project by id
TechStack tag cloud sized by frequency
BlogList / BlogPostCard your writing
PrincipleList philosophy questions only
LeadershipStories STAR-format experience stories — fresh per question
ProofPoints tag + claim + evidence rows
PersonalNote one-sentence first-person opener
Callout / Text / Link prose primitives

Project IDs, blog slugs, and cluster IDs become Zod enums automatically — the LLM cannot reference an artifact that doesn't exist. Bad specs fail validation and the renderer falls back gracefully.

Example

A complete starter template lives in example/ — a fictional "Jordan Lee, Staff Engineer" persona with 3 career steps, 4 projects across 3 clusters, 3 blog posts, and a prose KB. Clone, replace data, deploy.

cd example
cp .env.local.example .env.local   # paste your Anthropic key
npm install
npm run dev

Theme

Override any CSS variable from livecv/theme.css in your own stylesheet:

:root {
  --lc-foreground: #000;
  --lc-background: #fff;
  /* ... */
}

Dark mode = .dark class on <html> (or any ancestor).

Reference implementation

ai.sayantan.sh is the real-world site this was extracted from. Same code, real persona. The companion blog post walks through the architecture decisions.

License

MIT © Sayantan Bhowmik