",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/transitive-bullshit/agentic.git",
+ "directory": "packages/gravatar"
+ },
+ "type": "module",
+ "source": "./src/index.ts",
+ "types": "./dist/index.d.ts",
+ "sideEffects": false,
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js",
+ "default": "./dist/index.js"
+ }
+ },
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsup --config ../../tsup.config.ts",
+ "dev": "tsup --config ../../tsup.config.ts --watch",
+ "clean": "del dist",
+ "test": "run-s test:*",
+ "test:lint": "eslint .",
+ "test:typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@agentic/core": "workspace:*",
+ "ky": "^1.7.5",
+ "p-throttle": "^6.2.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.24.2"
+ },
+ "devDependencies": {
+ "@agentic/tsconfig": "workspace:*"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/gravatar/readme.md b/packages/gravatar/readme.md
new file mode 100644
index 0000000..38781f3
--- /dev/null
+++ b/packages/gravatar/readme.md
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+ AI agent stdlib that works with any LLM and TypeScript AI SDK.
+
+
+
+
+
+
+
+
+
+# Agentic
+
+**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
+
+## License
+
+MIT © [Travis Fischer](https://x.com/transitive_bs)
diff --git a/packages/gravatar/src/gravatar-client.ts b/packages/gravatar/src/gravatar-client.ts
new file mode 100644
index 0000000..6f51fd9
--- /dev/null
+++ b/packages/gravatar/src/gravatar-client.ts
@@ -0,0 +1,148 @@
+import crypto from 'node:crypto'
+
+import {
+ aiFunction,
+ AIFunctionsProvider,
+ getEnv,
+ throttleKy
+} from '@agentic/core'
+import defaultKy, { type KyInstance } from 'ky'
+import pThrottle from 'p-throttle'
+import z from 'zod'
+
+export namespace gravatar {
+ export const API_BASE_URL = 'https://api.gravatar.com'
+
+ // Allow up to 100 unauthenticated requests per hour by default.
+ export const unauthenticatedThrottle = pThrottle({
+ limit: 100,
+ interval: 60 * 60 * 1000
+ })
+
+ // Allow up to 1000 authenticated requests per hour by default.
+ export const authenticatedThrottle = pThrottle({
+ limit: 1000,
+ interval: 60 * 60 * 1000
+ })
+
+ export type GetProfileByIdentifierOptions = {
+ email: string
+ }
+
+ export interface Profile {
+ /** The SHA256 hash of the user’s primary email address. */
+ hash: string
+ /** The user’s display name that appears on their profile. */
+ display_name: string
+ /** The full URL to the user’s Gravatar profile. */
+ profile_url: string
+ /** The URL to the user’s avatar image, if set. */
+ avatar_url: string
+ /** Alternative text describing the user’s avatar. */
+ avatar_alt_text: string
+ /** The user’s geographical location. */
+ location: string
+ /** A short biography or description about the user found on their profile. */
+ description: string
+ /** The user’s current job title. */
+ job_title: string
+ /** The name of the company where the user is employed. */
+ company: string
+ /** An array of verified accounts the user has added to their profile. The number of verified accounts displayed is limited to a maximum of 4 in unauthenticated requests. */
+ verified_accounts: any[]
+ /** A phonetic guide to pronouncing the user’s name. */
+ pronunciation: string
+ /** The pronouns the user prefers to use. */
+ pronouns: string
+
+ /** The total number of verified accounts the user has added to their profile, including those not displayed on their profile. This property is only provided in authenticated API requests. */
+ number_verified_accounts?: number
+
+ /** The date and time (UTC) when the user last edited their profile. This property is only provided in authenticated API requests. Example: "2021-10-01T12:00:00Z" */
+ last_profile_edit?: string
+
+ /** The date the user registered their account. This property is only provided in authenticated API requests. Example: "2021-10-01" */
+ registration_date?: string
+ }
+}
+
+/**
+ * A client for the Gravatar API.
+ *
+ * API key is optional.
+ *
+ * @see https://docs.gravatar.com/getting-started/
+ */
+export class GravatarClient extends AIFunctionsProvider {
+ protected readonly ky: KyInstance
+ protected readonly apiKey?: string
+ protected readonly apiBaseUrl: string
+
+ constructor({
+ apiKey = getEnv('GRAVATAR_API_KEY'),
+ apiBaseUrl = gravatar.API_BASE_URL,
+ timeoutMs = 60_000,
+ throttle = true,
+ ky = defaultKy
+ }: {
+ apiKey?: string
+ apiBaseUrl?: string
+ timeoutMs?: number
+ throttle?: boolean
+ ky?: KyInstance
+ } = {}) {
+ super()
+
+ // API key is optional
+ this.apiKey = apiKey
+ this.apiBaseUrl = apiBaseUrl
+
+ const throttledKy = throttle
+ ? throttleKy(
+ ky,
+ apiKey
+ ? gravatar.authenticatedThrottle
+ : gravatar.unauthenticatedThrottle
+ )
+ : ky
+
+ this.ky = throttledKy.extend({
+ prefixUrl: apiBaseUrl,
+ timeout: timeoutMs,
+ headers: Object.fromEntries(
+ apiKey ? [['Authorization', `Bearer ${apiKey}`]] : []
+ )
+ })
+ }
+
+ @aiFunction({
+ name: 'gravatar_get_profile',
+ description:
+ 'Get Gravatar profile by email. Returns a profile object or `undefined` if not found.',
+ inputSchema: z.object({
+ email: z.string()
+ })
+ })
+ async getProfileByIdentifier(
+ emailOrOpts: string | gravatar.GetProfileByIdentifierOptions
+ ): Promise {
+ const { email } =
+ typeof emailOrOpts === 'string' ? { email: emailOrOpts } : emailOrOpts
+ const hashedEmail = crypto
+ .createHash('SHA256')
+ .update(email.trim().toLowerCase(), 'utf8')
+ .digest('hex')
+
+ try {
+ return await this.ky
+ .get(`v3/profiles/${hashedEmail}`)
+ .json()
+ } catch (err: any) {
+ if (err.response?.status === 404) {
+ return
+ }
+
+ throw err
+ }
+ }
+}
diff --git a/packages/gravatar/src/index.ts b/packages/gravatar/src/index.ts
new file mode 100644
index 0000000..80bdc7f
--- /dev/null
+++ b/packages/gravatar/src/index.ts
@@ -0,0 +1 @@
+export * from './gravatar-client'
diff --git a/packages/gravatar/tsconfig.json b/packages/gravatar/tsconfig.json
new file mode 100644
index 0000000..6c8d720
--- /dev/null
+++ b/packages/gravatar/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "@agentic/tsconfig/base.json",
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/stdlib/package.json b/packages/stdlib/package.json
index 46e970f..a12424b 100644
--- a/packages/stdlib/package.json
+++ b/packages/stdlib/package.json
@@ -45,6 +45,7 @@
"@agentic/firecrawl": "workspace:*",
"@agentic/genkit": "workspace:*",
"@agentic/github": "workspace:*",
+ "@agentic/gravatar": "workspace:*",
"@agentic/hacker-news": "workspace:*",
"@agentic/hunter": "workspace:*",
"@agentic/jina": "workspace:*",
diff --git a/packages/stdlib/src/index.ts b/packages/stdlib/src/index.ts
index 9fabd3a..c099adb 100644
--- a/packages/stdlib/src/index.ts
+++ b/packages/stdlib/src/index.ts
@@ -8,6 +8,7 @@ export * from '@agentic/e2b'
export * from '@agentic/exa'
export * from '@agentic/firecrawl'
export * from '@agentic/github'
+export * from '@agentic/gravatar'
export * from '@agentic/hacker-news'
export * from '@agentic/hunter'
export * from '@agentic/jina'
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index be276c9..13b6f93 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -495,6 +495,25 @@ importers:
specifier: workspace:*
version: link:../tsconfig
+ packages/gravatar:
+ dependencies:
+ '@agentic/core':
+ specifier: workspace:*
+ version: link:../core
+ ky:
+ specifier: ^1.7.5
+ version: 1.7.5
+ p-throttle:
+ specifier: ^6.2.0
+ version: 6.2.0
+ zod:
+ specifier: ^3.24.2
+ version: 3.24.2
+ devDependencies:
+ '@agentic/tsconfig':
+ specifier: workspace:*
+ version: link:../tsconfig
+
packages/hacker-news:
dependencies:
'@agentic/core':
@@ -883,6 +902,9 @@ importers:
'@agentic/github':
specifier: workspace:*
version: link:../github
+ '@agentic/gravatar':
+ specifier: workspace:*
+ version: link:../gravatar
'@agentic/hacker-news':
specifier: workspace:*
version: link:../hacker-news
diff --git a/readme.md b/readme.md
index 4dc3552..9fb16c5 100644
--- a/readme.md
+++ b/readme.md
@@ -157,6 +157,7 @@ Full docs are available at [agentic.so](https://agentic.so).
| [E2B](https://e2b.dev) | `@agentic/e2b` | [docs](https://agentic.so/tools/e2b) | Hosted Python code interpreter sandbox which is really useful for data analysis, flexible code execution, and advanced reasoning on-the-fly. |
| [Exa](https://docs.exa.ai) | `@agentic/exa` | [docs](https://agentic.so/tools/exa) | Web search tailored for LLMs. |
| [Firecrawl](https://www.firecrawl.dev) | `@agentic/firecrawl` | [docs](https://agentic.so/tools/firecrawl) | Website scraping and structured data extraction. |
+| [Gravatar](https://docs.gravatar.com/api/profiles/rest-api/) | `@agentic/gravatar` | [docs](https://agentic.so/tools/gravatar) | Gravatar profile API. |
| [HackerNews](https://github.com/HackerNews/API) | `@agentic/hacker-news` | [docs](https://agentic.so/tools/hacker-news) | Official HackerNews API. |
| [Hunter](https://hunter.io) | `@agentic/hunter` | [docs](https://agentic.so/tools/hunter) | Email finder, verifier, and enrichment. |
| [Jina](https://jina.ai/reader) | `@agentic/jina` | [docs](https://agentic.so/tools/jina) | URL scraper and web search. |