diff --git a/public/papercompute.svg b/public/papercompute.svg new file mode 100644 index 0000000..22976ee --- /dev/null +++ b/public/papercompute.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/tangled.svg b/public/tangled.svg new file mode 100644 index 0000000..8c5dd86 --- /dev/null +++ b/public/tangled.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/content/about/open-source.mdx b/src/content/about/open-source.mdx index 589c1e7..1d37b49 100644 --- a/src/content/about/open-source.mdx +++ b/src/content/about/open-source.mdx @@ -12,7 +12,7 @@ import { Icon } from "astro-icon/components"; - A custom third party Bluesky client with extra features like bookmarks and drafts - Implemented with SvelteKit, using libraries such as the AT Protocol SDK and TanStack Query -## Easytodo [](https://github.com/zeucapua/easytodo.link) +## Easytodo [Tangled Logo](https://github.com/zeucapua/easytodo.link) - An easy and local first todo list app, with per task stopwatch and other planned features - Implemented with SvelteKit, using a custom persistent local storage reactive function closure diff --git a/src/content/about/resume.mdx b/src/content/about/resume.mdx index c1b31c8..a250d7d 100644 --- a/src/content/about/resume.mdx +++ b/src/content/about/resume.mdx @@ -84,7 +84,18 @@ _Released August 2024_ # Contract Work -## Consultant Software Engineer, Zuplo [](https://zuplo.com) +## Software Engineering Consultant, Paper Compute Company [](https://papercompute.com) +_March 2026 - May 2026_ +- Implemented a cloud web platform that allows organization customers to manage their engineer's AI agent usage, +from the prompts written and results generated, to token usage and the total cost. +- Technology Stack: TanStack Start, TanStack Query, React, Typescript + + Paper Compute Logo + papercompute.com + + + +## Software Engineering Consultant, Zuplo [](https://zuplo.com) _November 2025 - December 2025_ - Implemented a proof of concept for `zudoku schema generate example` CLI command, generates examples for response and request body schemas without explicit examples given an OpenAPI JSON file diff --git a/src/lib/atf.ts b/src/lib/atf.ts new file mode 100644 index 0000000..fb50fb0 --- /dev/null +++ b/src/lib/atf.ts @@ -0,0 +1,32 @@ +import { buildAtUri, getDistinctDids, getRecord, resolveHandle, type MiniDoc } from "./utils"; + +type ATFProfileResult = { + avatar: { ref: { $link: string }}; + displayName: string; +} + +export async function getATFProfile(did: string): Promise { + const profile = await getRecord(buildAtUri(did, "com.atprotofans.profile", "self")); + return profile.value; +} + +type GetSupportersResult = { + total: number; + list: MiniDoc[]; +} + +export async function getSupporters(did: string): Promise { + const { total, linking_dids } = await getDistinctDids(did, "com.atprotofans.supporter", ".subject"); + console.log(linking_dids); + + const list = []; + for (const did of linking_dids) { + const miniDoc = await resolveHandle(did); + const profile = await getATFProfile(did); + miniDoc.avatar = `${miniDoc.pds}/xrpc/com.atproto.sync.getBlob?did=${miniDoc.did}&cid=${profile.avatar.ref.$link}`; + miniDoc.displayName = profile.displayName; + list.push(miniDoc); + } + + return { total, list }; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..42b2bf9 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,80 @@ +export const CONSTELLATION = "https://constellation.microcosm.blue"; +export const SLINGSHOT = "https://slingshot.microcosm.blue"; + +export type Ref = { + uri: string; + cid: string; +} + +export type MiniDoc = { + did: string; + handle: string; + pds: string; + signing_key: string; + avatar?: string; + displayName?: string; +}; + +export type ConstellationRecords = { + did: string; + collection: string; + rkey: string; +}[]; + +export function buildAtUri(did: string, collection: string, rkey: string) { + return `at://${did}/${collection}/${rkey}`; +} + +export async function getBacklinks(subject: string, source: string) { + const response = await fetch(`${CONSTELLATION}/xrpc/blue.microcosm.getBacklinks?subject=${subject}&source=${encodeURIComponent(source)}`, { + headers: { "Accept": "application/json" } + }); + + const { total, cursor, records } = await response.json() as { + total: number, + cursor: string | null, + records: ConstellationRecords + }; + + return { total, cursor, records }; +} + +export type GetDistinctDidsResult = { + total: number; + linking_dids: string[]; + cursor: string | null; +} + +export async function getDistinctDids(target: string, collection: string, path: string) { + const url = new URL(`${CONSTELLATION}/links/distinct-dids`); + url.searchParams.set("target", target); + url.searchParams.set("collection", collection); + url.searchParams.set("path", path); + + const response = await fetch(url); + const json = await response.json() as GetDistinctDidsResult; + return json; +} + +export async function getRecord(at_uri: string) { + const response = await fetch(`${SLINGSHOT}/xrpc/blue.microcosm.repo.getRecordByUri?at_uri=${at_uri}`); + const json = await response.json() as Ref & { value: T }; + + return json; +} + +let handleCache: Record = {}; + +export async function resolveHandle(handle: string) { + if (Object.keys(handleCache).includes(handle)) { + return handleCache[handle]; + } + + const url = new URL(`${SLINGSHOT}/xrpc/blue.microcosm.identity.resolveMiniDoc`); + url.searchParams.set("identifier", handle); + + const response = await fetch(url); + const miniDoc = await response.json() as MiniDoc; + + return miniDoc; +} diff --git a/src/pages/as-seen-on.astro b/src/pages/as-seen-on.astro index 029d870..8c07954 100644 --- a/src/pages/as-seen-on.astro +++ b/src/pages/as-seen-on.astro @@ -3,6 +3,8 @@ import { render } from "astro:content"; import SiteLayout from "../components/SiteLayout.astro"; import { getEntry } from "astro:content"; +export const prerender = true; + const entry = await getEntry("about", "as-seen-on"); if (!entry) { throw new Error("Could not find ../content/about/as-seen-on.mdx"); diff --git a/src/pages/index.astro b/src/pages/index.astro index e16b67b..90c6420 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,5 +1,8 @@ --- import SiteLayout from "../components/SiteLayout.astro"; +import { getSupporters } from "../lib/atf"; + +const { list: supporters } = await getSupporters("did:plc:gotnvwkr56ibs33l4hwgfoet"); --- @@ -36,5 +39,17 @@ import SiteLayout from "../components/SiteLayout.astro";

Career and professional work

+ +
+

+ Thank you to my Supporters +

+ { supporters.map((profile) => ( +
+ + {profile.displayName ?? profile.handle} +
+ ))} +
diff --git a/src/pages/open-source.astro b/src/pages/open-source.astro index c99c097..d33e9d7 100644 --- a/src/pages/open-source.astro +++ b/src/pages/open-source.astro @@ -2,6 +2,8 @@ import SiteLayout from "../components/SiteLayout.astro"; import { getEntry } from "astro:content"; +export const prerender = true; + const entry = await getEntry("about", "open-source"); if (!entry) { throw new Error("Could not find ../content/about/open-source.mdx"); diff --git a/src/pages/resume.astro b/src/pages/resume.astro index 20f5d8b..4a7b771 100644 --- a/src/pages/resume.astro +++ b/src/pages/resume.astro @@ -2,6 +2,8 @@ import SiteLayout from "../components/SiteLayout.astro"; import { getEntry } from "astro:content"; +export const prerender = true; + const entry = await getEntry("about", "resume"); if (!entry) { throw new Error("Could not find ../content/about/resume.mdx");