diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cd29521..3dbae88 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10.4.1 + version: 10.5.2 run_install: false - name: Install Node.js diff --git a/docs/mint.json b/docs/mint.json index e484038..e8ed2ca 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -63,6 +63,7 @@ "tools/exa", "tools/firecrawl", "tools/hacker-news", + "tools/gravatar", "tools/hunter", "tools/jina", "tools/leadmagic", diff --git a/docs/tools/gravatar.mdx b/docs/tools/gravatar.mdx new file mode 100644 index 0000000..9d6fba2 --- /dev/null +++ b/docs/tools/gravatar.mdx @@ -0,0 +1,36 @@ +--- +title: Gravatar +description: Gravatar Profile API. +--- + +- package: `@agentic/gravatar` +- exports: `class GravatarClient`, `namespace gravatar` +- env vars: `GRAVATAR_API_KEY` _(optional)_ +- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/gravatar/src/gravatar-client.ts) +- [Gravatar API docs](https://docs.gravatar.com/api/profiles/rest-api/) + +## Install + + +```bash npm +npm install @agentic/gravatar +``` + +```bash yarn +yarn add @agentic/gravatar +``` + +```bash pnpm +pnpm add @agentic/gravatar +``` + + + +## Usage + +```ts +import { GravatarClient } from '@agentic/gravatar' + +const gravatar = new GravatarClient() +const profile = await gravatar.getProfileByIdentifier('my-email@example.com') +``` diff --git a/examples/playground/bin/scratch.ts b/examples/playground/bin/scratch.ts index 3fe6d36..4aafe4b 100644 --- a/examples/playground/bin/scratch.ts +++ b/examples/playground/bin/scratch.ts @@ -1,6 +1,6 @@ import 'dotenv/config' -import { ZoomInfoClient } from '@agentic/stdlib' +import * as stdlib from '@agentic/stdlib' import restoreCursor from 'restore-cursor' /** @@ -135,15 +135,17 @@ async function main() { // category: 'linkedin profile' // }) - const zoomInfo = new ZoomInfoClient() - const res = await zoomInfo.enrichContact({ - // emailAddress: 'travis@transitivebullsh.it' - fullName: 'Kevin Raheja', - companyName: 'HeyGen' - }) + // const zoomInfo = new ZoomInfoClient() + // const res = await zoomInfo.enrichContact({ + // // emailAddress: 'travis@transitivebullsh.it' + // fullName: 'Kevin Raheja', + // companyName: 'HeyGen' + // }) // const res = await zoomInfo.searchContacts({ // fullName: 'Kevin Raheja' // }) + const gravatar = new stdlib.GravatarClient() + const res = await gravatar.getProfileByIdentifier('email@example.com') console.log(JSON.stringify(res, null, 2)) } diff --git a/package.json b/package.json index 00a2736..509e74b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "type": "git", "url": "git+https://github.com/transitive-bullshit/agentic.git" }, - "packageManager": "pnpm@10.4.1", + "packageManager": "pnpm@10.5.2", "engines": { "node": ">=18" }, diff --git a/packages/gravatar/package.json b/packages/gravatar/package.json new file mode 100644 index 0000000..0e286a1 --- /dev/null +++ b/packages/gravatar/package.json @@ -0,0 +1,48 @@ +{ + "name": "@agentic/gravatar", + "version": "7.4.1", + "description": "Agentic SDK for Gravatar.", + "author": "Travis Fischer ", + "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 @@ +

+ + Agentic + +

+ +

+ AI agent stdlib that works with any LLM and TypeScript AI SDK. +

+ +

+ Build Status + NPM + MIT License + Prettier Code Formatting +

+ +# 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. |