Merge pull request #706 from transitive-bullshit/feature/moar-improvements

old-agentic
Travis Fischer 2025-04-10 20:48:03 +07:00 zatwierdzone przez GitHub
commit 762723bd58
93 zmienionych plików z 3688 dodań i 247 usunięć

Wyświetl plik

@ -55,6 +55,7 @@
{
"group": "Tools",
"pages": [
"tools/airtable",
"tools/apollo",
"tools/arxiv",
"tools/bing",
@ -70,6 +71,8 @@
"tools/hacker-news",
"tools/gravatar",
"tools/google-custom-search",
"tools/google-docs",
"tools/google-drive",
"tools/hunter",
"tools/jina",
"tools/leadmagic",
@ -83,6 +86,7 @@
"tools/polygon",
"tools/predict-leads",
"tools/proxycurl",
"tools/reddit",
"tools/rocketreach",
"tools/searxng",
"tools/serpapi",
@ -92,10 +96,12 @@
"tools/tavily",
"tools/twilio",
"tools/twitter",
"tools/typeform",
"tools/weather",
"tools/wikidata",
"tools/wikipedia",
"tools/wolfram-alpha",
"tools/youtube",
"tools/zoominfo"
]
}

Wyświetl plik

@ -0,0 +1,51 @@
---
title: Airtable
description: Airtable is a no-code spreadsheets, CRM, and database.
---
- package: `@agentic/airtable`
- exports: `class AirtableClient`, `namespace airtable`
- env vars: `AIRTABLE_API_KEY`
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/airtable/src/airtable-client.ts)
- [airtable api docs](https://airtable.com/developers/web/api/introduction)
## Install
<CodeGroup>
```bash npm
npm install @agentic/airtable
```
```bash yarn
yarn add @agentic/airtable
```
```bash pnpm
pnpm add @agentic/airtable
```
</CodeGroup>
## Usage
```ts
import { AirtableClient } from '@agentic/airtable'
const airtable = new AirtableClient()
const { bases } = await airtable.listBases()
console.log('bases', tables)
const baseId = bases[0]!.id
const tables = await airtable.listTables({ baseId })
console.log('tables', tables)
const tableId = tables[0]!.id
const searchResults = await airtable.searchRecords({
baseId,
tableId,
searchTerm: 'Travis'
})
console.log('search results', searchResults)
```
(this is just an example of how you'd use the client)

Wyświetl plik

@ -0,0 +1,45 @@
---
title: Google Docs
description: Simplified Google Docs API.
---
- package: `@agentic/google-docs`
- exports: `class GoogleDocsClient`, `namespace googleDocs`
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/google-docs/src/google-docs-client.ts)
- [google docs docs](https://developers.google.com/workspace/docs/api)
## Install
<CodeGroup>
```bash npm
npm install @agentic/google-docs googleapis @google-cloud/local-auth
```
```bash yarn
yarn add @agentic/google-docs googleapis @google-cloud/local-auth
```
```bash pnpm
pnpm add @agentic/google-docs googleapis @google-cloud/local-auth
```
</CodeGroup>
## Example Usage
```ts
import { GoogleDriveClient } from '@agentic/google-drive'
import { authenticate } from '@google-cloud/local-auth'
import { google } from 'googleapis'
// (in a real app, store these auth credentials and reuse them)
const auth = await authenticate({
scopes: ['https://www.googleapis.com/auth/documents.readonly'],
keyfilePath: process.env.GOOGLE_CREDENTIALS_PATH
})
const docs = google.docs({ version: 'v1', auth })
const client = new GoogleDocsClient({ docs })
const document = await client.getDocument({ documentId: 'TODO' })
console.log(document)
```

Wyświetl plik

@ -0,0 +1,47 @@
---
title: Google Drive
description: Simplified Google Drive API.
---
- package: `@agentic/google-drive`
- exports: `class GoogleDriveClient`, `namespace googleDrive`
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/google-drive/src/google-drive-client.ts)
- [google drive docs](https://developers.google.com/workspace/drive/api)
## Install
<CodeGroup>
```bash npm
npm install @agentic/google-drive googleapis google-auth-library
```
```bash yarn
yarn add @agentic/google-drive googleapis google-auth-library
```
```bash pnpm
pnpm add @agentic/google-drive googleapis google-auth-library
```
</CodeGroup>
## Example Usage
```ts
import { GoogleDriveClient } from '@agentic/google-drive'
import { GoogleAuth } from 'google-auth-library'
import { google } from 'googleapis'
const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/drive' })
const drive = google.drive({ version: 'v3', auth })
const client = new GoogleDriveClient({ drive })
const result = await client.listFiles()
const file = result.files[0]!
const metadata = await client.getFile({ fileId: file.id })
const content = await client.exportFile({
fileId: file.id,
mimeType: 'application/pdf'
})
```

Wyświetl plik

@ -0,0 +1,45 @@
---
title: Reddit
description: Basic readonly Reddit API for getting top/hot/new/rising posts from subreddits.
---
- package: `@agentic/reddit`
- exports: `class RedditClient`, `namespace reddit`
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/reddit/src/reddit-client.ts)
- [reddit legacy api docs](https://old.reddit.com/dev/api)
<Note>
This client uses Reddit's free, legacy JSON API aimed at RSS feeds, so no auth
is required. With that being said, Reddit does impose rate limits on the API,
so be considerate.
</Note>
## Install
<CodeGroup>
```bash npm
npm install @agentic/reddit
```
```bash yarn
yarn add @agentic/reddit
```
```bash pnpm
pnpm add @agentic/reddit
```
</CodeGroup>
## Usage
```ts
import { RedditClient } from '@agentic/reddit'
const reddit = new RedditClient()
const result = await reddit.getSubredditPosts({
subreddit: 'AskReddit',
type: 'hot',
limit: 10
})
```

Wyświetl plik

@ -0,0 +1,43 @@
---
title: Typeform
description: Readonly Typeform API client for fetching form insights and responses.
---
- package: `@agentic/typeform`
- exports: `class TypeformClient`, `namespace typeform`
- env vars: `TYPEFORM_API_KEY`
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/typeform/src/typeform-client.ts)
- [typeform api docs](https://www.typeform.com/developers/get-started/)
## Install
<CodeGroup>
```bash npm
npm install @agentic/typeform
```
```bash yarn
yarn add @agentic/typeform
```
```bash pnpm
pnpm add @agentic/typeform
```
</CodeGroup>
## Usage
```ts
import { TypeformClient } from '@agentic/typeform'
const typeform = new TypeformClient()
const responses = await typeform.getResponsesForForm({
formId: 'TODO'
})
const insights = await typeform.getInsightsForForm({
formId: 'TODO'
})
```

Wyświetl plik

@ -0,0 +1,39 @@
---
title: YouTube
description: YouTube data API v3 client for searching YT videos and channels.
---
- package: `@agentic/youtube`
- exports: `class YouTubeClient`, `namespace youtube`
- env vars: `YOUTUBE_API_KEY`
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/youtube/src/youtube-client.ts)
- [youtube api docs](https://developers.google.com/youtube/v3)
- [search docs](https://developers.google.com/youtube/v3/docs/search/list)
## Install
<CodeGroup>
```bash npm
npm install @agentic/youtube
```
```bash yarn
yarn add @agentic/youtube
```
```bash pnpm
pnpm add @agentic/youtube
```
</CodeGroup>
## Usage
```ts
import { YouTubeClient } from '@agentic/youtube'
const youtube = new YouTubeClient()
const res = await youtube.searchVideos({
query: 'cute kittens'
})
```

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/ai-sdk"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -0,0 +1,44 @@
{
"name": "@agentic/airtable",
"version": "7.6.3",
"description": "Agentic SDK for Airtable.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/airtable"
},
"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",
"dev": "tsup --watch",
"clean": "del dist",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:typecheck": "tsc --noEmit"
},
"dependencies": {
"@agentic/core": "workspace:*",
"ky": "catalog:"
},
"peerDependencies": {
"zod": "catalog:"
},
"publishConfig": {
"access": "public"
}
}

Wyświetl plik

@ -0,0 +1,24 @@
<p align="center">
<a href="https://agentic.so">
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
</a>
</p>
<p align="center">
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
</p>
<p align="center">
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
</p>
# 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)

Wyświetl plik

@ -0,0 +1,423 @@
import {
aiFunction,
AIFunctionsProvider,
assert,
getEnv,
sanitizeSearchParams
} from '@agentic/core'
import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod'
import { airtable } from './airtable'
/**
* Airtable API client.
*
* @see https://airtable.com/developers/web/api/introduction
*/
export class AirtableClient extends AIFunctionsProvider {
protected readonly ky: KyInstance
protected readonly apiKey: string
protected readonly apiBaseUrl: string
constructor({
apiKey = getEnv('AIRTABLE_API_KEY'),
apiBaseUrl = airtable.API_BASE_URL,
timeoutMs = 60_000,
ky = defaultKy
}: {
apiKey?: string
apiBaseUrl?: string
timeoutMs?: number
ky?: KyInstance
} = {}) {
assert(
apiKey,
`AirtableClient missing required "apiKey" (defaults to "AIRTABLE_API_KEY")`
)
super()
this.apiKey = apiKey
this.apiBaseUrl = apiBaseUrl
this.ky = ky.extend({
prefixUrl: apiBaseUrl,
timeout: timeoutMs,
headers: {
Authorization: `Bearer ${apiKey}`
}
})
}
/**
* Lists all of the bases that the user has access to.
*/
@aiFunction({
name: 'airtable_list_bases',
description: 'Lists all accessible Airtable bases.',
inputSchema: z.object({})
})
async listBases(): Promise<airtable.ListBasesResponse> {
return this.ky.get('v0/meta/bases').json<airtable.ListBasesResponse>()
}
/**
* Lists all of the tables in a base.
*/
@aiFunction({
name: 'airtable_list_tables',
description: 'Lists all of the tables in a base.',
inputSchema: airtable.ListTablesArgsSchema
})
async listTables<
TDetailLevel extends airtable.TableDetailLevel = 'full'
>(args: {
baseId: string
detailLevel?: TDetailLevel
}): Promise<Array<airtable.AirtableTableToDetailLevel<TDetailLevel>>> {
const { baseId, detailLevel = 'full' } = args
const res = await this.ky
.get(`/v0/meta/bases/${baseId}/tables`)
.json<airtable.BaseSchemaResponse>()
return res.tables.map((table) =>
transformTableDetailLevel<TDetailLevel>({
table,
detailLevel: detailLevel as TDetailLevel
})
)
}
/**
* Gets a single table's schema in a base.
*/
@aiFunction({
name: 'airtable_get_table',
description: "Gets a single table's schema in a base.",
inputSchema: airtable.DescribeTableArgsSchema
})
async getTable<
TDetailLevel extends airtable.TableDetailLevel = 'full'
>(args: {
baseId: string
tableId: string
detailLevel?: TDetailLevel
}): Promise<airtable.AirtableTableToDetailLevel<TDetailLevel>> {
const tables = await this.listTables<TDetailLevel>(args)
const table = tables.find((t) => t.id === args.tableId)
assert(table, `Table ${args.tableId} not found in base ${args.baseId}`)
return table
}
/**
* Lists records from a table.
*/
@aiFunction({
name: 'airtable_list_records',
description: 'Lists records from a table.',
inputSchema: airtable.ListRecordsArgsSchema
})
async listRecords(
args: airtable.ListRecordsArgs
): Promise<airtable.AirtableRecord[]> {
const { baseId, tableId, ...options } = args
return this.ky
.get(`/v0/${baseId}/${tableId}`, {
searchParams: sanitizeSearchParams(options)
})
.json<airtable.AirtableRecord[]>()
}
/**
* Lists all records from a table.
*/
@aiFunction({
name: 'airtable_list_all_records',
description: 'Lists all records from a table.',
inputSchema: airtable.ListRecordsArgsSchema
})
async listAllRecords(
args: airtable.ListRecordsArgs
): Promise<airtable.AirtableRecord[]> {
const allRecords: airtable.AirtableRecord[] = []
let offset = args.offset ?? 0
do {
const res = await this.listRecords({
...args,
offset
})
if (!res.length) {
break
}
allRecords.push(...res)
offset += res.length
} while (true)
return allRecords
}
/**
* Gets a single record from a table.
*/
@aiFunction({
name: 'airtable_get_record',
description: 'Gets a single record from a table.',
inputSchema: airtable.GetRecordArgsSchema
})
async getRecord(
args: airtable.GetRecordArgs
): Promise<airtable.AirtableRecord> {
const { baseId, tableId, recordId } = args
return this.ky
.get(`/v0/${baseId}/${tableId}/${recordId}`)
.json<airtable.AirtableRecord>()
}
/**
* Creates a record in a table.
*/
@aiFunction({
name: 'airtable_create_record',
description: 'Creates a record in a table.',
inputSchema: airtable.CreateRecordArgsSchema
})
async createRecord(
args: airtable.CreateRecordArgs
): Promise<airtable.AirtableRecord> {
const { baseId, tableId, ...body } = args
return this.ky
.post(`/v0/${baseId}/${tableId}`, {
json: body
})
.json<airtable.AirtableRecord>()
}
/**
* Updates records in a table.
*/
@aiFunction({
name: 'airtable_update_records',
description: 'Updates records in a table.',
inputSchema: airtable.UpdateRecordsArgsSchema
})
async updateRecords(
args: airtable.UpdateRecordsArgs
): Promise<airtable.AirtableRecord[]> {
const { baseId, tableId, ...body } = args
return this.ky
.patch(`/v0/${baseId}/${tableId}`, {
json: body
})
.json<airtable.AirtableRecord[]>()
}
/**
* Deletes records from a table.
*/
@aiFunction({
name: 'airtable_delete_records',
description: 'Deletes records from a table.',
inputSchema: airtable.DeleteRecordsArgsSchema
})
async deleteRecords(
args: airtable.DeleteRecordsArgs
): Promise<{ id: string }[]> {
const { baseId, tableId, recordIds } = args
const queryString = recordIds.map((id) => `records[]=${id}`).join('&')
const res = await this.ky
.delete(`/v0/${baseId}/${tableId}?${queryString}`)
.json<{ records: { id: string; deleted: boolean }[] }>()
return res.records.map(({ id }) => ({ id }))
}
/**
* Creates a table in a base.
*/
@aiFunction({
name: 'airtable_create_table',
description: 'Creates a table in a base.',
inputSchema: airtable.CreateTableArgsSchema
})
async createTable(args: airtable.CreateTableArgs): Promise<airtable.Table> {
const { baseId, ...body } = args
return this.ky
.post(`/v0/meta/bases/${baseId}/tables`, {
json: body
})
.json<airtable.Table>()
}
/**
* Updates a table in a base.
*/
@aiFunction({
name: 'airtable_update_table',
description: 'Updates a table in a base.',
inputSchema: airtable.UpdateTableArgsSchema
})
async updateTable(args: airtable.UpdateTableArgs): Promise<airtable.Table> {
const { baseId, tableId, ...body } = args
return this.ky
.patch(`/v0/meta/bases/${baseId}/tables/${tableId}`, {
json: body
})
.json<airtable.Table>()
}
/**
* Creates a field in a table.
*/
@aiFunction({
name: 'airtable_create_field',
description: 'Creates a field in a table.',
inputSchema: airtable.CreateFieldArgsSchema
})
async createField(args: airtable.CreateFieldArgs): Promise<airtable.Field> {
const { baseId, tableId, body } = args
return this.ky
.post(`/v0/meta/bases/${baseId}/tables/${tableId}/fields`, {
json: body.field
})
.json<airtable.Field>()
}
/**
* Updates a field in a table.
*/
@aiFunction({
name: 'airtable_update_field',
description: 'Updates a field in a table.',
inputSchema: airtable.UpdateFieldArgsSchema
})
async updateField(args: airtable.UpdateFieldArgs): Promise<airtable.Field> {
const { baseId, tableId, fieldId, ...body } = args
return this.ky
.patch(`/v0/meta/bases/${baseId}/tables/${tableId}/fields/${fieldId}`, {
json: body
})
.json<airtable.Field>()
}
/**
* Searches for records in a table which contain specific text.
*/
@aiFunction({
name: 'airtable_search_records',
description: 'Searches for records in a table which contain specific text.',
inputSchema: airtable.SearchRecordsArgsSchema
})
async searchRecords(
args: airtable.SearchRecordsArgs
): Promise<airtable.AirtableRecord[]> {
const { baseId, tableId, fieldIds, searchTerm, ...opts } = args
// Validate and get search fields
const searchFieldIds = await this.validateAndGetSearchFields({
baseId,
tableId,
fieldIds
})
// Escape the search term to prevent formula injection
const escapedSearchTerm = searchTerm.replaceAll(/["\\]/g, '\\$&')
// Build OR(FIND("term", field1), FIND("term", field2), ...)
const filterByFormula = `OR(${searchFieldIds
.map((fieldId) => `FIND("${escapedSearchTerm}", {${fieldId}})`)
.join(',')})`
return this.listRecords({ ...opts, baseId, tableId, filterByFormula })
}
/**
* Validates and gets the searchable text fields in a table.
*/
protected async validateAndGetSearchFields({
baseId,
tableId,
fieldIds
}: {
baseId: string
tableId: string
fieldIds?: string[]
}): Promise<string[]> {
const table = await this.getTable({ baseId, tableId })
const searchableFieldTypes = new Set([
'singleLineText',
'multilineText',
'richText',
'email',
'url',
'phoneNumber'
])
const searchableFieldIds = new Set(
table.fields
.filter((field) => searchableFieldTypes.has(field.type))
.map((field) => field.id)
)
if (!searchableFieldIds.size) {
throw new Error('No text fields available to search')
}
// If specific fields were requested, validate that they exist and are, in
// fact, valid searchable text fields.
if (fieldIds && fieldIds.length > 0) {
// Check if any requested fields were invalid
const invalidFieldIds = fieldIds.filter(
(fieldId) => !searchableFieldIds.has(fieldId)
)
if (invalidFieldIds.length > 0) {
throw new Error(
`Invalid fields requested: ${invalidFieldIds.join(', ')}`
)
}
return fieldIds
}
return Array.from(searchableFieldIds)
}
}
function transformTableDetailLevel<
T extends airtable.TableDetailLevel = 'full'
>({
table,
detailLevel
}: {
table: airtable.Table
detailLevel: T
}): airtable.AirtableTableToDetailLevel<T> {
switch (detailLevel) {
case 'tableIdentifiersOnly':
return {
id: table.id,
name: table.name
} as any
case 'identifiersOnly':
return {
id: table.id,
name: table.name,
fields: table.fields.map((field) => ({
id: field.id,
name: field.name
})),
views: table.views.map((view) => ({
id: view.id,
name: view.name
}))
} as any
default:
return table as any
}
}

Wyświetl plik

@ -0,0 +1,665 @@
import { z } from 'zod'
export namespace airtable {
export const API_BASE_URL = 'https://api.airtable.com'
export const BaseSchema = z.object({
id: z.string(),
name: z.string(),
permissionLevel: z.string()
})
export type Base = z.infer<typeof BaseSchema>
export const ListBasesResponseSchema = z.object({
bases: z.array(BaseSchema),
offset: z.string().optional()
})
export type ListBasesResponse = z.infer<typeof ListBasesResponseSchema>
export const FieldOptionsSchema = z
.object({
isReversed: z.boolean().optional(),
inverseLinkFieldId: z.string().optional(),
linkedTableId: z.string().optional(),
prefersSingleRecordLink: z.boolean().optional(),
color: z.string().optional(),
icon: z.string().optional()
})
.passthrough()
export type FieldOptions = z.infer<typeof FieldOptionsSchema>
export const FieldSchema = z
.object({
name: z.string(),
description: z.string().optional()
})
.and(
// Extracted from Airtable API docs
z.union([
z.object({ type: z.literal('autoNumber') }),
z.object({ type: z.literal('barcode') }),
z.object({ type: z.literal('button') }),
z
.object({
options: z.object({
color: z
.enum([
'greenBright',
'tealBright',
'cyanBright',
'blueBright',
'purpleBright',
'pinkBright',
'redBright',
'orangeBright',
'yellowBright',
'grayBright'
])
.describe('The color of the checkbox.'),
icon: z
.enum([
'check',
'xCheckbox',
'star',
'heart',
'thumbsUp',
'flag',
'dot'
])
.describe('The icon name of the checkbox.')
}),
type: z.literal('checkbox')
})
.describe(
"Bases on a free or plus plan are limited to using the `'check'` icon and `'greenBright'` color."
),
z.object({ type: z.literal('createdBy') }),
z.object({
options: z.object({
result: z
.union([
z.object({
options: z.object({
dateFormat: z.object({
format: z
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
.describe(
'`format` is always provided when reading.\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
),
name: z.enum([
'local',
'friendly',
'us',
'european',
'iso'
])
})
}),
type: z.literal('date')
}),
z.object({
options: z.object({
dateFormat: z.object({
format: z
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
.describe(
'`format` is always provided when reading.\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
),
name: z.enum([
'local',
'friendly',
'us',
'european',
'iso'
])
}),
timeFormat: z.object({
format: z.enum(['h:mma', 'HH:mm']),
name: z.enum(['12hour', '24hour'])
}),
timeZone: z.any()
}),
type: z.literal('dateTime')
})
])
.describe(
'This will always be a `date` or `dateTime` field config.'
)
.optional()
}),
type: z.literal('createdTime')
}),
z.object({
options: z.object({
isValid: z
.boolean()
.describe(
'`false` when recordLinkFieldId is null, e.g. the referenced column was deleted.'
),
recordLinkFieldId: z.union([z.string(), z.null()]).optional()
}),
type: z.literal('count')
}),
z.any(),
z.object({
options: z.object({
isValid: z
.boolean()
.describe(
'False if this formula/field configuation has an error'
),
referencedFieldIds: z
.union([z.array(z.string()), z.null()])
.describe('The fields to check the last modified time of'),
result: z
.union([
z.object({
options: z.object({
dateFormat: z.object({
format: z
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
.describe(
'`format` is always provided when reading.\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
),
name: z.enum([
'local',
'friendly',
'us',
'european',
'iso'
])
})
}),
type: z.literal('date')
}),
z.object({
options: z.object({
dateFormat: z.object({
format: z
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
.describe(
'`format` is always provided when reading.\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
),
name: z.enum([
'local',
'friendly',
'us',
'european',
'iso'
])
}),
timeFormat: z.object({
format: z.enum(['h:mma', 'HH:mm']),
name: z.enum(['12hour', '24hour'])
}),
timeZone: z.any()
}),
type: z.literal('dateTime')
}),
z.null()
])
.describe(
'This will always be a `date` or `dateTime` field config.'
)
}),
type: z.literal('lastModifiedTime')
}),
z.object({ type: z.literal('lastModifiedBy') }),
z.object({
options: z.object({
fieldIdInLinkedTable: z
.union([z.string(), z.null()])
.describe(
'The field in the linked table that this field is looking up.'
),
isValid: z
.boolean()
.describe(
'Is the field currently valid (e.g. false if the linked record field has\nbeen deleted)'
),
recordLinkFieldId: z
.union([z.string(), z.null()])
.describe('The linked record field in the current table.'),
result: z
.union([z.any(), z.null()])
.describe(
'The field type and options inside of the linked table. See other field\ntype configs on this page for the possible values. Can be null if invalid.'
)
}),
type: z.literal('lookup')
}),
z.object({
options: z.object({
precision: z
.number()
.describe(
'Indicates the number of digits shown to the right of the decimal point for this field. (0-8 inclusive)'
)
}),
type: z.literal('number')
}),
z.object({
options: z.object({
precision: z
.number()
.describe(
'Indicates the number of digits shown to the right of the decimal point for this field. (0-8 inclusive)'
)
}),
type: z.literal('percent')
}),
z.object({
options: z.object({
precision: z
.number()
.describe(
'Indicates the number of digits shown to the right of the decimal point for this field. (0-7 inclusive)'
),
symbol: z.string().describe('Currency symbol to use.')
}),
type: z.literal('currency')
}),
z.object({
options: z.object({
durationFormat: z.enum([
'h:mm',
'h:mm:ss',
'h:mm:ss.S',
'h:mm:ss.SS',
'h:mm:ss.SSS'
])
}),
type: z.literal('duration')
}),
z.object({ type: z.literal('multilineText') }),
z.object({ type: z.literal('phoneNumber') }),
z
.object({
options: z.object({
color: z
.enum([
'yellowBright',
'orangeBright',
'redBright',
'pinkBright',
'purpleBright',
'blueBright',
'cyanBright',
'tealBright',
'greenBright',
'grayBright'
])
.describe('The color of selected icons.'),
icon: z
.enum(['star', 'heart', 'thumbsUp', 'flag', 'dot'])
.describe('The icon name used to display the rating.'),
max: z
.number()
.describe(
'The maximum value for the rating, from 1 to 10 inclusive.'
)
}),
type: z.literal('rating')
})
.describe(
"Bases on a free or plus plan are limited to using the 'star' icon and 'yellowBright' color."
),
z.object({ type: z.literal('richText') }),
z.object({
options: z.object({
fieldIdInLinkedTable: z
.string()
.describe('The id of the field in the linked table')
.optional(),
isValid: z.boolean().optional(),
recordLinkFieldId: z
.string()
.describe('The linked field id')
.optional(),
referencedFieldIds: z
.array(z.string())
.describe(
'The ids of any fields referenced in the rollup formula'
)
.optional(),
result: z
.union([z.any(), z.null()])
.describe(
'The resulting field type and options for the rollup. See other field\ntype configs on this page for the possible values. Can be null if invalid.'
)
.optional()
}),
type: z.literal('rollup')
}),
z.object({ type: z.literal('singleLineText') }),
z.object({ type: z.literal('email') }),
z.object({ type: z.literal('url') }),
z.object({
options: z.object({
choices: z.array(
z.object({
color: z
.string()
.describe(
'Optional when the select field is configured to not use colors.\n\nAllowed values: "blueLight2", "cyanLight2", "tealLight2", "greenLight2", "yellowLight2", "orangeLight2", "redLight2", "pinkLight2", "purpleLight2", "grayLight2", "blueLight1", "cyanLight1", "tealLight1", "greenLight1", "yellowLight1", "orangeLight1", "redLight1", "pinkLight1", "purpleLight1", "grayLight1", "blueBright", "cyanBright", "tealBright", "greenBright", "yellowBright", "orangeBright", "redBright", "pinkBright", "purpleBright", "grayBright", "blueDark1", "cyanDark1", "tealDark1", "greenDark1", "yellowDark1", "orangeDark1", "redDark1", "pinkDark1", "purpleDark1", "grayDark1"'
)
.optional(),
id: z.string(),
name: z.string()
})
)
}),
type: z.literal('externalSyncSource')
}),
z.object({
options: z.object({
prompt: z
.array(
z.union([
z.string(),
z.object({ field: z.object({ fieldId: z.string() }) })
])
)
.describe(
'The prompt that is used to generate the results in the AI field, additional object\ntypes may be added in the future. Currently, this is an array of strings or objects that identify any fields interpolated into the prompt.\n\nThe prompt will not currently be provided if this field config is within another\nfields configuration (like a lookup field)'
)
.optional(),
referencedFieldIds: z
.array(z.string())
.describe(
'The other fields in the record that are used in the ai field\n\nThe referencedFieldIds will not currently be provided if this field config is within another\nfields configuration (like a lookup field)'
)
.optional()
}),
type: z.literal('aiText')
}),
z
.object({
options: z.object({
linkedTableId: z
.string()
.describe('The ID of the table this field links to'),
viewIdForRecordSelection: z
.string()
.describe(
'The ID of the view in the linked table\nto use when showing a list of records to select from'
)
.optional()
}),
type: z.literal('multipleRecordLinks')
})
.describe(
'Creating "multipleRecordLinks" fields is supported but updating options for\nexisting "multipleRecordLinks" fields is not supported.'
),
z.object({
options: z.object({
choices: z.array(
z.object({
color: z
.string()
.describe('Optional when creating an option.')
.optional(),
id: z
.string()
.describe(
'This is not specified when creating new options, useful when specifing existing\noptions (for example: reordering options, keeping old options and adding new ones, etc)'
)
.optional(),
name: z.string()
})
)
}),
type: z.literal('singleSelect')
}),
z.object({
options: z.object({
choices: z.array(
z.object({
color: z
.string()
.describe('Optional when creating an option.')
.optional(),
id: z
.string()
.describe(
'This is not specified when creating new options, useful when specifing existing\noptions (for example: reordering options, keeping old options and adding new ones, etc)'
)
.optional(),
name: z.string()
})
)
}),
type: z.literal('multipleSelects')
}),
z.object({
options: z.record(z.any()).optional(),
type: z.literal('singleCollaborator')
}),
z.object({
options: z.record(z.any()).optional(),
type: z.literal('multipleCollaborators')
}),
z.object({
options: z.object({
dateFormat: z.object({
format: z
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
.describe(
'Format is optional when writing, but it must match\nthe corresponding name if provided.\n\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
)
.optional(),
name: z.enum(['local', 'friendly', 'us', 'european', 'iso'])
})
}),
type: z.literal('date')
}),
z.object({
options: z.object({
dateFormat: z.object({
format: z
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
.describe(
'Format is optional when writing, but it must match\nthe corresponding name if provided.\n\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
)
.optional(),
name: z.enum(['local', 'friendly', 'us', 'european', 'iso'])
}),
timeFormat: z.object({
format: z.enum(['h:mma', 'HH:mm']).optional(),
name: z.enum(['12hour', '24hour'])
}),
timeZone: z.any()
}),
type: z.literal('dateTime')
}),
z.object({
options: z.object({ isReversed: z.boolean() }).optional(),
type: z.literal('multipleAttachments')
})
])
)
.describe(
'The config of a field. NB: Formula fields cannot be created with this MCP due to a limitation in the Airtable API.'
)
export type Field = z.infer<typeof FieldSchema> & { id: string }
export const ViewSchema = z.object({
id: z.string(),
name: z.string(),
type: z.string()
})
export type View = z.infer<typeof ViewSchema>
export const TableSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().optional(),
primaryFieldId: z.string(),
fields: z.array(FieldSchema.and(z.object({ id: z.string() }))),
views: z.array(ViewSchema)
})
export type Table = z.infer<typeof TableSchema>
export const BaseSchemaResponseSchema = z.object({
tables: z.array(TableSchema)
})
export type BaseSchemaResponse = z.infer<typeof BaseSchemaResponseSchema>
// Zod schemas for tool arguments
export const ListRecordsArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
offset: z
.number()
.optional()
.describe('Offset to start the list from. Defaults to 0.'),
maxRecords: z
.number()
.optional()
.describe('Maximum number of records to return. Defaults to 100.'),
filterByFormula: z
.string()
.optional()
.describe('Airtable formula to filter records')
})
export type ListRecordsArgs = z.infer<typeof ListRecordsArgsSchema>
export const SearchRecordsArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
searchTerm: z.string().describe('Text to search for in records'),
fieldIds: z
.array(z.string())
.optional()
.describe(
'Specific field ids to search in. If not provided, searches all text-based fields.'
),
maxRecords: z
.number()
.optional()
.describe('Maximum number of records to return. Defaults to 100.')
})
export type SearchRecordsArgs = z.infer<typeof SearchRecordsArgsSchema>
export const TableDetailLevelSchema = z.enum([
'tableIdentifiersOnly',
'identifiersOnly',
'full'
]).describe(`Detail level for table information:
- tableIdentifiersOnly: table IDs and names
- identifiersOnly: table, field, and view IDs and names
- full: complete details including field types, descriptions, and configurations
Note for LLMs: To optimize context window usage, request the minimum detail level needed:
- Use 'tableIdentifiersOnly' when you only need to list or reference tables
- Use 'identifiersOnly' when you need to work with field or view references
- Only use 'full' when you need field types, descriptions, or other detailed configuration
If you only need detailed information on a few tables in a base with many complex tables, it might be more efficient for you to use list_tables with tableIdentifiersOnly, then describe_table with full on the specific tables you want.`)
export type TableDetailLevel = z.infer<typeof TableDetailLevelSchema>
export const DescribeTableArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
detailLevel: TableDetailLevelSchema.optional().default('full')
})
export type DescribeTableArgs = z.infer<typeof DescribeTableArgsSchema>
export const ListTablesArgsSchema = z.object({
baseId: z.string(),
detailLevel: TableDetailLevelSchema.optional().default('full')
})
export type ListTablesArgs = z.infer<typeof ListTablesArgsSchema>
export const GetRecordArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
recordId: z.string()
})
export type GetRecordArgs = z.infer<typeof GetRecordArgsSchema>
export const CreateRecordArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
fields: z.record(z.any())
})
export type CreateRecordArgs = z.infer<typeof CreateRecordArgsSchema>
export const UpdateRecordsArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
records: z.array(
z.object({
id: z.string(),
fields: z.record(z.any())
})
)
})
export type UpdateRecordsArgs = z.infer<typeof UpdateRecordsArgsSchema>
export const DeleteRecordsArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
recordIds: z.array(z.string())
})
export type DeleteRecordsArgs = z.infer<typeof DeleteRecordsArgsSchema>
export const CreateTableArgsSchema = z.object({
baseId: z.string(),
name: z
.string()
.describe('Name for the new table. Must be unique in the base.'),
description: z.string().optional(),
fields: z.array(FieldSchema).describe(`Table fields. Rules:
- At least one field must be specified.
- The primary (first) field must be one of: single line text, long text, date, phone number, email, URL, number, currency, percent, duration, formula, autonumber, barcode.`)
})
export type CreateTableArgs = z.infer<typeof CreateTableArgsSchema>
export const UpdateTableArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
name: z.string().optional(),
description: z.string().optional()
})
export type UpdateTableArgs = z.infer<typeof UpdateTableArgsSchema>
export const CreateFieldArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
// This is used as a workaround for https://github.com/orgs/modelcontextprotocol/discussions/90
body: z.object({
field: FieldSchema
})
})
export type CreateFieldArgs = z.infer<typeof CreateFieldArgsSchema>
export const UpdateFieldArgsSchema = z.object({
baseId: z.string(),
tableId: z.string(),
fieldId: z.string(),
name: z.string().optional(),
description: z.string().optional()
})
export type UpdateFieldArgs = z.infer<typeof UpdateFieldArgsSchema>
export type FieldSet = Record<string, any>
export type AirtableRecord = { id: string; fields: FieldSet }
export type AirtableTableToDetailLevel<
TDetailLevel extends TableDetailLevel | undefined = 'full'
> = TDetailLevel extends undefined
? airtable.Table
: TDetailLevel extends 'full'
? airtable.Table
: TDetailLevel extends 'identifiersOnly'
? {
id: string
name: string
fields: { id: string; name: string }[]
views: { id: string; name: string }[]
}
: TDetailLevel extends 'tableIdentifiersOnly'
? { id: string; name: string }
: never
}

Wyświetl plik

@ -0,0 +1,2 @@
export * from './airtable'
export * from './airtable-client'

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -293,7 +293,7 @@ export class ApolloClient extends AIFunctionsProvider {
} = {}) {
assert(
apiKey,
`ApolloClient missing required "username" (defaults to "APOLLO_API_KEY")`
`ApolloClient missing required "apiKey" (defaults to "APOLLO_API_KEY")`
)
super()

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/arxiv"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/bing"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/calculator"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/clearbit"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/core"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/dexa"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/dexter"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/diffbot"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/duck-duck-go"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/e2b"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/exa"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -9,7 +9,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/firecrawl"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/genkit"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/github"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -0,0 +1,48 @@
{
"name": "@agentic/google-docs",
"version": "7.6.3",
"description": "Agentic SDK for Google Docs.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/google-docs"
},
"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",
"dev": "tsup --watch",
"clean": "del dist",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:typecheck": "tsc --noEmit"
},
"dependencies": {
"@agentic/core": "workspace:*",
"type-fest": "catalog:"
},
"devDependencies": {
"googleapis": "catalog:"
},
"peerDependencies": {
"googleapis": "catalog:",
"zod": "catalog:"
},
"publishConfig": {
"access": "public"
}
}

Wyświetl plik

@ -0,0 +1,24 @@
<p align="center">
<a href="https://agentic.so">
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
</a>
</p>
<p align="center">
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
</p>
<p align="center">
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
</p>
# 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)

Wyświetl plik

@ -0,0 +1,76 @@
import type * as google from 'googleapis'
import type { SetNonNullable, Simplify } from 'type-fest'
import {
aiFunction,
AIFunctionsProvider,
pruneNullOrUndefinedDeep,
type SetRequired
} from '@agentic/core'
import { z } from 'zod'
export namespace googleDocs {
export type Document = Simplify<
SetNonNullable<google.docs_v1.Schema$Document>
>
}
/**
* Simplified Google Docs API client.
*
* @see https://developers.google.com/workspace/drive/api
*
* @example
* ```ts
* import { GoogleDocsClient } from '@agentic/google-docs'
* import { authenticate } from '@google-cloud/local-auth'
* import { google } from 'googleapis'
*
* // (in a real app, store these auth credentials and reuse them)
* const auth = await authenticate({
* scopes: ['https://www.googleapis.com/auth/documents.readonly'],
* keyfilePath: process.env.GOOGLE_CREDENTIALS_PATH
* })
* const docs = google.docs({ version: 'v1', auth })
* const client = new GoogleDocsClient({ docs })
* ```
*/
export class GoogleDocsClient extends AIFunctionsProvider {
protected readonly docs: google.docs_v1.Docs
constructor({ docs }: { docs: google.docs_v1.Docs }) {
super()
this.docs = docs
}
/**
* Gets a Google Docs document by ID.
*/
@aiFunction({
name: 'google_docs_get_document',
description: 'Gets a Google Docs document by ID.',
inputSchema: z.object({
documentId: z.string()
})
})
async getDocument(
args: Simplify<
SetRequired<google.docs_v1.Params$Resource$Documents$Get, 'documentId'>
>
): Promise<googleDocs.Document> {
const { documentId, ...opts } = args
const { data } = await this.docs.documents.get({
...opts,
documentId
})
return convertDocument(data)
}
}
function convertDocument(
data: google.docs_v1.Schema$Document
): googleDocs.Document {
return pruneNullOrUndefinedDeep(data)
}

Wyświetl plik

@ -0,0 +1 @@
export * from './google-docs-client'

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -0,0 +1,48 @@
{
"name": "@agentic/google-drive",
"version": "7.6.3",
"description": "Agentic SDK for Google Drive.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/google-drive"
},
"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",
"dev": "tsup --watch",
"clean": "del dist",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:typecheck": "tsc --noEmit"
},
"dependencies": {
"@agentic/core": "workspace:*",
"type-fest": "catalog:"
},
"devDependencies": {
"googleapis": "catalog:"
},
"peerDependencies": {
"googleapis": "catalog:",
"zod": "catalog:"
},
"publishConfig": {
"access": "public"
}
}

Wyświetl plik

@ -0,0 +1,24 @@
<p align="center">
<a href="https://agentic.so">
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
</a>
</p>
<p align="center">
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
</p>
<p align="center">
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
</p>
# 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)

Wyświetl plik

@ -0,0 +1,201 @@
import type * as google from 'googleapis'
import type { SetNonNullable, Simplify } from 'type-fest'
import {
aiFunction,
AIFunctionsProvider,
pick,
pruneNullOrUndefined,
pruneNullOrUndefinedDeep
} from '@agentic/core'
import { z } from 'zod'
export namespace googleDrive {
export type File = Simplify<
SetNonNullable<
Pick<
google.drive_v3.Schema$File,
| 'id'
| 'name'
| 'mimeType'
| 'webViewLink'
| 'webContentLink'
| 'size'
| 'createdTime'
| 'modifiedTime'
| 'parents'
>
>
>
export const fileFields: readonly (keyof File)[] = [
'id',
'name',
'mimeType',
'webViewLink',
'webContentLink',
'size',
'createdTime',
'modifiedTime',
'parents'
]
export const requestFileFields = `files(${fileFields.join(',')}),nextPageToken`
export interface ListFilesResponse {
files: File[]
nextPageToken?: string
}
export interface DownloadResponse {
content: string
metadata: File
}
export const ListFilesParamsSchema = z.object({
folderId: z.string().optional(),
query: z.string().optional(),
pageSize: z.number().optional(),
pageToken: z.string().optional()
})
}
/**
* Simplified Google Drive API client.
*
* @see https://developers.google.com/workspace/drive/api
*
* @example
* ```ts
* import { GoogleDriveClient } from '@agentic/google-drive'
* import { GoogleAuth } from 'google-auth-library'
* import { google } from 'googleapis'
*
* const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/drive' })
* const drive = google.drive({ version: 'v3', auth })
* const client = new GoogleDriveClient({ drive })
* ```
*/
export class GoogleDriveClient extends AIFunctionsProvider {
protected readonly drive: google.drive_v3.Drive
constructor({ drive }: { drive: google.drive_v3.Drive }) {
super()
this.drive = drive
}
/**
* Lists files and folders in a Google Drive folder.
*/
@aiFunction({
name: 'google_drive_list_files',
description: 'Lists files and folders in a Google Drive folder.',
inputSchema: googleDrive.ListFilesParamsSchema
})
async listFiles(
args: {
folderId?: string
query?: string
} & google.drive_v3.Params$Resource$Files$Get
): Promise<googleDrive.ListFilesResponse> {
const { folderId, query, ...opts } = args
// Build the query conditions
const conditions = ['trashed = false'] // Always exclude trashed files
if (folderId) {
conditions.push(`'${folderId}' in parents`)
}
if (query) {
conditions.push(`name contains '${query}'`)
}
// Combine all conditions with AND
const q = conditions.join(' and ')
const { data } = await this.drive.files.list({
fields: googleDrive.requestFileFields,
...opts,
q
})
const files = (data.files ?? []).map(convertFile)
return pruneNullOrUndefined({
files,
nextPageToken: data.nextPageToken
})
}
/**
* Gets a file's metadata from Google Drive.
*/
@aiFunction({
name: 'google_drive_get_file',
description: "Gets a file's metadata from Google Drive.",
inputSchema: z.object({ fileId: z.string() })
})
async getFile(
opts: google.drive_v3.Params$Resource$Files$Get
): Promise<googleDrive.File> {
const { data } = await this.drive.files.get({
fields: googleDrive.requestFileFields,
...opts
})
return convertFile(data)
}
/**
* Exports a file from Google Drive.
*/
@aiFunction({
name: 'google_drive_export_file',
description: 'Exports a file from Google Drive to a given mime-type.',
inputSchema: z.object({
fileId: z.string().describe('The ID of the file to export.'),
mimeType: z
.string()
.describe('The MIME type of the format requested for this export.')
})
})
async exportFile(
opts: google.drive_v3.Params$Resource$Files$Export
): Promise<unknown> {
return this.drive.files.export(opts)
}
@aiFunction({
name: 'google_drive_create_folder',
description: 'Creates a new folder in Google Drive.',
inputSchema: z.object({
name: z.string().describe('The name of the folder to create.'),
parentId: z.string().describe('The ID of the parent folder.').optional()
})
})
async createFolder(
opts: Omit<
google.drive_v3.Params$Resource$Files$Create,
'media' | 'requestBody'
> & {
name: string
parentId?: string
}
): Promise<googleDrive.File> {
const { data } = await this.drive.files.create({
requestBody: {
mimeType: 'application/vnd.google-apps.folder',
name: opts.name,
parents: opts.parentId ? [opts.parentId] : undefined
},
fields: googleDrive.requestFileFields,
...opts
})
return convertFile(data)
}
}
function convertFile(data: google.drive_v3.Schema$File): googleDrive.File {
return pruneNullOrUndefinedDeep(
pick(data, ...googleDrive.fileFields)
) as googleDrive.File
}

Wyświetl plik

@ -0,0 +1 @@
export * from './google-drive-client'

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/hacker-news"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/hunter"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/jigsawstack"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/jina"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/langchain"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -131,7 +131,7 @@ export class LeadMagicClient extends AIFunctionsProvider {
} = {}) {
assert(
apiKey,
`LeadMagicClient missing required "username" (defaults to "LEADMAGIC_API_KEY")`
`LeadMagicClient missing required "apiKey" (defaults to "LEADMAGIC_API_KEY")`
)
super()

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/llamaindex"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/mastra"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/mcp"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/midjourney"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/novu"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/open-meteo"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -28,10 +28,12 @@ export class OpenMeteoClient extends AIFunctionsProvider {
constructor({
apiKey = getEnv('OPEN_METEO_API_KEY'),
apiBaseUrl = openmeteo.apiBaseUrl,
timeoutMs = 60_000,
ky = defaultKy
}: {
apiKey?: string
apiBaseUrl?: string
timeoutMs?: number
ky?: KyInstance
} = {}) {
super()
@ -41,6 +43,7 @@ export class OpenMeteoClient extends AIFunctionsProvider {
this.ky = ky.extend({
prefixUrl: apiBaseUrl,
timeout: timeoutMs,
...(apiKey
? {
headers: {

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/openapi-to-ts"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/people-data-labs"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/perigon"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/polygon"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/predict-leads"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/proxycurl"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -0,0 +1,44 @@
{
"name": "@agentic/reddit",
"version": "0.1.0",
"description": "Agentic SDK for Reddit.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/reddit"
},
"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",
"dev": "tsup --watch",
"clean": "del dist",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:typecheck": "tsc --noEmit"
},
"dependencies": {
"@agentic/core": "workspace:*",
"ky": "catalog:"
},
"peerDependencies": {
"zod": "catalog:"
},
"publishConfig": {
"access": "public"
}
}

Wyświetl plik

@ -0,0 +1,24 @@
<p align="center">
<a href="https://agentic.so">
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
</a>
</p>
<p align="center">
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
</p>
<p align="center">
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
</p>
# 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)

Wyświetl plik

@ -0,0 +1 @@
export * from './reddit-client'

Wyświetl plik

@ -0,0 +1,455 @@
import {
aiFunction,
AIFunctionsProvider,
pick,
sanitizeSearchParams
} from '@agentic/core'
import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod'
export namespace reddit {
export const BASE_URL = 'https://www.reddit.com'
export interface Post {
id: string
name: string // name is `t3_<id>`
title: string
subreddit: string
selftext?: string
author: string
author_fullname: string
url: string
permalink: string
thumbnail?: string
thumbnail_width?: number
thumbnail_height?: number
score: number
ups: number
downs: number
num_comments: number
created_utc: number
is_self: boolean
is_video: boolean
}
export interface FullPost {
id: string
name: string
author: string
title: string
subreddit: string
subreddit_name_prefixed: string
score: number
approved_at_utc: string | null
selftext?: string
author_fullname: string
is_self: boolean
saved: boolean
url: string
permalink: string
mod_reason_title: string | null
gilded: number
clicked: boolean
link_flair_richtext: any[]
hidden: boolean
pwls: number
link_flair_css_class: string
downs: number
thumbnail_height: any
top_awarded_type: any
hide_score: boolean
quarantine: boolean
link_flair_text_color: string
upvote_ratio: number
author_flair_background_color: any
subreddit_type: string
ups: number
total_awards_received: number
media_embed?: any
secure_media_embed?: any
thumbnail_width: any
author_flair_template_id: any
is_original_content: boolean
user_reports: any[]
secure_media: any
is_reddit_media_domain: boolean
is_meta: boolean
category: any
link_flair_text: string
can_mod_post: boolean
approved_by: any
is_created_from_ads_ui: boolean
author_premium: boolean
thumbnail?: string
edited: boolean
author_flair_css_class: any
author_flair_richtext: any[]
gildings?: any
content_categories: any
mod_note: any
created: number
link_flair_type: string
wls: number
removed_by_category: any
banned_by: any
author_flair_type: string
domain: string
allow_live_comments: boolean
selftext_html: string
likes: any
suggested_sort: any
banned_at_utc: any
view_count: any
archived: boolean
no_follow: boolean
is_crosspostable: boolean
pinned: boolean
over_18: boolean
all_awardings: any[]
awarders: any[]
media_only: boolean
link_flair_template_id: string
can_gild: boolean
spoiler: boolean
locked: boolean
author_flair_text: any
treatment_tags: any[]
visited: boolean
removed_by: any
num_reports: any
distinguished: any
subreddit_id: string
author_is_blocked: boolean
mod_reason_by: any
removal_reason: any
link_flair_background_color: string
is_robot_indexable: boolean
report_reasons: any
discussion_type: any
num_comments: number
send_replies: boolean
contest_mode: boolean
mod_reports: any[]
author_patreon_flair: boolean
author_flair_text_color: any
stickied: boolean
subreddit_subscribers: number
created_utc: number
num_crossposts: number
media?: any
is_video: boolean
// preview images
preview?: {
enabled: boolean
images: Array<{
id: string
source: Image
resolutions: Image[]
variants?: Record<
string,
{
id: string
source: Image
resolutions: Image[]
}
>
}>
}
}
export interface Image {
url: string
width: number
height: number
}
export interface PostT3 {
kind: 't3'
data: FullPost
}
export interface PostListingResponse {
kind: 'Listing'
data: {
after: string
dist: number
modhash: string
geo_filter?: null
children: PostT3[]
}
before?: null
}
export type PostFilter = 'hot' | 'top' | 'new' | 'rising'
export type GeoFilter =
| 'GLOBAL'
| 'US'
| 'AR'
| 'AU'
| 'BG'
| 'CA'
| 'CL'
| 'CO'
| 'HR'
| 'CZ'
| 'FI'
| 'FR'
| 'DE'
| 'GR'
| 'HU'
| 'IS'
| 'IN'
| 'IE'
| 'IT'
| 'JP'
| 'MY'
| 'MX'
| 'NZ'
| 'PH'
| 'PL'
| 'PT'
| 'PR'
| 'RO'
| 'RS'
| 'SG'
| 'ES'
| 'SE'
| 'TW'
| 'TH'
| 'TR'
| 'GB'
| 'US_WA'
| 'US_DE'
| 'US_DC'
| 'US_WI'
| 'US_WV'
| 'US_HI'
| 'US_FL'
| 'US_WY'
| 'US_NH'
| 'US_NJ'
| 'US_NM'
| 'US_TX'
| 'US_LA'
| 'US_NC'
| 'US_ND'
| 'US_NE'
| 'US_TN'
| 'US_NY'
| 'US_PA'
| 'US_CA'
| 'US_NV'
| 'US_VA'
| 'US_CO'
| 'US_AK'
| 'US_AL'
| 'US_AR'
| 'US_VT'
| 'US_IL'
| 'US_GA'
| 'US_IN'
| 'US_IA'
| 'US_OK'
| 'US_AZ'
| 'US_ID'
| 'US_CT'
| 'US_ME'
| 'US_MD'
| 'US_MA'
| 'US_OH'
| 'US_UT'
| 'US_MO'
| 'US_MN'
| 'US_MI'
| 'US_RI'
| 'US_KS'
| 'US_MT'
| 'US_MS'
| 'US_SC'
| 'US_KY'
| 'US_OR'
| 'US_SD'
export type TimePeriod = 'hour' | 'day' | 'week' | 'month' | 'year' | 'all'
export type GetSubredditPostsOptions = {
subreddit: string
type?: PostFilter
// Pagination size and offset (count)
limit?: number
count?: number
// Pagination by fullnames of posts
before?: string
after?: string
/**
* Geographical filter. Only applicable to 'hot' posts.
*/
geo?: GeoFilter
/**
* Filter by time period. Only applicable to 'top' posts.
*/
time?: TimePeriod
}
export interface PostListingResult {
subreddit: string
type: PostFilter
geo?: GeoFilter
time?: TimePeriod
posts: Post[]
}
}
/**
* Basic readonly Reddit API for fetching top/hot/new/rising posts from subreddits.
*
* Uses Reddit's legacy JSON API aimed at RSS feeds.
*
* @see https://old.reddit.com/dev/api
*/
export class RedditClient extends AIFunctionsProvider {
protected readonly ky: KyInstance
protected readonly baseUrl: string
constructor({
baseUrl = reddit.BASE_URL,
userAgent = 'agentic-reddit-client/1.0.0',
timeoutMs = 60_000,
ky = defaultKy
}: {
baseUrl?: string
userAgent?: string
timeoutMs?: number
ky?: KyInstance
} = {}) {
super()
this.baseUrl = baseUrl
this.ky = ky.extend({
prefixUrl: this.baseUrl,
timeout: timeoutMs,
headers: {
'User-Agent': userAgent
}
})
}
/**
* Fetches posts from a subreddit.
*
* @see https://old.reddit.com/dev/api/#GET_hot
*/
@aiFunction({
name: 'reddit_get_subreddit_posts',
description: 'Fetches posts from a subreddit.',
inputSchema: z.object({
subreddit: z.string().describe('The subreddit to fetch posts from.'),
type: z
.union([
z.literal('hot'),
z.literal('top'),
z.literal('new'),
z.literal('rising')
])
.optional()
.describe('Type of posts to fetch (defaults to "hot").'),
limit: z
.number()
.int()
.max(100)
.optional()
.describe('Max number of posts to return (defaults to 5).'),
count: z
.number()
.int()
.optional()
.describe('Number of posts to offset by (defaults to 0).'),
time: z
.union([
z.literal('hour'),
z.literal('day'),
z.literal('week'),
z.literal('month'),
z.literal('year'),
z.literal('all')
])
.optional()
.describe(
'Time period to filter posts by (defaults to "all"). Only applicable to "top" posts type.'
)
})
})
async getSubredditPosts(
subredditOrOpts: string | reddit.GetSubredditPostsOptions
): Promise<reddit.PostListingResult> {
const params =
typeof subredditOrOpts === 'string'
? { subreddit: subredditOrOpts }
: subredditOrOpts
const { subreddit, type = 'hot', limit = 5, geo, time, ...opts } = params
const res = await this.ky
.get(`r/${subreddit}/${type}.json`, {
searchParams: sanitizeSearchParams({
...opts,
limit,
g: type === 'hot' ? geo : undefined,
t: type === 'top' ? time : undefined
})
})
.json<reddit.PostListingResponse>()
return {
subreddit,
type,
geo: type === 'hot' ? geo : undefined,
time: type === 'top' ? time : undefined,
posts: res.data.children.map((child) => {
const post = child.data
// Trim the post data to only include the bare minimum
// TODO: add preview images
// TODO: add video media info
return {
...pick(
post,
'id',
'name',
'title',
'subreddit',
'selftext',
'author',
'author_fullname',
'url',
'permalink',
'thumbnail',
'thumbnail_width',
'thumbnail_height',
'score',
'ups',
'downs',
'num_comments',
'created_utc',
'is_self',
'is_video'
),
permalink: `${this.baseUrl}${post.permalink}`,
thumbnail:
post.thumbnail !== 'self' &&
post.thumbnail !== 'default' &&
post.thumbnail !== 'spoiler' &&
post.thumbnail !== 'nsfw'
? post.thumbnail
: undefined
}
})
}
}
}

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -163,7 +163,7 @@ export class RocketReachClient extends AIFunctionsProvider {
} = {}) {
assert(
apiKey,
`RocketReachClient missing required "username" (defaults to "ROCKETREACH_API_KEY")`
`RocketReachClient missing required "apiKey" (defaults to "ROCKETREACH_API_KEY")`
)
super()

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/searxng"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/serpapi"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/serper"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/slack"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/social-data"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/stdlib"
},
"type": "module",
"source": "./src/index.ts",
@ -31,6 +32,7 @@
"test:typecheck": "tsc --noEmit"
},
"dependencies": {
"@agentic/airtable": "workspace:*",
"@agentic/apollo": "workspace:*",
"@agentic/arxiv": "workspace:*",
"@agentic/bing": "workspace:*",
@ -46,6 +48,8 @@
"@agentic/firecrawl": "workspace:*",
"@agentic/github": "workspace:*",
"@agentic/google-custom-search": "workspace:*",
"@agentic/google-docs": "workspace:*",
"@agentic/google-drive": "workspace:*",
"@agentic/gravatar": "workspace:*",
"@agentic/hacker-news": "workspace:*",
"@agentic/hunter": "workspace:*",
@ -61,6 +65,7 @@
"@agentic/polygon": "workspace:*",
"@agentic/predict-leads": "workspace:*",
"@agentic/proxycurl": "workspace:*",
"@agentic/reddit": "workspace:*",
"@agentic/rocketreach": "workspace:*",
"@agentic/searxng": "workspace:*",
"@agentic/serpapi": "workspace:*",
@ -70,10 +75,12 @@
"@agentic/tavily": "workspace:*",
"@agentic/twilio": "workspace:*",
"@agentic/twitter": "workspace:*",
"@agentic/typeform": "workspace:*",
"@agentic/weather": "workspace:*",
"@agentic/wikidata": "workspace:*",
"@agentic/wikipedia": "workspace:*",
"@agentic/wolfram-alpha": "workspace:*",
"@agentic/youtube": "workspace:*",
"@agentic/zoominfo": "workspace:*",
"@e2b/code-interpreter": "catalog:"
},

Wyświetl plik

@ -1,3 +1,4 @@
export * from '@agentic/airtable'
export * from '@agentic/apollo'
export * from '@agentic/arxiv'
export * from '@agentic/bing'
@ -12,6 +13,8 @@ export * from '@agentic/exa'
export * from '@agentic/firecrawl'
export * from '@agentic/github'
export * from '@agentic/google-custom-search'
export * from '@agentic/google-docs'
export * from '@agentic/google-drive'
export * from '@agentic/gravatar'
export * from '@agentic/hacker-news'
export * from '@agentic/hunter'
@ -27,6 +30,7 @@ export * from '@agentic/perigon'
export * from '@agentic/polygon'
export * from '@agentic/predict-leads'
export * from '@agentic/proxycurl'
export * from '@agentic/reddit'
export * from '@agentic/rocketreach'
export * from '@agentic/searxng'
export * from '@agentic/serpapi'
@ -36,8 +40,10 @@ export * from '@agentic/social-data'
export * from '@agentic/tavily'
export * from '@agentic/twilio'
export * from '@agentic/twitter'
export * from '@agentic/typeform'
export * from '@agentic/weather'
export * from '@agentic/wikidata'
export * from '@agentic/wikipedia'
export * from '@agentic/wolfram-alpha'
export * from '@agentic/youtube'
export * from '@agentic/zoominfo'

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/tavily"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/twilio"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/twitter"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -141,10 +141,10 @@ const twitterApiRateLimitsByPlan: Record<
}
/**
* Twitter API v2 client wrapper with rate-limited methods and `@aiFunction`
* Twitter/X API v2 client wrapper with rate-limited methods and `@aiFunction`
* compatibility.
*
* Rate limits differ by plan, so make sure theh `twitterApiPlan` parameter is
* Rate limits differ by plan, so make sure the `twitterApiPlan` parameter is
* properly set to maximize your rate-limit usage.
*
* @note This class does not handle distributed rate-limits. It assumes a

Wyświetl plik

@ -0,0 +1,44 @@
{
"name": "@agentic/typeform",
"version": "7.6.3",
"description": "Agentic SDK for the Typeform API.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/typeform"
},
"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",
"dev": "tsup --watch",
"clean": "del dist",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:typecheck": "tsc --noEmit"
},
"dependencies": {
"@agentic/core": "workspace:*",
"ky": "catalog:"
},
"peerDependencies": {
"zod": "catalog:"
},
"publishConfig": {
"access": "public"
}
}

Wyświetl plik

@ -0,0 +1,24 @@
<p align="center">
<a href="https://agentic.so">
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
</a>
</p>
<p align="center">
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
</p>
<p align="center">
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
</p>
# 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)

Wyświetl plik

@ -0,0 +1 @@
export * from './typeform-client'

Wyświetl plik

@ -0,0 +1,197 @@
import {
aiFunction,
AIFunctionsProvider,
assert,
getEnv,
sanitizeSearchParams
} from '@agentic/core'
import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod'
export namespace typeform {
export const API_BASE_URL = 'https://api.typeform.com'
export interface GetInsightsForFormResponse {
fields: Array<{
dropoffs: number
id: string
label: string
ref: string
title: string
type: string
views: number
}>
form: {
platforms: Array<{
average_time: number
completion_rate: number
platform: string
responses_count: number
total_visits: number
unique_visits: number
}>
summary: {
average_time: number
completion_rate: number
responses_count: number
total_visits: number
unique_visits: number
}
}
}
export interface GetResponsesForFormParams {
formId: string
pageSize?: number
since?: string
until?: string
completed?: boolean
}
export interface GetResponsesForFormResponse {
total_items: number
page_count: number
items: Array<{
landing_id: string
token: string
landed_at: string
submitted_at: string
metadata: {
user_agent: string
platform: string
referer: string
network_id: string
browser: string
}
answers: Array<{
field: {
id: string
type: string
ref: string
}
type: string
[key: string]: any
}>
hidden: Record<string, any>
calculated: {
score: number
}
variables: Array<{
key: string
type: string
[key: string]: any
}>
}>
}
}
/**
* Readonly Typeform API client for fetching form insights and responses.
*
* @see https://www.typeform.com/developers/get-started/
*/
export class TypeformClient extends AIFunctionsProvider {
protected readonly ky: KyInstance
protected readonly apiKey: string
protected readonly apiBaseUrl: string
constructor({
apiKey = getEnv('TYPEFORM_API_KEY'),
apiBaseUrl = typeform.API_BASE_URL,
timeoutMs = 60_000,
ky = defaultKy
}: {
/** Typeform Personal Access Token */
apiKey?: string
apiBaseUrl?: string
timeoutMs?: number
ky?: KyInstance
} = {}) {
assert(
apiKey,
'TypeformClient missing required "apiKey" (defaults to "TYPEFORM_API_KEY")'
)
super()
this.apiKey = apiKey
this.apiBaseUrl = apiBaseUrl
this.ky = ky.extend({
prefixUrl: this.apiBaseUrl,
timeout: timeoutMs,
headers: {
Authorization: `Bearer ${this.apiKey}`
}
})
}
/**
* Retrieves insights and analytics for a Typeform form.
*/
@aiFunction({
name: 'typeform_get_insights_for_form',
description: 'Retrieve insights and analytics for a Typeform form.',
inputSchema: z.object({
formId: z
.string()
.describe('The ID of the Typeform form to get insights for.')
})
})
async getInsightsForForm(
formIdOrOptions: string | { formId: string }
): Promise<typeform.GetInsightsForFormResponse> {
const { formId } =
typeof formIdOrOptions === 'string'
? { formId: formIdOrOptions }
: formIdOrOptions
const encodedFormId = encodeURIComponent(formId)
return this.ky
.get(`insights/${encodedFormId}/summary`)
.json<typeform.GetInsightsForFormResponse>()
}
/**
* Retrieves responses for a Typeform form.
*/
@aiFunction({
name: 'typeform_get_responses_for_form',
description: 'Retrieve responses for a Typeform form.',
inputSchema: z.object({
formId: z
.string()
.describe('The ID of the Typeform form to get responses for.'),
pageSize: z
.number()
.describe('The number of responses to retrieve per page.')
.optional(),
since: z
.string()
.describe('The date to start retrieving responses from.')
.optional(),
until: z
.string()
.describe('The date to stop retrieving responses at.')
.optional(),
completed: z
.boolean()
.describe('Filter responses by completion status.')
.optional()
})
})
async getResponsesForForm(
formIdOrOptions: string | typeform.GetResponsesForFormParams
): Promise<typeform.GetResponsesForFormResponse> {
const { formId, ...params } =
typeof formIdOrOptions === 'string'
? { formId: formIdOrOptions }
: formIdOrOptions
const encodedFormId = encodeURIComponent(formId)
return this.ky
.get(`forms/${encodedFormId}/responses`, {
searchParams: sanitizeSearchParams(params)
})
.json<typeform.GetResponsesForFormResponse>()
}
}

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/weather"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/wikidata"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/wikipedia"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/wolfram-alpha"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -6,7 +6,8 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git"
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/xsai"
},
"type": "module",
"source": "./src/index.ts",

Wyświetl plik

@ -0,0 +1,44 @@
{
"name": "@agentic/youtube",
"version": "7.6.3",
"description": "Agentic SDK for the YouTube data API.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic.git",
"directory": "packages/youtube"
},
"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",
"dev": "tsup --watch",
"clean": "del dist",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:typecheck": "tsc --noEmit"
},
"dependencies": {
"@agentic/core": "workspace:*",
"ky": "catalog:"
},
"peerDependencies": {
"zod": "catalog:"
},
"publishConfig": {
"access": "public"
}
}

Wyświetl plik

@ -0,0 +1,24 @@
<p align="center">
<a href="https://agentic.so">
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
</a>
</p>
<p align="center">
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
</p>
<p align="center">
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
</p>
# 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)

Wyświetl plik

@ -0,0 +1 @@
export * from './youtube-client'

Wyświetl plik

@ -0,0 +1,270 @@
import {
aiFunction,
AIFunctionsProvider,
assert,
getEnv,
sanitizeSearchParams
} from '@agentic/core'
import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod'
export namespace youtube {
export const API_BASE_URL = 'https://www.googleapis.com/youtube/v3'
export interface SearchOptions {
query: string
maxResults?: number
pageToken?: string
channelId?: string
channelType?: 'any' | 'show'
eventType?: 'live' | 'completed' | 'upcoming'
location?: string
locationRadius?: string
order?:
| 'relevance'
| 'date'
| 'rating'
| 'title'
| 'videoCount'
| 'viewCount'
// The value is an RFC 3339 formatted date-time value (1970-01-01T00:00:00Z).
publishedAfter?: string
publishedBefore?: string
// The regionCode parameter instructs the API to return search results for videos that can be viewed in the specified country. The parameter value is an ISO 3166-1 alpha-2 country code.
regionCode?: string
relevanceLanguage?: string
safeSearch?: 'moderate' | 'none' | 'strict'
topicId?: string
videoCaption?: 'any' | 'closedCaption' | 'none'
videoCategoryId?: string
videoDefinition?: 'any' | 'high' | 'standard'
videoDimension?: '2d' | '3d' | 'any'
videoDuration?: 'any' | 'long' | 'medium' | 'short'
videoEmbeddable?: 'any' | 'true'
videoLicense?: 'any' | 'creativeCommon' | 'youtube'
videoPaidProductPlacement?: 'any' | 'true'
videoSyndicated?: 'any' | 'true'
videoType?: 'any' | 'episode' | 'movie'
}
export type SearchType = 'video' | 'channel' | 'playlist'
export interface SearchVideosResult {
videoId: string
title: string
description: string
thumbnail: string
channelId: string
channelTitle: string
publishedAt: string
url: string
}
export interface SearchChannelsResult {
channelId: string
title: string
description: string
thumbnail: string
publishedAt: string
url: string
}
export type SearchResponse<T extends SearchType> = {
results: T extends 'video'
? SearchVideosResult[]
: T extends 'channel'
? SearchChannelsResult[]
: never
totalResults: number
prevPageToken?: string
nextPageToken?: string
}
export type SearchVideosResponse = SearchResponse<'video'>
export type SearchChannelsResponse = SearchResponse<'channel'>
}
/**
* YouTube data API v3 client.
*
* @see https://developers.google.com/youtube/v3
*/
export class YouTubeClient extends AIFunctionsProvider {
protected readonly ky: KyInstance
protected readonly apiKey: string
protected readonly apiBaseUrl: string
constructor({
apiKey = getEnv('YOUTUBE_API_KEY'),
apiBaseUrl = youtube.API_BASE_URL,
timeoutMs = 30_000,
ky = defaultKy
}: {
apiKey?: string
apiBaseUrl?: string
timeoutMs?: number
ky?: KyInstance
} = {}) {
assert(
apiKey,
'YouTubeClient missing required "apiKey" (defaults to "YOUTUBE_API_KEY")'
)
super()
this.apiKey = apiKey
this.apiBaseUrl = apiBaseUrl
this.ky = ky.extend({
prefixUrl: this.apiBaseUrl,
timeout: timeoutMs
})
}
/**
* Searches for videos on YouTube.
*
* @see https://developers.google.com/youtube/v3/docs/search/list
*/
@aiFunction({
name: 'youtube_search_videos',
description: 'Searches for videos on YouTube.',
inputSchema: z.object({
query: z.string().describe(`The query to search for.
Your request can optionally use the Boolean NOT (-) and OR (|) operators to exclude videos or to find videos that are associated with one of several search terms. For example, to search for videos matching either "boating" or "sailing", set the query parameter value to boating|sailing. Similarly, to search for videos matching either "boating" or "sailing" but not "fishing", set the query parameter value to boating|sailing -fishing.`),
maxResults: z
.number()
.int()
.optional()
.describe('The maximum number of results to return (defaults to 5).')
})
})
async searchVideos(
queryOrOpts: string | youtube.SearchOptions
): Promise<youtube.SearchVideosResponse> {
const opts =
typeof queryOrOpts === 'string' ? { query: queryOrOpts } : queryOrOpts
const data = await this._search({
...opts,
type: 'video'
})
const results = (data.items || [])
.map((item: any) => {
const snippet = item.snippet
if (!snippet) return null
const videoId = item.id?.videoId
if (!videoId) return null
const thumbnails = snippet.thumbnails
if (!thumbnails) return null
return {
videoId,
title: snippet.title,
description: snippet.description,
// https://i.ytimg.com/vi/MRtg6A1f2Ko/maxresdefault.jpg
thumbnail:
thumbnails.high?.url ||
thumbnails.medium?.url ||
thumbnails.default?.url ||
`https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`,
channelId: snippet.channelId,
channelTitle: snippet.channelTitle,
publishedAt: snippet.publishedAt,
url: `https://www.youtube.com/watch?v=${videoId}`
}
})
.filter(Boolean)
return {
results,
totalResults: data.pageInfo?.totalResults || 0,
prevPageToken: data.prevPageToken,
nextPageToken: data.nextPageToken
}
}
/**
* Searches for channels on YouTube.
*
* @see https://developers.google.com/youtube/v3/docs/search/list
*/
@aiFunction({
name: 'youtube_search_channels',
description: 'Searches for channels on YouTube.',
inputSchema: z.object({
query: z.string().describe('The query to search for.'),
maxResults: z
.number()
.int()
.optional()
.describe('The maximum number of results to return (defaults to 5).')
})
})
async searchChannels(
queryOrOpts: string | youtube.SearchOptions
): Promise<youtube.SearchChannelsResponse> {
const opts =
typeof queryOrOpts === 'string' ? { query: queryOrOpts } : queryOrOpts
const data = await this._search({
...opts,
type: 'channel'
})
const results = (data.items || [])
.map((item: any) => {
const snippet = item.snippet
if (!snippet) return null
const channelId = item.id?.channelId
if (!channelId) return null
const thumbnails = snippet.thumbnails
if (!thumbnails) return null
return {
channelId,
title: snippet.title,
description: snippet.description,
thumbnail:
thumbnails.high?.url ||
thumbnails.medium?.url ||
thumbnails.default?.url,
publishedAt: snippet.publishedAt,
url: `https://www.youtube.com/channel/${channelId}`
}
})
.filter(Boolean)
return {
results,
totalResults: data.pageInfo?.totalResults || 0,
prevPageToken: data.prevPageToken,
nextPageToken: data.nextPageToken
}
}
protected async _search(
opts: youtube.SearchOptions & {
type: youtube.SearchType
}
) {
const { query, ...params } = opts
return this.ky
.get('search', {
searchParams: sanitizeSearchParams({
q: query,
part: 'snippet',
maxResults: 5,
...params,
key: this.apiKey
})
})
.json<any>()
}
}

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Plik diff jest za duży Load Diff

Wyświetl plik

@ -6,20 +6,21 @@ updateConfig:
- p-throttle
- eslint
catalog:
'@ai-sdk/openai': ^1.3.7
'@apidevtools/swagger-parser': ^10.1.1
'@dexaai/dexter': ^4.1.1
'@e2b/code-interpreter': ^1.1.0
'@fisch0920/config': ^1.0.2
'@langchain/core': ^0.3.43
'@langchain/openai': ^0.5.4
'@mastra/core': ^0.7.0
'@modelcontextprotocol/sdk': ^1.8.0
'@nangohq/node': 0.42.22 # pinned for now
'@types/jsrsasign': ^10.5.15
'@types/node': ^22.14.0
'@xsai/tool': ^0.2.0-beta.3
ai: ^4.3.2
"@ai-sdk/openai": ^1.3.9
"@apidevtools/swagger-parser": ^10.1.1
"@dexaai/dexter": ^4.1.1
"@e2b/code-interpreter": ^1.1.0
"@fisch0920/config": ^1.0.2
"@googleapis/customsearch": ^3.2.0
"@langchain/core": ^0.3.44
"@langchain/openai": ^0.5.5
"@mastra/core": ^0.8.1
"@modelcontextprotocol/sdk": ^1.9.0
"@nangohq/node": 0.42.22
"@types/jsrsasign": ^10.5.15
"@types/node": ^22.14.0
"@xsai/tool": ^0.2.0-beta.3
ai: ^4.3.4
bumpp: ^10.1.0
camelcase: ^8.0.0
cleye: ^1.3.4
@ -33,26 +34,27 @@ catalog:
execa: ^9.5.2
exit-hook: ^4.0.0
fast-xml-parser: ^5.2.0
genkit: ^1.5.0
genkit: ^1.6.0
genkitx-openai: ^0.20.2
'@googleapis/customsearch': ^3.2.0
google-auth-library: ^9.15.1
googleapis: ^148.0.0
json-schema-to-zod: ^2.6.1
jsonrepair: ^3.12.0
jsrsasign: ^10.9.0
ky: ^1.8.0
langchain: ^0.3.20
langchain: ^0.3.21
lint-staged: ^15.5.0
llamaindex: ^0.9.16
llamaindex: ^0.9.17
mathjs: ^13.2.3
npm-run-all2: ^7.0.2
octokit: ^4.1.2
only-allow: ^1.2.1
openai: ^4.91.1
openai: ^4.93.0
openai-fetch: ^3.4.2
openai-zod-to-json-schema: ^1.0.3
openapi-types: ^12.1.3
p-map: ^7.0.3
p-throttle: 6.2.0 # pinned for now
p-throttle: 6.2.0
prettier: ^3.5.3
restore-cursor: ^5.1.0
simple-git-hooks: ^2.12.1

Wyświetl plik

@ -180,6 +180,7 @@ Full docs are available at [agentic.so](https://agentic.so).
| Service / Tool | Package | Docs | Description |
| ------------------------------------------------------------------------------- | ------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Airtable](https://airtable.com/developers/web/api/introduction) | `@agentic/airtable` | [docs](https://agentic.so/tools/airtable) | No-code spreadsheets, CRM, and database. |
| [Apollo](https://docs.apollo.io) | `@agentic/apollo` | [docs](https://agentic.so/tools/apollo) | B2B person and company enrichment API. |
| [ArXiv](https://arxiv.org) | `@agentic/arxiv` | [docs](https://agentic.so/tools/arxiv) | Search for research articles. |
| [Bing](https://www.microsoft.com/en-us/bing/apis/bing-web-search-api) | `@agentic/bing` | [docs](https://agentic.so/tools/bing) | Bing web search. |
@ -193,6 +194,8 @@ Full docs are available at [agentic.so](https://agentic.so).
| [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. |
| [Google Custom Search](https://developers.google.com/custom-search/v1/overview) | `@agentic/google-custom-search` | [docs](https://agentic.so/tools/google-custom-search) | Official Google Custom Search API. |
| [Google Drive](https://developers.google.com/workspace/drive/api) | `@agentic/google-drive` | [docs](https://agentic.so/tools/google-drive) | Simplified Google Drive API. |
| [Google Docs](https://developers.google.com/workspace/docs/api) | `@agentic/google-docs` | [docs](https://agentic.so/tools/google-docs) | Simplified Google Docs API. |
| [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. |
@ -208,6 +211,7 @@ Full docs are available at [agentic.so](https://agentic.so).
| [Polygon](https://polygon.io) | `@agentic/polygon` | [docs](https://agentic.so/tools/polygon) | Stock market and company financial data. |
| [PredictLeads](https://predictleads.com) | `@agentic/predict-leads` | [docs](https://agentic.so/tools/predict-leads) | In-depth company data including signals like fundraising events, hiring news, product launches, technologies used, etc. |
| [Proxycurl](https://nubela.co/proxycurl) | `@agentic/proxycurl` | [docs](https://agentic.so/tools/proxycurl) | People and company data from LinkedIn & Crunchbase. |
| [Reddit](https://old.reddit.com/dev/api) | `@agentic/reddit` | [docs](https://agentic.so/tools/reddit) | Basic readonly Reddit API for getting top/hot/new/rising posts from subreddits. |
| [RocketReach](https://rocketreach.co/api/v2/docs) | `@agentic/rocketreach` | [docs](https://agentic.so/tools/rocketreach) | B2B person and company enrichment API. |
| [Searxng](https://docs.searxng.org) | `@agentic/searxng` | [docs](https://agentic.so/tools/searxng) | OSS meta search engine capable of searching across many providers like Reddit, Google, Brave, Arxiv, Genius, IMDB, Rotten Tomatoes, Wikidata, Wolfram Alpha, YouTube, GitHub, [etc](https://docs.searxng.org/user/configured_engines.html#configured-engines). |
| [SerpAPI](https://serpapi.com/search-api) | `@agentic/serpapi` | [docs](https://agentic.so/tools/serpapi) | Lightweight wrapper around SerpAPI for Google search. |
@ -217,10 +221,12 @@ Full docs are available at [agentic.so](https://agentic.so).
| [Tavily](https://tavily.com) | `@agentic/tavily` | [docs](https://agentic.so/tools/tavily) | Web search API tailored for LLMs. |
| [Twilio](https://www.twilio.com/docs/conversations/api) | `@agentic/twilio` | [docs](https://agentic.so/tools/twilio) | Twilio conversation API to send and receive SMS messages. |
| [Twitter](https://developer.x.com/en/docs/twitter-api) | `@agentic/twitter` | [docs](https://agentic.so/tools/twitter) | Basic Twitter API methods for fetching users, tweets, and searching recent tweets. Includes support for plan-aware rate-limiting. Uses [Nango](https://www.nango.dev) for OAuth support. |
| [Typeform](https://www.typeform.com/developers/get-started/) | `@agentic/typeform` | [docs](https://agentic.so/tools/typeform) | Readonly Typeform client for fetching form insights and responses. |
| [Weather](https://www.weatherapi.com) | `@agentic/weather` | [docs](https://agentic.so/tools/weather) | Basic access to current weather data based on location. |
| [Wikidata](https://www.wikidata.org/wiki/Wikidata:Data_access) | `@agentic/wikidata` | [docs](https://agentic.so/tools/wikidata) | Basic Wikidata client. |
| [Wikipedia](https://www.mediawiki.org/wiki/API) | `@agentic/wikipedia` | [docs](https://agentic.so/tools/wikipedia) | Wikipedia page search and summaries. |
| [Wolfram Alpha](https://products.wolframalpha.com/llm-api/documentation) | `@agentic/wolfram-alpha` | [docs](https://agentic.so/tools/wolfram-alpha) | Wolfram Alpha LLM API client for answering computational, mathematical, and scientific questions. |
| [YouTube](https://developers.google.com/youtube/v3) | `@agentic/youtube` | [docs](https://agentic.so/tools/youtube) | YouTube data API v3 for searching YT videos and channels. |
| [ZoomInfo](https://api-docs.zoominfo.com) | `@agentic/zoominfo` | [docs](https://agentic.so/tools/zoominfo) | Powerful B2B person and company data enrichment. |
> [!NOTE]