@bendyline/gilde 0.1.46 → 0.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/chat-models/index.json +1 -1
- package/data/chat-models/qw/qwen3.8-27b-q4/manifest.json +2 -2
- package/data/chat-models/qw/qwen3.8-27b-q4/versions/1.0.3/manifest.json +116 -0
- package/data/connector-types/az/azure-monitor-logs/manifest.json +18 -0
- package/data/connector-types/az/azure-monitor-logs/versions/1.0.0/manifest.json +81 -0
- package/data/connector-types/ht/http-json-rows/manifest.json +18 -0
- package/data/connector-types/ht/http-json-rows/versions/1.0.0/manifest.json +74 -0
- package/data/connector-types/index.json +1 -1
- package/package.json +1 -1
- package/schemas/chat-model-version.schema.json +62 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schemaVersion":1,"kind":"connector-type","count":15,"entries":[{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"airtable-records","name":"Airtable Records","description":"Mirror an Airtable table into a searchable corpus — one file per record — via the Airtable API, hosted off-platform through a vendored Prismatic component. Captures the table's current rows each sync.","tags":["airtable","records","spectral"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"spectral","configSchema":{"type":"object","properties":{"baseId":{"type":"string","title":"Base ID"},"tableName":{"type":"string","title":"Table name"},"view":{"type":"string","title":"View (optional)"},"filterByFormula":{"type":"string","title":"Filter by formula (optional)"}},"required":["baseId","tableName"]},"secretShape":{"kind":"apikey","field":"apiKey","label":"Airtable personal access token","required":true,"x-gezel-priority":"primary","description":"Use an Airtable personal access token, not a legacy API key. Give it read access to records and access only to the base you want Gezel to sync.","helpUrl":"https://airtable.com/create/tokens","helpLabel":"Open Airtable token settings"},"setupInstructions":{"title":"Create an Airtable personal access token","description":"Airtable retired legacy API keys. Create a separate, narrowly scoped token for Gezel instead of sharing your Airtable account password.","steps":["Open Airtable's token settings using the link below and choose Create token.","Name the token Gezel and add the data.records:read scope.","Under Access, add only the base or workspace that contains the table you want to sync.","Create the token, copy it once, and paste it into the Airtable personal access token field."],"url":"https://airtable.com/create/tokens","urlLabel":"Open Airtable token settings"},"source":{"component":"airtable","action":"listRecords","connectionKey":"personalAccessToken","connectionInput":"airtableConnection","inputs":{"baseId":"$config.baseId","tableName":"$config.tableName","view":"$config.view","filterByFormula":"$config.filterByFormula"},"itemsPath":"$.data"},"normalize":{"kind":"mapping","map":{"id":"$.id","title":"$.fields.Name","timestamp":"$.createdTime","body":"$.fields","frontmatter":{"recordId":"$.id","createdTime":"$.createdTime"}}},"actions":[],"completeness":"mirror","notes":"Personal Access Token connection. Each sync re-fetches every row in the table (no incremental cursor); the writer is content-hash idempotent, so re-syncs are cheap and only changed rows rewrite. Deleted Airtable rows are not pruned from the corpus. `title` maps to the `Name` field (Airtable's usual primary column) and falls back to the record id when absent.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"bluesky-posts","name":"Bluesky Posts","description":"Keep a searchable copy of your Bluesky presence: your posts with their likes and reposts, mentions and replies from others, and a daily follower count. Sign in with an app password from Bluesky's settings — never your real password. Your crew can also draft posts for you, and with your permission publish them; every publish still asks for your consent.","tags":["bluesky","social","posts","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"handle":{"type":"string","title":"Bluesky handle","description":"Your full handle, such as yourname.bsky.social.","x-gezel-priority":"primary"},"service":{"type":"string","title":"Service URL","default":"https://bsky.social"},"syncMentions":{"type":"boolean","title":"Sync mentions, replies, and quotes","default":true},"allowPublish":{"type":"boolean","title":"Allow this project's gezels to queue posts for publishing (each publish still needs your consent)","default":false},"metricsWindowDays":{"type":"integer","title":"Days of recent posts to keep engagement counts fresh for","default":30,"minimum":1,"maximum":365},"metricsRefreshHours":{"type":"number","title":"Hours between engagement-count refreshes","default":6},"statsIntervalHours":{"type":"number","title":"Hours between account-stats snapshots","default":6},"metricsHistory":{"type":"boolean","title":"Record daily engagement history records","description":"One per post per day while a post stays inside the metrics window","default":true}},"required":["handle"]},"secretShape":{"kind":"apikey","field":"appPassword","label":"Bluesky app password","required":true,"x-gezel-priority":"primary","description":"Do not enter your normal Bluesky account password. Create a separate app password in Bluesky, then paste the generated app password here.","helpUrl":"https://bsky.app/settings/app-passwords","helpLabel":"Open Bluesky app-password settings"},"setupInstructions":{"title":"Create a Bluesky app password","description":"Create this password inside Bluesky. If Bluesky asks you to sign in, enter your normal password only on Bluesky's own site — never in Gezel.","steps":["Open Bluesky's App Passwords settings using the link below and sign in to Bluesky if asked.","Choose Add App Password and give it a name such as Gezel.","Copy the generated app password, return to Gezel, and paste it into the Bluesky app password field."],"url":"https://bsky.app/settings/app-passwords","urlLabel":"Open Bluesky app-password settings"},"source":{"adapterId":"bluesky-posts"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"social-publish"}],"completeness":"window","notes":"Syncs three things from the signed-in account: the account's own posts (text, language, reply threading, link and image-description context, plus like/repost/reply/quote counts), mentions of the account (mention, reply, and quote notifications), and a once-per-day account-stats record (followers, following, total posts) refreshed at the configured interval. Engagement counts on posts from the last metricsWindowDays are re-observed roughly every metricsRefreshHours, updating the existing files in place; unless metricsHistory is turned off, each observation also writes a per-post per-day engagement history record under the posts corpus's metrics-history/ bucket, so engagement curves stay reconstructable after counts move on. The first sync captures up to 500 recent posts; older history is not backfilled. Reposts are skipped in this version, and synced images are represented by their alt text rather than downloaded. Publishing is off by default: a gezel can only draft posts until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Published posts can embed up to 4 images (1,000,000 bytes each at most; png/jpg/jpeg/webp/gif), read from project-relative paths resolved against the project's artifacts directory first, then its workspace.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"mail-imap","name":"Email (IMAP)","description":"Mirror an IMAP mailbox into a normalized, searchable corpus of messages.","tags":["email","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"address":{"type":"string","title":"Email address"},"provider":{"type":"string","const":"imap"},"syncFolders":{"type":"array","title":"Folders to sync","items":{"type":"string"}},"allowedRecipients":{"type":"array","title":"Allowed send recipients","items":{"type":"string"}},"allowedDomains":{"type":"array","title":"Allowed send domains","items":{"type":"string"}}},"required":["address"]},"secretShape":{"kind":"imap","description":"Use an app-specific password when your mail provider offers one. Do not enter your normal mail password unless your provider explicitly says it supports password-based IMAP access."},"setupInstructions":{"title":"Check which password your mail provider requires","description":"For Gmail, Outlook.com, or Microsoft 365, prefer Gezel's dedicated connector so you can authorize through the provider's website. Use generic IMAP for other providers or when your provider specifically supports an app password.","steps":["Gmail: use the Gmail connector when possible. Otherwise, with 2-Step Verification enabled, create a 16-digit App Password in your Google Account security settings.","iCloud Mail: at account.apple.com, open Sign-In and Security, then App-Specific Passwords, and generate one for Gezel.","Yahoo Mail: open Yahoo Account Security, then External connections, and create an app password for Gezel.","Outlook.com or Microsoft 365: use Gezel's Outlook.com or Microsoft 365 connector instead of generic IMAP whenever possible.","For another provider, check its IMAP documentation before entering a password. Use a dedicated app password if it offers one."]},"source":{"adapterId":"mail-imap"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"github-issues","name":"GitHub Issues","description":"Mirror recent issues from one GitHub repository into searchable project files. Public repositories work without sign-in; add a token for private repositories or higher rate limits.","tags":["github","issues","script"],"maintainer":{"name":"Gezel"},"version":"1.0.2","releasedAt":"2026-08-13T00:00:00Z","driver":"script","configSchema":{"type":"object","properties":{"owner":{"type":"string","title":"Repository owner"},"repository":{"type":"string","title":"Repository name"},"apiBaseUrl":{"type":"string","title":"API base URL (GitHub Enterprise only)"}},"required":["owner","repository"]},"secretShape":{"kind":"apikey","field":"token","label":"GitHub personal access token","required":false,"description":"Optional. Leave this blank for public repositories. For a private repository or higher API limits, create a fine-grained personal access token—never enter your GitHub account password—and grant read-only Issues access to only this repository.","helpUrl":"https://github.com/settings/personal-access-tokens/new","helpLabel":"Create a fine-grained GitHub token"},"setupInstructions":{"title":"A GitHub token is optional","description":"Public repositories work without a token. Add a fine-grained personal access token only for a private repository or higher API rate limits.","steps":["Leave the token field blank if the repository is public and the anonymous API limit is sufficient.","Otherwise, open GitHub's fine-grained token form using the link below and choose the repository owner.","Limit repository access to the repository you want to sync and grant Repository permissions → Issues: Read-only.","Set an expiration, generate the token, and paste it here. Treat the token like a password; never paste your GitHub account password into Gezel."],"url":"https://github.com/settings/personal-access-tokens/new","urlLabel":"Create a fine-grained GitHub token"},"source":{"inlineFetch":"import { defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'github-issues-fetch',\n description: 'Fetches a bounded, incremental window of GitHub repository issues.',\n inputs: {\n cursor: { type: 'json', description: 'Last successfully synced GitHub updated_at value.' },\n config: { type: 'json', description: 'Repository owner, name, and optional API base URL.', required: true },\n },\n outputs: {\n records: { type: 'array', description: 'GitHub issues ready for connector normalization.', itemType: 'object' },\n cursor: { type: 'string', description: 'Newest updated_at value processed.', nullable: true },\n rateLimited: { type: 'boolean', description: 'True when GitHub throttled the pass; the engine backs off and retries from this cursor.', nullable: true },\n partial: { type: 'boolean', description: 'True when more pages remained after the per-pass page budget.', nullable: true },\n },\n requires: ['network', 'credential:$credential'],\n} as const);\n\ntype Config = { owner?: unknown; repository?: unknown; apiBaseUrl?: unknown };\ntype Issue = Record<string, unknown> & { updated_at?: unknown; pull_request?: unknown };\nconst input = gezel.input as { cursor?: unknown; config?: Config };\nconst config = input.config ?? {};\nconst owner = typeof config.owner === 'string' ? config.owner.trim() : '';\nconst repository = typeof config.repository === 'string' ? config.repository.trim() : '';\nif (!owner || !repository) throw new Error('GitHub owner and repository are required.');\n\nconst apiBaseUrl =\n typeof config.apiBaseUrl === 'string' && config.apiBaseUrl.trim()\n ? config.apiBaseUrl.trim().replace(/\\/+$/, '')\n : 'https://api.github.com';\nlet apiOrigin: string;\ntry {\n const parsed = new URL(apiBaseUrl);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') throw new Error('bad protocol');\n apiOrigin = parsed.origin;\n} catch {\n throw new Error('GitHub API base URL must be an absolute HTTP or HTTPS URL.');\n}\n\nconst params = new URLSearchParams({\n state: 'all',\n sort: 'updated',\n direction: typeof input.cursor === 'string' ? 'asc' : 'desc',\n per_page: '100',\n});\nif (typeof input.cursor === 'string' && input.cursor) params.set('since', input.cursor);\nlet nextUrl =\n apiBaseUrl + '/repos/' + encodeURIComponent(owner) + '/' + encodeURIComponent(repository) + '/issues?' + params;\nlet nextCursor = typeof input.cursor === 'string' ? input.cursor : null;\nconst records: Issue[] = [];\nlet rateLimited = false;\n\nfunction nextPage(link: string | undefined): string | null {\n if (!link) return null;\n for (const part of link.split(',')) {\n const match = part.match(/<([^>]+)>;\\s*rel=\"next\"/);\n if (match?.[1]) return match[1];\n }\n return null;\n}\n\nfunction isThrottle(status: number, headers: Record<string, string | undefined>): boolean {\n if (status === 429) return true;\n return status === 403 && (headers['x-ratelimit-remaining'] === '0' || Boolean(headers['retry-after']));\n}\n\nfor (let page = 0; page < 5 && nextUrl; page++) {\n const target = new URL(nextUrl);\n if (target.origin !== apiOrigin) throw new Error('GitHub pagination changed origin; refusing to forward the token.');\n const headers = {\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'User-Agent': 'Gezel-GitHub-Issues-Connector',\n };\n let response;\n try {\n response = await gezel.http.authed(target.toString(), {\n credential: '$credential',\n headers,\n });\n } catch (error) {\n if ((error as { code?: string }).code !== 'CREDENTIAL_MISSING') throw error;\n response = await gezel.http.request(target.toString(), { headers });\n }\n if (isThrottle(response.status, response.headers)) {\n // Emit what this pass already collected with the cursor those records\n // reached, and flag the throttle instead of throwing: a throw voids the\n // batch and re-fetches the same window next tick, which is exactly how\n // rate-limited APIs get hammered. The engine persists the cursor only\n // after a clean write and applies its jittered backoff to the binding.\n rateLimited = true;\n break;\n }\n if (!response.ok) {\n throw new Error('GitHub issues request failed (' + response.status + '): ' + response.body.slice(0, 240));\n }\n const pageItems = JSON.parse(response.body) as unknown;\n if (!Array.isArray(pageItems)) throw new Error('GitHub returned a non-array issues response.');\n for (const raw of pageItems) {\n if (!raw || typeof raw !== 'object') continue;\n const issue = raw as Issue;\n const updatedAt = typeof issue.updated_at === 'string' ? issue.updated_at : '';\n if (updatedAt && (!nextCursor || updatedAt > nextCursor)) nextCursor = updatedAt;\n if (issue.pull_request) continue;\n const labels = Array.isArray(issue.labels)\n ? issue.labels\n .map((label) =>\n typeof label === 'string'\n ? label\n : label && typeof label === 'object'\n ? String((label as Record<string, unknown>).name ?? '')\n : '',\n )\n .filter(Boolean)\n : [];\n const assignees = Array.isArray(issue.assignees)\n ? issue.assignees\n .map((person) =>\n person && typeof person === 'object'\n ? String((person as Record<string, unknown>).login ?? '')\n : '',\n )\n .filter(Boolean)\n : [];\n records.push({\n ...issue,\n gezel_repository: owner + '/' + repository,\n gezel_labels: labels,\n gezel_assignees: assignees,\n });\n }\n const candidate = nextPage(response.headers.link);\n nextUrl = candidate && new URL(candidate).origin === apiOrigin ? candidate : '';\n}\n\nconst cursorOut = records.length > 0 ? nextCursor : typeof input.cursor === 'string' ? input.cursor : null;\ngezel.output({\n records,\n cursor: rateLimited ? cursorOut : nextCursor,\n ...(rateLimited ? { rateLimited: true } : {}),\n ...(nextUrl && !rateLimited ? { partial: true } : {}),\n});\n","idPath":"$.id"},"normalize":{"kind":"mapping","map":{"id":"$.id","title":"$.title","group":"$.gezel_repository","timestamp":"$.updated_at","body":"$.body","frontmatter":{"number":"$.number","state":"$.state","stateReason":"$.state_reason","author":"$.user.login","assignees":"$.gezel_assignees","labels":"$.gezel_labels","milestone":"$.milestone.title","comments":"$.comments","url":"$.html_url","createdAt":"$.created_at","closedAt":"$.closed_at","locked":"$.locked"}}},"actions":[],"completeness":"window","notes":"Uses GitHub's REST list-repository-issues endpoint anonymously for public repositories, or with an optional personal access token for private repositories and higher rate limits. Pull requests are excluded because GitHub's Issues API returns both. The first sync captures up to 500 recently updated issues; later syncs resume from updated_at in oldest-first order so busy repositories catch up over multiple passes. Issue comments are represented by their count but comment bodies are not mirrored in this version. Since 1.0.1 the fetch reports GitHub throttling (HTTP 429, or 403 with an exhausted rate-limit budget) as a rateLimited batch instead of an error, so the sync engine backs off and resumes from the last clean cursor, and declares its credential destination origins for the daemon-side allowlist.","availableVersions":["1.0.2","1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"github-pulls","name":"GitHub Pull Requests","description":"Mirror a project's open pull requests into local files: a summary, one file per changed file with its patch, and the complete unified diff. Lets a gezel review a PR by reading the workspace instead of calling the GitHub API mid-turn.","tags":["github","pull-requests","code-review","diff","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"maxPulls":{"type":"integer","title":"Open pull requests to mirror","minimum":1,"maximum":200,"default":20},"includeComments":{"type":"boolean","title":"Include review and issue comments","default":true}}},"secretShape":{"kind":"apikey","field":"token","label":"GitHub personal access token","required":false,"description":"Optional. Leave this blank to use Gezel's existing GitHub sign-in. If you need a separate credential, create a fine-grained personal access token—never enter your GitHub account password—and restrict it to this repository.","helpUrl":"https://github.com/settings/personal-access-tokens/new","helpLabel":"Create a fine-grained GitHub token"},"setupInstructions":{"title":"A separate GitHub token is usually unnecessary","description":"Gezel uses your existing GitHub sign-in when available. Add a connector-specific token only when that sign-in cannot read the repository.","steps":["Try leaving the token field blank first, especially for a public repository.","If a token is needed, open GitHub's fine-grained token form and limit repository access to this project.","Grant Repository permissions → Pull requests: Read-only. If comment syncing is enabled, also grant Issues: Read-only.","Set an expiration, generate the token, and paste it here. Treat the token like a password; never paste your GitHub account password into Gezel."],"url":"https://github.com/settings/personal-access-tokens/new","urlLabel":"Create a fine-grained GitHub token"},"source":{"adapterId":"github-pulls"},"normalize":{"kind":"native"},"actions":[],"completeness":"window","notes":"Takes the repository from the project's own GitHub link rather than binding config, so the coordinates cannot drift from the project. The corpus lives in the project's managed artifacts tree, so mirrored data never dirties the user's checkout. Each open pull request becomes one scope (`pr-<number>`) holding an overview record — title, author, branches, counts, description, and comment digest — plus one record per changed file under `files/` carrying that file's patch, and the complete unified diff as an attachment. Patches and the diff are stored untruncated because a gezel opens these as files. Comment and description text is untrusted and passes through the standard content scan and quarantine. Completeness is `window`: only the newest open pull requests are mirrored, so absence from the corpus does not mean absence upstream. A task launch can target one pull request directly, which syncs just that scope.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"github-releases","name":"GitHub Releases","description":"Mirror GitHub release notes and uploaded release assets into local project files. Public repositories work anonymously; Gezel's GitHub sign-in or an optional token also enables private repositories and draft releases.","tags":["github","releases","artifacts","binaries","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"owner":{"type":"string","title":"Repository owner"},"repository":{"type":"string","title":"Repository name"},"maxReleases":{"type":"integer","title":"Latest releases (blank = all)","minimum":1,"maximum":10000},"includeDrafts":{"type":"boolean","title":"Include draft releases when signed in","default":true},"includePrereleases":{"type":"boolean","title":"Include prereleases","default":true},"downloadAssets":{"type":"boolean","title":"Download uploaded release assets","default":true},"assetNamePattern":{"type":"string","title":"Asset filename globs (optional, comma-separated)"},"maxAssetSizeMb":{"type":"number","title":"Maximum asset size in MB (blank = unlimited)","exclusiveMinimum":0},"apiBaseUrl":{"type":"string","title":"GitHub Enterprise API base URL (optional)"}},"required":["owner","repository"]},"secretShape":{"kind":"apikey","field":"token","label":"GitHub personal access token","required":false,"description":"Optional. Public releases work without a token, and Gezel can use your existing GitHub sign-in. For private repositories or draft releases, use a fine-grained personal access token—never your GitHub account password—restricted to this repository.","helpUrl":"https://github.com/settings/personal-access-tokens/new","helpLabel":"Create a fine-grained GitHub token"},"setupInstructions":{"title":"A GitHub token is optional","description":"Public releases work anonymously. Gezel also uses your existing GitHub sign-in when available, so add a separate token only when the repository or draft releases are not otherwise accessible.","steps":["Try leaving the token field blank first.","If a token is needed, open GitHub's fine-grained token form and limit repository access to the repository whose releases you want to sync.","Grant Repository permissions → Contents: Read-only. Access to draft releases also depends on your GitHub account having access to them.","Set an expiration, generate the token, and paste it here. Treat the token like a password; never paste your GitHub account password into Gezel."],"url":"https://github.com/settings/personal-access-tokens/new","urlLabel":"Create a fine-grained GitHub token"},"source":{"adapterId":"github-releases"},"normalize":{"kind":"native"},"actions":[],"completeness":"mirror","notes":"Lists releases through GitHub's REST API and stores one release-notes record per release. Uploaded assets are streamed to disk, checked against GitHub's size and sha256 digest when supplied, and published as file-backed connector attachments without buffering the entire asset in memory. Public repositories work anonymously. For github.com, a per-binding token wins, then Gezel's stored GitHub sign-in, GH_TOKEN/GITHUB_TOKEN, or a signed-in GitHub CLI. Users with push access receive draft releases from GitHub's list-releases endpoint. Optional filters bound the newest release count, prerelease/draft inclusion, asset filename globs, and per-asset size. GitHub-generated source archives are not release assets and are not downloaded.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"github-wiki","name":"GitHub Wiki","description":"Mirror a repository's wiki pages into searchable project files. Public wikis work anonymously; add a token for private repositories.","tags":["github","wiki","documentation","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"owner":{"type":"string","title":"Repository owner"},"repository":{"type":"string","title":"Repository name"},"host":{"type":"string","title":"GitHub Enterprise host (optional)"}},"required":["owner","repository"]},"secretShape":{"kind":"apikey","field":"token","label":"GitHub personal access token","required":false,"description":"Optional. Leave this blank for a public wiki. For a private repository wiki, create a fine-grained personal access token—never enter your GitHub account password—and grant read-only Contents access to only this repository.","helpUrl":"https://github.com/settings/personal-access-tokens/new","helpLabel":"Create a fine-grained GitHub token"},"setupInstructions":{"title":"A GitHub token is optional","description":"Public wikis clone without a token. Add a fine-grained personal access token only for a private repository wiki.","steps":["Leave the token field blank if the wiki belongs to a public repository.","For a private wiki, open GitHub's fine-grained token form and limit repository access to the repository whose wiki you want to sync.","Grant Repository permissions → Contents: Read-only.","Set an expiration, generate the token, and paste it here. Treat the token like a password; never paste your GitHub account password into Gezel."],"url":"https://github.com/settings/personal-access-tokens/new","urlLabel":"Create a fine-grained GitHub token"},"source":{"adapterId":"github-wiki"},"normalize":{"kind":"native"},"actions":[],"completeness":"mirror","notes":"Uses GitHub's separate .wiki.git repository as a read-only transport. Public wikis clone anonymously. An optional personal access token enables private repository wikis. Supported textual wiki markup is mirrored and scanned as untrusted external content; binary files and Git history are not exposed. Changed pages are stored as immutable revisions. Deletions are not currently pruned from the local corpus.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"mail-gmail","name":"Gmail","description":"Mirror a Gmail mailbox into a normalized, searchable corpus of messages (OAuth).","tags":["email","native","oauth"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-07-08T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"address":{"type":"string","title":"Email address"},"provider":{"type":"string","const":"gmail"},"syncFolders":{"type":"array","title":"Labels to sync","items":{"type":"string"}},"allowedRecipients":{"type":"array","title":"Allowed send recipients","items":{"type":"string"}},"allowedDomains":{"type":"array","title":"Allowed send domains","items":{"type":"string"}}},"required":["address"]},"secretShape":{"kind":"oauth2","authorizeUrl":"https://accounts.google.com/o/oauth2/v2/auth","tokenUrl":"https://oauth2.googleapis.com/token","scopes":"https://mail.google.com/","clientIdEnv":"GEZEL_GOOGLE_CLIENT_ID","clientSecretEnv":"GEZEL_GOOGLE_CLIENT_SECRET","authParams":{"access_type":"offline","prompt":"consent"},"clientSetup":{"providerLabel":"Google","docsUrl":"https://console.cloud.google.com/apis/credentials","secretRequired":true,"appTypeNote":"Create an OAuth client ID of type Desktop app in Google Cloud Console. Desktop clients are issued a client secret Google does not treat as confidential — enter both here. Any loopback port works for the redirect.","steps":["Sign in at console.cloud.google.com and create (or pick) a project.","Under APIs & Services, open Library and enable the Gmail API.","Under APIs & Services, open OAuth consent screen: configure the app as External and add yourself as a test user.","Set the publishing status to In production — apps left in Testing get their sign-ins expired every 7 days. An unverified-app warning on your own app is safe to click through.","Under APIs & Services, open Credentials and choose Create credentials, then OAuth client ID, application type 'Desktop app'.","Copy the Client ID and Client Secret from the created client and paste them below. Desktop clients accept any 127.0.0.1 port, so there is no redirect URI to register."]}},"source":{"adapterId":"mail-gmail"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"calendar-google","name":"Google Calendar","description":"Mirror Google Calendar events into a normalized, searchable corpus (OAuth). Pairs with Gmail for cross-corpus reports.","tags":["calendar","native","oauth"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-07-08T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"calendars":{"type":"array","title":"Calendar IDs (blank = all)","items":{"type":"string"}}}},"secretShape":{"kind":"oauth2","authorizeUrl":"https://accounts.google.com/o/oauth2/v2/auth","tokenUrl":"https://oauth2.googleapis.com/token","scopes":"https://www.googleapis.com/auth/calendar.readonly","clientIdEnv":"GEZEL_GOOGLE_CLIENT_ID","clientSecretEnv":"GEZEL_GOOGLE_CLIENT_SECRET","authParams":{"access_type":"offline","prompt":"consent"},"clientSetup":{"providerLabel":"Google","docsUrl":"https://console.cloud.google.com/apis/credentials","secretRequired":true,"appTypeNote":"Create an OAuth client ID of type Desktop app in Google Cloud Console. Desktop clients are issued a client secret Google does not treat as confidential — enter both here. Any loopback port works for the redirect.","steps":["Sign in at console.cloud.google.com and create (or pick) a project.","Under APIs & Services, open Library and enable the Google Calendar API.","Under APIs & Services, open OAuth consent screen: configure the app as External and add yourself as a test user.","Set the publishing status to In production — apps left in Testing get their sign-ins expired every 7 days. An unverified-app warning on your own app is safe to click through.","Under APIs & Services, open Credentials and choose Create credentials, then OAuth client ID, application type 'Desktop app'.","Copy the Client ID and Client Secret from the created client and paste them below. Desktop clients accept any 127.0.0.1 port, so there is no redirect URI to register."]}},"source":{"adapterId":"calendar-google"},"normalize":{"kind":"native"},"actions":[],"completeness":"mirror","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"instagram-media","name":"Instagram Media","description":"Keep a searchable copy of your Instagram presence: your posts, reels, and carousels with captions, links, like and comment counts and a daily engagement history, plus a daily follower count. Works with Instagram professional (business or creator) accounts through Meta's own sign-in. Your crew can draft posts for review and — once you allow publishing on the connector — publish images with your per-post consent. One honest catch: Instagram's API only accepts images already hosted at a public https URL, so Gezel cannot publish local files directly.","tags":["instagram","social","media","posts","native"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-12T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"downloadMedia":{"type":"boolean","title":"Download media files (reserved — this version stores captions and links only)","default":false},"allowPublish":{"type":"boolean","title":"Allow this project's gezels to queue posts for publishing (each publish still needs your consent)","default":false},"maxMedia":{"type":"integer","title":"Most recent posts to keep synced","default":200,"minimum":1,"maximum":10000},"metricsWindowDays":{"type":"integer","title":"Days of recent posts to keep like/comment counts fresh for","default":30,"minimum":1,"maximum":365},"metricsRefreshHours":{"type":"number","title":"Hours between like/comment-count refreshes","default":12,"minimum":0.25,"maximum":720},"metricsHistory":{"type":"boolean","title":"Record daily engagement history records","default":true},"statsIntervalHours":{"type":"number","title":"Hours between account-stats snapshots","default":6,"minimum":0.25,"maximum":720}}},"secretShape":{"kind":"oauth2","authorizeUrl":"https://www.instagram.com/oauth/authorize","tokenUrl":"https://api.instagram.com/oauth/access_token","scopes":"instagram_business_basic instagram_business_content_publish","clientIdEnv":"GEZEL_INSTAGRAM_CLIENT_ID","clientSecretEnv":"GEZEL_INSTAGRAM_CLIENT_SECRET","longLivedExchange":{"exchangeUrl":"https://graph.instagram.com/access_token","refreshUrl":"https://graph.instagram.com/refresh_access_token"},"clientSetup":{"providerLabel":"Instagram (Meta)","docsUrl":"https://developers.facebook.com/apps/","secretRequired":true,"appTypeNote":"Create a Meta app with Instagram API (Instagram Login) for a business or creator account. The app secret stays on this machine, in your OS keychain.","redirectPort":6242,"redirectNote":"Meta matches redirect URIs exactly and may require an https URI; if it refuses the http form, tell us — https loopback support is planned.","steps":["Sign in at developers.facebook.com, choose My Apps, then Create app (the Business type, or the default when not asked).","On the app dashboard, add the 'Instagram' product and choose 'API setup with Instagram Login'.","Your Instagram account must be Professional (business or creator) — switch it in the Instagram app under Settings if needed, then connect it as the tester account when prompted.","In the product's API setup, open 'Set up Instagram business login' and add the redirect URI shown below under OAuth redirect URIs, exactly.","Copy the Instagram App ID and Instagram App Secret from the same setup page (they also live under App settings, Basic).","Paste both below — the secret stays in your OS keychain. Your own account works while the Meta app stays in development mode."]}},"source":{"adapterId":"instagram-media"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"social-publish"}],"completeness":"window","notes":"Works only with Instagram professional accounts (business or creator) via the Instagram API with Instagram Login — personal accounts cannot link. Requires your own Meta developer app: set GEZEL_INSTAGRAM_CLIENT_ID and GEZEL_INSTAGRAM_CLIENT_SECRET, and note Meta requires app review before accounts other than the app's own testers can link in production. Meta issues no refresh token: linking trades the sign-in for a long-lived (~60 day) token that Gezel refreshes in place whenever a sync runs with under 7 days remaining; if the account sits unsynced past expiry, re-link it. Syncs the account's media (posts, reels, carousels) as caption plus permalink — media files are not downloaded in this version — with like and comment counts on posts from the last metricsWindowDays re-observed roughly every metricsRefreshHours (each observation also appends a per-day metrics-history record per post, the engagement curve over time, unless metricsHistory is turned off), plus a once-per-day account-stats record (followers, following, total posts). The first sync captures up to maxMedia recent posts; older history is not backfilled. Publishing works, with Meta's contract stated honestly: Instagram's publishing API has Meta's servers fetch the image from a public https URL, and Gezel is local-first and does not host your files — so publishing needs an image that is already hosted somewhere public (any CDN or static host will do), caption-only posts do not exist on Instagram, and drafts stay fully usable without any public URL. The caption travels as the draft's post text, so Gezel's draft flow requires one (Instagram itself allows empty captions). Publishing is off by default: a gezel can only draft until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Accounts linked before publishing existed must re-link from the connector's settings to grant the instagram_business_content_publish permission.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"linear-issues","name":"Linear Issues","description":"Mirror recent Linear issues into a searchable corpus via Linear's MCP server. A rolling window of recent activity, not full history.","tags":["issues","mcp"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"mcp","configSchema":{"type":"object","properties":{}},"secretShape":{"kind":"apikey","field":"Authorization","label":"Linear personal API key","required":true,"x-gezel-priority":"primary","description":"Create a personal API key in Linear. Do not enter your Linear account password. Grant read access and limit the key to only the teams you want Gezel to sync.","helpUrl":"https://linear.app/docs/api-and-webhooks","helpLabel":"View Linear API-key instructions"},"setupInstructions":{"title":"Create a Linear personal API key","description":"Gezel uses this key only to read issues through Linear's API. Your normal Linear password does not belong here.","steps":["In Linear, open Settings, then Account, then Security & Access.","Under Personal API keys, create a new key named Gezel.","Grant read permissions and restrict team access to only the teams you want synced.","Copy the generated key and paste it into the Linear personal API key field."],"url":"https://linear.app/docs/api-and-webhooks","urlLabel":"View Linear API-key instructions"},"source":{"server":{"kind":"http","transport":"streamable-http","url":"https://mcp.linear.app/mcp","headersFromSecret":{"Authorization":"Bearer $secret"}},"list":{"tool":"list_issues","cursorArg":"updatedAfter","itemsPath":"$.issues","idPath":"$.id","tsPath":"$.updatedAt"},"fetch":{"tool":"get_issue","idArg":"id"}},"normalize":{"kind":"mapping","map":{"id":"$.id","title":"$.title","group":"$.team.name","timestamp":"$.updatedAt","body":"$.description","frontmatter":{"identifier":"$.identifier","state":"$.state.name","assignee":"$.assignee.name","url":"$.url"}}},"actions":[],"completeness":"window","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"linkedin-posts","name":"LinkedIn Posts","description":"Publish to LinkedIn from Gezel: your crew drafts posts in your voice, and with your permission queues them — every publish still asks for your consent before anything goes out. LinkedIn does not let ordinary apps read your feed (that part is reserved for its partner program), so nothing syncs back: reception and analytics stay on LinkedIn, and this connection is honestly posting-only.","tags":["linkedin","social","posts","native"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-12T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"allowPublish":{"type":"boolean","title":"Allow this project's gezels to queue posts for publishing (each publish still needs your consent)","default":false}}},"secretShape":{"kind":"oauth2","authorizeUrl":"https://www.linkedin.com/oauth/v2/authorization","tokenUrl":"https://www.linkedin.com/oauth/v2/accessToken","scopes":"openid profile w_member_social","clientIdEnv":"GEZEL_LINKEDIN_CLIENT_ID","clientSecretEnv":"GEZEL_LINKEDIN_CLIENT_SECRET","longLived":true,"clientSetup":{"providerLabel":"LinkedIn","docsUrl":"https://www.linkedin.com/developers/apps","secretRequired":true,"appTypeNote":"Create a LinkedIn developer app and request the 'Share on LinkedIn' and 'Sign in with LinkedIn using OpenID Connect' products. LinkedIn requires the client secret; it stays in your OS keychain.","redirectPort":6243,"redirectNote":"LinkedIn matches redirect URLs exactly. Register the URI shown here character-for-character. Tokens last about 60 days; reconnect when prompted.","steps":["Sign in at linkedin.com/developers/apps and create an app — it must be tied to a LinkedIn Page (create a free one if you have none).","On the Products tab, request 'Share on LinkedIn' and 'Sign in with LinkedIn using OpenID Connect'; both are approved instantly.","On the Auth tab, add the redirect URI shown below under 'Authorized redirect URLs for your app', exactly.","Copy the Client ID and Client Secret from the Auth tab.","Paste both below — the secret stays in your OS keychain."]}},"source":{"adapterId":"linkedin-posts"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"social-publish"}],"completeness":"window","notes":"Publishing-only, by LinkedIn's design: reading a member's posts requires LinkedIn's restricted Community Management partner program, which ordinary developer apps cannot join, so nothing syncs back from the feed — no post history, no engagement counts, no mentions. The corpus root holds only what Gezel itself did: the _meta.json marker plus a receipt for every published post under _actions/_sent/; reception and analytics stay on linkedin.com. Publishing works with the standard 'Share on LinkedIn' product: text posts up to 3000 characters, posted to the linked member's own feed with a permalink returned for the record. Publishing is off by default — a gezel can only draft until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Requires your own LinkedIn developer app: set GEZEL_LINKEDIN_CLIENT_ID and GEZEL_LINKEDIN_CLIENT_SECRET, or register the app under Settings — LinkedIn requires the client secret (it has no public-client PKCE support; Gezel still sends PKCE params, which LinkedIn ignores). LinkedIn issues no refresh token to ordinary apps, so the connection lasts about 60 days and then asks to be reconnected.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"mail-microsoft365","name":"Microsoft 365 Mail","description":"Mirror a Microsoft 365 (work/school) mailbox into a normalized, searchable corpus of messages (OAuth).","tags":["email","native","oauth"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-09T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"address":{"type":"string","title":"Email address"},"provider":{"type":"string","const":"microsoft365"},"tenant":{"type":"string","const":"organizations"},"syncFolders":{"type":"array","title":"Folders to sync","items":{"type":"string"}},"allowedRecipients":{"type":"array","title":"Allowed send recipients","items":{"type":"string"}},"allowedDomains":{"type":"array","title":"Allowed send domains","items":{"type":"string"}}},"required":["address"]},"secretShape":{"kind":"oauth2","authorizeUrl":"https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize","tokenUrl":"https://login.microsoftonline.com/organizations/oauth2/v2.0/token","scopes":"offline_access https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Send","clientIdEnv":"GEZEL_MICROSOFT_CLIENT_ID","clientSetup":{"providerLabel":"Microsoft","docsUrl":"https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade","secretRequired":false,"appTypeNote":"Register an app in Microsoft Entra (Azure portal) and add the Mobile and desktop applications platform — a public client needs no client secret. Any loopback port works for the redirect.","steps":["Sign in at portal.azure.com and open Microsoft Entra ID, then App registrations, then New registration.","For supported account types choose 'Accounts in any organizational directory and personal Microsoft accounts'.","Under Authentication, add the 'Mobile and desktop applications' platform and enable the loopback redirect — any 127.0.0.1 port works.","No client secret is needed: this registration is a public client.","Copy the Application (client) ID from the Overview page and paste it below."]}},"source":{"adapterId":"mail-microsoft365"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","notes":"Work/school accounts in the 'organizations' Azure AD tenant. Sending is deny-by-default: only recipients on the binding's allowed recipients/domains lists can be sent to, and night shift defers sends to the outbox for daytime approval.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"mail-outlook","name":"Outlook.com Mail","description":"Mirror a personal Outlook.com mailbox into a normalized, searchable corpus of messages (OAuth).","tags":["email","native","oauth"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-09T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"address":{"type":"string","title":"Email address"},"provider":{"type":"string","const":"outlook"},"tenant":{"type":"string","const":"consumers"},"syncFolders":{"type":"array","title":"Folders to sync","items":{"type":"string"}},"allowedRecipients":{"type":"array","title":"Allowed send recipients","items":{"type":"string"}},"allowedDomains":{"type":"array","title":"Allowed send domains","items":{"type":"string"}}},"required":["address"]},"secretShape":{"kind":"oauth2","authorizeUrl":"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize","tokenUrl":"https://login.microsoftonline.com/consumers/oauth2/v2.0/token","scopes":"offline_access https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Send","clientIdEnv":"GEZEL_MICROSOFT_CLIENT_ID","clientSetup":{"providerLabel":"Microsoft","docsUrl":"https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade","secretRequired":false,"appTypeNote":"Register an app in Microsoft Entra (Azure portal) and add the Mobile and desktop applications platform — a public client needs no client secret. Any loopback port works for the redirect.","steps":["Sign in at portal.azure.com and open Microsoft Entra ID, then App registrations, then New registration.","For supported account types choose 'Accounts in any organizational directory and personal Microsoft accounts'.","Under Authentication, add the 'Mobile and desktop applications' platform and enable the loopback redirect — any 127.0.0.1 port works.","No client secret is needed: this registration is a public client.","Copy the Application (client) ID from the Overview page and paste it below."]}},"source":{"adapterId":"mail-outlook"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","notes":"Personal Outlook.com accounts in the 'consumers' tenant. Sending is deny-by-default: only recipients on the binding's allowed recipients/domains lists can be sent to, and night shift defers sends to the outbox for daytime approval.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"x-posts","name":"X Posts","description":"Keep a searchable copy of your X (Twitter) presence: your posts with their like, repost, and view counts and a daily engagement history, optional mentions, and a daily follower count. Links through X's own sign-in. X charges for API reads, so syncs are spaced out by default. Your crew can draft posts for review and — once you allow publishing on the connector — post them with your per-post consent; posting fits X's free tier.","tags":["x","twitter","social","posts","native"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-12T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"syncMentions":{"type":"boolean","title":"Sync mentions of the account (costs extra paid reads)","default":false},"allowPublish":{"type":"boolean","title":"Allow this project's gezels to queue posts for publishing (each publish still needs your consent)","default":false},"syncIntervalHours":{"type":"number","title":"Hours between post syncs (X reads are paid — keep this generous)","default":4,"minimum":0.25,"maximum":720},"metricsWindowDays":{"type":"integer","title":"Days of recent posts to keep engagement counts fresh for","default":7,"minimum":1,"maximum":365},"metricsRefreshHours":{"type":"number","title":"Hours between engagement-count refreshes","default":24,"minimum":0.25,"maximum":720},"metricsHistory":{"type":"boolean","title":"Record daily engagement history records","default":true},"statsIntervalHours":{"type":"number","title":"Hours between account-stats snapshots","default":6,"minimum":0.25,"maximum":720}}},"secretShape":{"kind":"oauth2","authorizeUrl":"https://x.com/i/oauth2/authorize","tokenUrl":"https://api.x.com/2/oauth2/token","scopes":"tweet.read users.read offline.access tweet.write","clientIdEnv":"GEZEL_X_CLIENT_ID","clientSecretEnv":"GEZEL_X_CLIENT_SECRET","clientSetup":{"providerLabel":"X (Twitter)","docsUrl":"https://developer.x.com/en/portal/dashboard","secretRequired":false,"appTypeNote":"Create a free developer app and set its type to a Native/Public client — PKCE needs no client secret. Your API tier and read limits are your own.","redirectPort":6241,"redirectNote":"X matches callback URLs exactly. Register the URI shown here character-for-character.","steps":["Sign in at developer.x.com and create a project with an app — the free plan works for publishing.","In the app's settings, open 'User authentication settings' and click Set up.","Choose the 'Native App' (public client) type and enable OAuth 2.0.","Paste the redirect URI shown below into Callback URI / Redirect URL, character-for-character.","Fill the required website URL field with any page of yours and save.","Copy the OAuth 2.0 Client ID from the app's Keys and tokens page and paste it below — native apps use PKCE, so there is no secret to copy."]}},"source":{"adapterId":"x-posts"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"social-publish"}],"completeness":"window","notes":"Syncs three things from the linked account: the account's own posts (text with links expanded, language, reply threading, plus like/repost/reply/quote/bookmark/view counts), mentions of the account when enabled, and a once-per-day account-stats record (followers, following, total posts). Be blunt about cost: X charges for API reads on every tier, and the free tier allows roughly 100 reads per month — that fits only the default generous intervals (posts every 4 hours, stats every 6, engagement refresh daily); for real day-to-day use you need the paid Basic tier or higher. Every read scope is interval-gated (syncIntervalHours for posts and mentions, statsIntervalHours for stats) so background syncing can never burn reads faster than configured, and mentions are OFF by default to conserve reads. Engagement counts on posts from the last metricsWindowDays are re-observed roughly every metricsRefreshHours as one extra page per refresh, updating existing files in place; each observation also appends a per-day metrics-history record per post (the engagement curve over time) unless metricsHistory is turned off. The first sync captures up to 500 recent posts; older history is not backfilled. Publishing works even on the free tier — X budgets writes separately at roughly 500 posts per month — and is off by default: a gezel can only draft posts until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Accounts linked before publishing existed must reconnect from the connector's settings to grant the new tweet.write scope. Requires your own X developer app: set GEZEL_X_CLIENT_ID (and GEZEL_X_CLIENT_SECRET for confidential apps) to enable linking.","availableVersions":["1.0.0"]}}]}
|
|
1
|
+
{"schemaVersion":1,"kind":"connector-type","count":17,"entries":[{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"airtable-records","name":"Airtable Records","description":"Mirror an Airtable table into a searchable corpus — one file per record — via the Airtable API, hosted off-platform through a vendored Prismatic component. Captures the table's current rows each sync.","tags":["airtable","records","spectral"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"spectral","configSchema":{"type":"object","properties":{"baseId":{"type":"string","title":"Base ID"},"tableName":{"type":"string","title":"Table name"},"view":{"type":"string","title":"View (optional)"},"filterByFormula":{"type":"string","title":"Filter by formula (optional)"}},"required":["baseId","tableName"]},"secretShape":{"kind":"apikey","field":"apiKey","label":"Airtable personal access token","required":true,"x-gezel-priority":"primary","description":"Use an Airtable personal access token, not a legacy API key. Give it read access to records and access only to the base you want Gezel to sync.","helpUrl":"https://airtable.com/create/tokens","helpLabel":"Open Airtable token settings"},"setupInstructions":{"title":"Create an Airtable personal access token","description":"Airtable retired legacy API keys. Create a separate, narrowly scoped token for Gezel instead of sharing your Airtable account password.","steps":["Open Airtable's token settings using the link below and choose Create token.","Name the token Gezel and add the data.records:read scope.","Under Access, add only the base or workspace that contains the table you want to sync.","Create the token, copy it once, and paste it into the Airtable personal access token field."],"url":"https://airtable.com/create/tokens","urlLabel":"Open Airtable token settings"},"source":{"component":"airtable","action":"listRecords","connectionKey":"personalAccessToken","connectionInput":"airtableConnection","inputs":{"baseId":"$config.baseId","tableName":"$config.tableName","view":"$config.view","filterByFormula":"$config.filterByFormula"},"itemsPath":"$.data"},"normalize":{"kind":"mapping","map":{"id":"$.id","title":"$.fields.Name","timestamp":"$.createdTime","body":"$.fields","frontmatter":{"recordId":"$.id","createdTime":"$.createdTime"}}},"actions":[],"completeness":"mirror","notes":"Personal Access Token connection. Each sync re-fetches every row in the table (no incremental cursor); the writer is content-hash idempotent, so re-syncs are cheap and only changed rows rewrite. Deleted Airtable rows are not pruned from the corpus. `title` maps to the `Name` field (Airtable's usual primary column) and falls back to the record id when absent.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"azure-monitor-logs","name":"Azure Monitor Logs","description":"Mirror a Log Analytics workspace table — request logs, diagnostics, app telemetry — into a queryable data table. Azure reports the column types, so your gezels get a real schema and can answer questions with SQL instead of reading files.","tags":["azure","logs","telemetry","data","table","observations"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-29T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"workspaceId":{"type":"string","title":"Log Analytics workspace ID"},"kqlTable":{"type":"string","title":"Table to mirror (e.g. AppRequests)"},"filter":{"type":"string","title":"Extra KQL filter (optional)"},"table":{"type":"string","title":"Name for the table in Gezel (optional)"},"timeColumn":{"type":"string","title":"Timestamp column"},"backfillDays":{"type":"integer","title":"Days of history on the first sync","minimum":1,"maximum":365},"pageRows":{"type":"integer","title":"Rows per sync","minimum":100,"maximum":50000},"apiBaseUrl":{"type":"string","title":"API base URL (sovereign clouds only)"}},"required":["workspaceId","kqlTable"]},"secretShape":{"kind":"apikey","field":"token","label":"Azure access token","required":true,"description":"An Azure AD access token for https://api.loganalytics.io with Log Analytics Reader on the workspace. Stored in your keychain; it never reaches the model.","helpUrl":"https://learn.microsoft.com/azure/azure-monitor/logs/api/overview","helpLabel":"Azure Monitor Logs API docs"},"setupInstructions":{"title":"Connect a Log Analytics workspace","description":"Gezel reads one table from your workspace on a schedule and stores it locally as a data table. Nothing is written back to Azure, and the query only ever reads.","steps":["In the Azure portal, open your Log Analytics workspace and copy its Workspace ID from the Overview page.","Name the table you want mirrored — AppRequests, AzureDiagnostics, and AppExceptions are common starting points.","Give Gezel an access token for the Log Analytics API with the Log Analytics Reader role. Read access is all it needs.","Optionally add a KQL filter to narrow what is mirrored, and set how many days of history the first sync should reach back."]},"source":{"adapterId":"azure-monitor-logs"},"normalize":{"kind":"observations"},"actions":[],"completeness":"window","notes":"An observation corpus: rows land as partitioned columnar files and gezels read them through list_tables/describe_table/query_table rather than as files. Log Analytics has no continuation token, so syncing pages over time — each pass reads rows strictly newer than the last one written, oldest-first, up to the row budget. The watermark advances only after a clean write, so a failed pass re-reads its window instead of skipping it. Because the comparison is strict, a row sharing a timestamp to the tick with the previous page's last row can be missed; the alternative re-delivers that row on every pass forever. Azure reports each column's type, so the table's schema comes from the source rather than being inferred from a sample.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"bluesky-posts","name":"Bluesky Posts","description":"Keep a searchable copy of your Bluesky presence: your posts with their likes and reposts, mentions and replies from others, and a daily follower count. Sign in with an app password from Bluesky's settings — never your real password. Your crew can also draft posts for you, and with your permission publish them; every publish still asks for your consent.","tags":["bluesky","social","posts","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"handle":{"type":"string","title":"Bluesky handle","description":"Your full handle, such as yourname.bsky.social.","x-gezel-priority":"primary"},"service":{"type":"string","title":"Service URL","default":"https://bsky.social"},"syncMentions":{"type":"boolean","title":"Sync mentions, replies, and quotes","default":true},"allowPublish":{"type":"boolean","title":"Allow this project's gezels to queue posts for publishing (each publish still needs your consent)","default":false},"metricsWindowDays":{"type":"integer","title":"Days of recent posts to keep engagement counts fresh for","default":30,"minimum":1,"maximum":365},"metricsRefreshHours":{"type":"number","title":"Hours between engagement-count refreshes","default":6},"statsIntervalHours":{"type":"number","title":"Hours between account-stats snapshots","default":6},"metricsHistory":{"type":"boolean","title":"Record daily engagement history records","description":"One per post per day while a post stays inside the metrics window","default":true}},"required":["handle"]},"secretShape":{"kind":"apikey","field":"appPassword","label":"Bluesky app password","required":true,"x-gezel-priority":"primary","description":"Do not enter your normal Bluesky account password. Create a separate app password in Bluesky, then paste the generated app password here.","helpUrl":"https://bsky.app/settings/app-passwords","helpLabel":"Open Bluesky app-password settings"},"setupInstructions":{"title":"Create a Bluesky app password","description":"Create this password inside Bluesky. If Bluesky asks you to sign in, enter your normal password only on Bluesky's own site — never in Gezel.","steps":["Open Bluesky's App Passwords settings using the link below and sign in to Bluesky if asked.","Choose Add App Password and give it a name such as Gezel.","Copy the generated app password, return to Gezel, and paste it into the Bluesky app password field."],"url":"https://bsky.app/settings/app-passwords","urlLabel":"Open Bluesky app-password settings"},"source":{"adapterId":"bluesky-posts"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"social-publish"}],"completeness":"window","notes":"Syncs three things from the signed-in account: the account's own posts (text, language, reply threading, link and image-description context, plus like/repost/reply/quote counts), mentions of the account (mention, reply, and quote notifications), and a once-per-day account-stats record (followers, following, total posts) refreshed at the configured interval. Engagement counts on posts from the last metricsWindowDays are re-observed roughly every metricsRefreshHours, updating the existing files in place; unless metricsHistory is turned off, each observation also writes a per-post per-day engagement history record under the posts corpus's metrics-history/ bucket, so engagement curves stay reconstructable after counts move on. The first sync captures up to 500 recent posts; older history is not backfilled. Reposts are skipped in this version, and synced images are represented by their alt text rather than downloaded. Publishing is off by default: a gezel can only draft posts until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Published posts can embed up to 4 images (1,000,000 bytes each at most; png/jpg/jpeg/webp/gif), read from project-relative paths resolved against the project's artifacts directory first, then its workspace.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"mail-imap","name":"Email (IMAP)","description":"Mirror an IMAP mailbox into a normalized, searchable corpus of messages.","tags":["email","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"address":{"type":"string","title":"Email address"},"provider":{"type":"string","const":"imap"},"syncFolders":{"type":"array","title":"Folders to sync","items":{"type":"string"}},"allowedRecipients":{"type":"array","title":"Allowed send recipients","items":{"type":"string"}},"allowedDomains":{"type":"array","title":"Allowed send domains","items":{"type":"string"}}},"required":["address"]},"secretShape":{"kind":"imap","description":"Use an app-specific password when your mail provider offers one. Do not enter your normal mail password unless your provider explicitly says it supports password-based IMAP access."},"setupInstructions":{"title":"Check which password your mail provider requires","description":"For Gmail, Outlook.com, or Microsoft 365, prefer Gezel's dedicated connector so you can authorize through the provider's website. Use generic IMAP for other providers or when your provider specifically supports an app password.","steps":["Gmail: use the Gmail connector when possible. Otherwise, with 2-Step Verification enabled, create a 16-digit App Password in your Google Account security settings.","iCloud Mail: at account.apple.com, open Sign-In and Security, then App-Specific Passwords, and generate one for Gezel.","Yahoo Mail: open Yahoo Account Security, then External connections, and create an app password for Gezel.","Outlook.com or Microsoft 365: use Gezel's Outlook.com or Microsoft 365 connector instead of generic IMAP whenever possible.","For another provider, check its IMAP documentation before entering a password. Use a dedicated app password if it offers one."]},"source":{"adapterId":"mail-imap"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"github-issues","name":"GitHub Issues","description":"Mirror recent issues from one GitHub repository into searchable project files. Public repositories work without sign-in; add a token for private repositories or higher rate limits.","tags":["github","issues","script"],"maintainer":{"name":"Gezel"},"version":"1.0.2","releasedAt":"2026-08-13T00:00:00Z","driver":"script","configSchema":{"type":"object","properties":{"owner":{"type":"string","title":"Repository owner"},"repository":{"type":"string","title":"Repository name"},"apiBaseUrl":{"type":"string","title":"API base URL (GitHub Enterprise only)"}},"required":["owner","repository"]},"secretShape":{"kind":"apikey","field":"token","label":"GitHub personal access token","required":false,"description":"Optional. Leave this blank for public repositories. For a private repository or higher API limits, create a fine-grained personal access token—never enter your GitHub account password—and grant read-only Issues access to only this repository.","helpUrl":"https://github.com/settings/personal-access-tokens/new","helpLabel":"Create a fine-grained GitHub token"},"setupInstructions":{"title":"A GitHub token is optional","description":"Public repositories work without a token. Add a fine-grained personal access token only for a private repository or higher API rate limits.","steps":["Leave the token field blank if the repository is public and the anonymous API limit is sufficient.","Otherwise, open GitHub's fine-grained token form using the link below and choose the repository owner.","Limit repository access to the repository you want to sync and grant Repository permissions → Issues: Read-only.","Set an expiration, generate the token, and paste it here. Treat the token like a password; never paste your GitHub account password into Gezel."],"url":"https://github.com/settings/personal-access-tokens/new","urlLabel":"Create a fine-grained GitHub token"},"source":{"inlineFetch":"import { defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'github-issues-fetch',\n description: 'Fetches a bounded, incremental window of GitHub repository issues.',\n inputs: {\n cursor: { type: 'json', description: 'Last successfully synced GitHub updated_at value.' },\n config: { type: 'json', description: 'Repository owner, name, and optional API base URL.', required: true },\n },\n outputs: {\n records: { type: 'array', description: 'GitHub issues ready for connector normalization.', itemType: 'object' },\n cursor: { type: 'string', description: 'Newest updated_at value processed.', nullable: true },\n rateLimited: { type: 'boolean', description: 'True when GitHub throttled the pass; the engine backs off and retries from this cursor.', nullable: true },\n partial: { type: 'boolean', description: 'True when more pages remained after the per-pass page budget.', nullable: true },\n },\n requires: ['network', 'credential:$credential'],\n} as const);\n\ntype Config = { owner?: unknown; repository?: unknown; apiBaseUrl?: unknown };\ntype Issue = Record<string, unknown> & { updated_at?: unknown; pull_request?: unknown };\nconst input = gezel.input as { cursor?: unknown; config?: Config };\nconst config = input.config ?? {};\nconst owner = typeof config.owner === 'string' ? config.owner.trim() : '';\nconst repository = typeof config.repository === 'string' ? config.repository.trim() : '';\nif (!owner || !repository) throw new Error('GitHub owner and repository are required.');\n\nconst apiBaseUrl =\n typeof config.apiBaseUrl === 'string' && config.apiBaseUrl.trim()\n ? config.apiBaseUrl.trim().replace(/\\/+$/, '')\n : 'https://api.github.com';\nlet apiOrigin: string;\ntry {\n const parsed = new URL(apiBaseUrl);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') throw new Error('bad protocol');\n apiOrigin = parsed.origin;\n} catch {\n throw new Error('GitHub API base URL must be an absolute HTTP or HTTPS URL.');\n}\n\nconst params = new URLSearchParams({\n state: 'all',\n sort: 'updated',\n direction: typeof input.cursor === 'string' ? 'asc' : 'desc',\n per_page: '100',\n});\nif (typeof input.cursor === 'string' && input.cursor) params.set('since', input.cursor);\nlet nextUrl =\n apiBaseUrl + '/repos/' + encodeURIComponent(owner) + '/' + encodeURIComponent(repository) + '/issues?' + params;\nlet nextCursor = typeof input.cursor === 'string' ? input.cursor : null;\nconst records: Issue[] = [];\nlet rateLimited = false;\n\nfunction nextPage(link: string | undefined): string | null {\n if (!link) return null;\n for (const part of link.split(',')) {\n const match = part.match(/<([^>]+)>;\\s*rel=\"next\"/);\n if (match?.[1]) return match[1];\n }\n return null;\n}\n\nfunction isThrottle(status: number, headers: Record<string, string | undefined>): boolean {\n if (status === 429) return true;\n return status === 403 && (headers['x-ratelimit-remaining'] === '0' || Boolean(headers['retry-after']));\n}\n\nfor (let page = 0; page < 5 && nextUrl; page++) {\n const target = new URL(nextUrl);\n if (target.origin !== apiOrigin) throw new Error('GitHub pagination changed origin; refusing to forward the token.');\n const headers = {\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'User-Agent': 'Gezel-GitHub-Issues-Connector',\n };\n let response;\n try {\n response = await gezel.http.authed(target.toString(), {\n credential: '$credential',\n headers,\n });\n } catch (error) {\n if ((error as { code?: string }).code !== 'CREDENTIAL_MISSING') throw error;\n response = await gezel.http.request(target.toString(), { headers });\n }\n if (isThrottle(response.status, response.headers)) {\n // Emit what this pass already collected with the cursor those records\n // reached, and flag the throttle instead of throwing: a throw voids the\n // batch and re-fetches the same window next tick, which is exactly how\n // rate-limited APIs get hammered. The engine persists the cursor only\n // after a clean write and applies its jittered backoff to the binding.\n rateLimited = true;\n break;\n }\n if (!response.ok) {\n throw new Error('GitHub issues request failed (' + response.status + '): ' + response.body.slice(0, 240));\n }\n const pageItems = JSON.parse(response.body) as unknown;\n if (!Array.isArray(pageItems)) throw new Error('GitHub returned a non-array issues response.');\n for (const raw of pageItems) {\n if (!raw || typeof raw !== 'object') continue;\n const issue = raw as Issue;\n const updatedAt = typeof issue.updated_at === 'string' ? issue.updated_at : '';\n if (updatedAt && (!nextCursor || updatedAt > nextCursor)) nextCursor = updatedAt;\n if (issue.pull_request) continue;\n const labels = Array.isArray(issue.labels)\n ? issue.labels\n .map((label) =>\n typeof label === 'string'\n ? label\n : label && typeof label === 'object'\n ? String((label as Record<string, unknown>).name ?? '')\n : '',\n )\n .filter(Boolean)\n : [];\n const assignees = Array.isArray(issue.assignees)\n ? issue.assignees\n .map((person) =>\n person && typeof person === 'object'\n ? String((person as Record<string, unknown>).login ?? '')\n : '',\n )\n .filter(Boolean)\n : [];\n records.push({\n ...issue,\n gezel_repository: owner + '/' + repository,\n gezel_labels: labels,\n gezel_assignees: assignees,\n });\n }\n const candidate = nextPage(response.headers.link);\n nextUrl = candidate && new URL(candidate).origin === apiOrigin ? candidate : '';\n}\n\nconst cursorOut = records.length > 0 ? nextCursor : typeof input.cursor === 'string' ? input.cursor : null;\ngezel.output({\n records,\n cursor: rateLimited ? cursorOut : nextCursor,\n ...(rateLimited ? { rateLimited: true } : {}),\n ...(nextUrl && !rateLimited ? { partial: true } : {}),\n});\n","idPath":"$.id"},"normalize":{"kind":"mapping","map":{"id":"$.id","title":"$.title","group":"$.gezel_repository","timestamp":"$.updated_at","body":"$.body","frontmatter":{"number":"$.number","state":"$.state","stateReason":"$.state_reason","author":"$.user.login","assignees":"$.gezel_assignees","labels":"$.gezel_labels","milestone":"$.milestone.title","comments":"$.comments","url":"$.html_url","createdAt":"$.created_at","closedAt":"$.closed_at","locked":"$.locked"}}},"actions":[],"completeness":"window","notes":"Uses GitHub's REST list-repository-issues endpoint anonymously for public repositories, or with an optional personal access token for private repositories and higher rate limits. Pull requests are excluded because GitHub's Issues API returns both. The first sync captures up to 500 recently updated issues; later syncs resume from updated_at in oldest-first order so busy repositories catch up over multiple passes. Issue comments are represented by their count but comment bodies are not mirrored in this version. Since 1.0.1 the fetch reports GitHub throttling (HTTP 429, or 403 with an exhausted rate-limit budget) as a rateLimited batch instead of an error, so the sync engine backs off and resumes from the last clean cursor, and declares its credential destination origins for the daemon-side allowlist.","availableVersions":["1.0.2","1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"github-pulls","name":"GitHub Pull Requests","description":"Mirror a project's open pull requests into local files: a summary, one file per changed file with its patch, and the complete unified diff. Lets a gezel review a PR by reading the workspace instead of calling the GitHub API mid-turn.","tags":["github","pull-requests","code-review","diff","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"maxPulls":{"type":"integer","title":"Open pull requests to mirror","minimum":1,"maximum":200,"default":20},"includeComments":{"type":"boolean","title":"Include review and issue comments","default":true}}},"secretShape":{"kind":"apikey","field":"token","label":"GitHub personal access token","required":false,"description":"Optional. Leave this blank to use Gezel's existing GitHub sign-in. If you need a separate credential, create a fine-grained personal access token—never enter your GitHub account password—and restrict it to this repository.","helpUrl":"https://github.com/settings/personal-access-tokens/new","helpLabel":"Create a fine-grained GitHub token"},"setupInstructions":{"title":"A separate GitHub token is usually unnecessary","description":"Gezel uses your existing GitHub sign-in when available. Add a connector-specific token only when that sign-in cannot read the repository.","steps":["Try leaving the token field blank first, especially for a public repository.","If a token is needed, open GitHub's fine-grained token form and limit repository access to this project.","Grant Repository permissions → Pull requests: Read-only. If comment syncing is enabled, also grant Issues: Read-only.","Set an expiration, generate the token, and paste it here. Treat the token like a password; never paste your GitHub account password into Gezel."],"url":"https://github.com/settings/personal-access-tokens/new","urlLabel":"Create a fine-grained GitHub token"},"source":{"adapterId":"github-pulls"},"normalize":{"kind":"native"},"actions":[],"completeness":"window","notes":"Takes the repository from the project's own GitHub link rather than binding config, so the coordinates cannot drift from the project. The corpus lives in the project's managed artifacts tree, so mirrored data never dirties the user's checkout. Each open pull request becomes one scope (`pr-<number>`) holding an overview record — title, author, branches, counts, description, and comment digest — plus one record per changed file under `files/` carrying that file's patch, and the complete unified diff as an attachment. Patches and the diff are stored untruncated because a gezel opens these as files. Comment and description text is untrusted and passes through the standard content scan and quarantine. Completeness is `window`: only the newest open pull requests are mirrored, so absence from the corpus does not mean absence upstream. A task launch can target one pull request directly, which syncs just that scope.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"github-releases","name":"GitHub Releases","description":"Mirror GitHub release notes and uploaded release assets into local project files. Public repositories work anonymously; Gezel's GitHub sign-in or an optional token also enables private repositories and draft releases.","tags":["github","releases","artifacts","binaries","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"owner":{"type":"string","title":"Repository owner"},"repository":{"type":"string","title":"Repository name"},"maxReleases":{"type":"integer","title":"Latest releases (blank = all)","minimum":1,"maximum":10000},"includeDrafts":{"type":"boolean","title":"Include draft releases when signed in","default":true},"includePrereleases":{"type":"boolean","title":"Include prereleases","default":true},"downloadAssets":{"type":"boolean","title":"Download uploaded release assets","default":true},"assetNamePattern":{"type":"string","title":"Asset filename globs (optional, comma-separated)"},"maxAssetSizeMb":{"type":"number","title":"Maximum asset size in MB (blank = unlimited)","exclusiveMinimum":0},"apiBaseUrl":{"type":"string","title":"GitHub Enterprise API base URL (optional)"}},"required":["owner","repository"]},"secretShape":{"kind":"apikey","field":"token","label":"GitHub personal access token","required":false,"description":"Optional. Public releases work without a token, and Gezel can use your existing GitHub sign-in. For private repositories or draft releases, use a fine-grained personal access token—never your GitHub account password—restricted to this repository.","helpUrl":"https://github.com/settings/personal-access-tokens/new","helpLabel":"Create a fine-grained GitHub token"},"setupInstructions":{"title":"A GitHub token is optional","description":"Public releases work anonymously. Gezel also uses your existing GitHub sign-in when available, so add a separate token only when the repository or draft releases are not otherwise accessible.","steps":["Try leaving the token field blank first.","If a token is needed, open GitHub's fine-grained token form and limit repository access to the repository whose releases you want to sync.","Grant Repository permissions → Contents: Read-only. Access to draft releases also depends on your GitHub account having access to them.","Set an expiration, generate the token, and paste it here. Treat the token like a password; never paste your GitHub account password into Gezel."],"url":"https://github.com/settings/personal-access-tokens/new","urlLabel":"Create a fine-grained GitHub token"},"source":{"adapterId":"github-releases"},"normalize":{"kind":"native"},"actions":[],"completeness":"mirror","notes":"Lists releases through GitHub's REST API and stores one release-notes record per release. Uploaded assets are streamed to disk, checked against GitHub's size and sha256 digest when supplied, and published as file-backed connector attachments without buffering the entire asset in memory. Public repositories work anonymously. For github.com, a per-binding token wins, then Gezel's stored GitHub sign-in, GH_TOKEN/GITHUB_TOKEN, or a signed-in GitHub CLI. Users with push access receive draft releases from GitHub's list-releases endpoint. Optional filters bound the newest release count, prerelease/draft inclusion, asset filename globs, and per-asset size. GitHub-generated source archives are not release assets and are not downloaded.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"github-wiki","name":"GitHub Wiki","description":"Mirror a repository's wiki pages into searchable project files. Public wikis work anonymously; add a token for private repositories.","tags":["github","wiki","documentation","native"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"owner":{"type":"string","title":"Repository owner"},"repository":{"type":"string","title":"Repository name"},"host":{"type":"string","title":"GitHub Enterprise host (optional)"}},"required":["owner","repository"]},"secretShape":{"kind":"apikey","field":"token","label":"GitHub personal access token","required":false,"description":"Optional. Leave this blank for a public wiki. For a private repository wiki, create a fine-grained personal access token—never enter your GitHub account password—and grant read-only Contents access to only this repository.","helpUrl":"https://github.com/settings/personal-access-tokens/new","helpLabel":"Create a fine-grained GitHub token"},"setupInstructions":{"title":"A GitHub token is optional","description":"Public wikis clone without a token. Add a fine-grained personal access token only for a private repository wiki.","steps":["Leave the token field blank if the wiki belongs to a public repository.","For a private wiki, open GitHub's fine-grained token form and limit repository access to the repository whose wiki you want to sync.","Grant Repository permissions → Contents: Read-only.","Set an expiration, generate the token, and paste it here. Treat the token like a password; never paste your GitHub account password into Gezel."],"url":"https://github.com/settings/personal-access-tokens/new","urlLabel":"Create a fine-grained GitHub token"},"source":{"adapterId":"github-wiki"},"normalize":{"kind":"native"},"actions":[],"completeness":"mirror","notes":"Uses GitHub's separate .wiki.git repository as a read-only transport. Public wikis clone anonymously. An optional personal access token enables private repository wikis. Supported textual wiki markup is mirrored and scanned as untrusted external content; binary files and Git history are not exposed. Changed pages are stored as immutable revisions. Deletions are not currently pruned from the local corpus.","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"mail-gmail","name":"Gmail","description":"Mirror a Gmail mailbox into a normalized, searchable corpus of messages (OAuth).","tags":["email","native","oauth"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-07-08T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"address":{"type":"string","title":"Email address"},"provider":{"type":"string","const":"gmail"},"syncFolders":{"type":"array","title":"Labels to sync","items":{"type":"string"}},"allowedRecipients":{"type":"array","title":"Allowed send recipients","items":{"type":"string"}},"allowedDomains":{"type":"array","title":"Allowed send domains","items":{"type":"string"}}},"required":["address"]},"secretShape":{"kind":"oauth2","authorizeUrl":"https://accounts.google.com/o/oauth2/v2/auth","tokenUrl":"https://oauth2.googleapis.com/token","scopes":"https://mail.google.com/","clientIdEnv":"GEZEL_GOOGLE_CLIENT_ID","clientSecretEnv":"GEZEL_GOOGLE_CLIENT_SECRET","authParams":{"access_type":"offline","prompt":"consent"},"clientSetup":{"providerLabel":"Google","docsUrl":"https://console.cloud.google.com/apis/credentials","secretRequired":true,"appTypeNote":"Create an OAuth client ID of type Desktop app in Google Cloud Console. Desktop clients are issued a client secret Google does not treat as confidential — enter both here. Any loopback port works for the redirect.","steps":["Sign in at console.cloud.google.com and create (or pick) a project.","Under APIs & Services, open Library and enable the Gmail API.","Under APIs & Services, open OAuth consent screen: configure the app as External and add yourself as a test user.","Set the publishing status to In production — apps left in Testing get their sign-ins expired every 7 days. An unverified-app warning on your own app is safe to click through.","Under APIs & Services, open Credentials and choose Create credentials, then OAuth client ID, application type 'Desktop app'.","Copy the Client ID and Client Secret from the created client and paste them below. Desktop clients accept any 127.0.0.1 port, so there is no redirect URI to register."]}},"source":{"adapterId":"mail-gmail"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"calendar-google","name":"Google Calendar","description":"Mirror Google Calendar events into a normalized, searchable corpus (OAuth). Pairs with Gmail for cross-corpus reports.","tags":["calendar","native","oauth"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-07-08T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"calendars":{"type":"array","title":"Calendar IDs (blank = all)","items":{"type":"string"}}}},"secretShape":{"kind":"oauth2","authorizeUrl":"https://accounts.google.com/o/oauth2/v2/auth","tokenUrl":"https://oauth2.googleapis.com/token","scopes":"https://www.googleapis.com/auth/calendar.readonly","clientIdEnv":"GEZEL_GOOGLE_CLIENT_ID","clientSecretEnv":"GEZEL_GOOGLE_CLIENT_SECRET","authParams":{"access_type":"offline","prompt":"consent"},"clientSetup":{"providerLabel":"Google","docsUrl":"https://console.cloud.google.com/apis/credentials","secretRequired":true,"appTypeNote":"Create an OAuth client ID of type Desktop app in Google Cloud Console. Desktop clients are issued a client secret Google does not treat as confidential — enter both here. Any loopback port works for the redirect.","steps":["Sign in at console.cloud.google.com and create (or pick) a project.","Under APIs & Services, open Library and enable the Google Calendar API.","Under APIs & Services, open OAuth consent screen: configure the app as External and add yourself as a test user.","Set the publishing status to In production — apps left in Testing get their sign-ins expired every 7 days. An unverified-app warning on your own app is safe to click through.","Under APIs & Services, open Credentials and choose Create credentials, then OAuth client ID, application type 'Desktop app'.","Copy the Client ID and Client Secret from the created client and paste them below. Desktop clients accept any 127.0.0.1 port, so there is no redirect URI to register."]}},"source":{"adapterId":"calendar-google"},"normalize":{"kind":"native"},"actions":[],"completeness":"mirror","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"instagram-media","name":"Instagram Media","description":"Keep a searchable copy of your Instagram presence: your posts, reels, and carousels with captions, links, like and comment counts and a daily engagement history, plus a daily follower count. Works with Instagram professional (business or creator) accounts through Meta's own sign-in. Your crew can draft posts for review and — once you allow publishing on the connector — publish images with your per-post consent. One honest catch: Instagram's API only accepts images already hosted at a public https URL, so Gezel cannot publish local files directly.","tags":["instagram","social","media","posts","native"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-12T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"downloadMedia":{"type":"boolean","title":"Download media files (reserved — this version stores captions and links only)","default":false},"allowPublish":{"type":"boolean","title":"Allow this project's gezels to queue posts for publishing (each publish still needs your consent)","default":false},"maxMedia":{"type":"integer","title":"Most recent posts to keep synced","default":200,"minimum":1,"maximum":10000},"metricsWindowDays":{"type":"integer","title":"Days of recent posts to keep like/comment counts fresh for","default":30,"minimum":1,"maximum":365},"metricsRefreshHours":{"type":"number","title":"Hours between like/comment-count refreshes","default":12,"minimum":0.25,"maximum":720},"metricsHistory":{"type":"boolean","title":"Record daily engagement history records","default":true},"statsIntervalHours":{"type":"number","title":"Hours between account-stats snapshots","default":6,"minimum":0.25,"maximum":720}}},"secretShape":{"kind":"oauth2","authorizeUrl":"https://www.instagram.com/oauth/authorize","tokenUrl":"https://api.instagram.com/oauth/access_token","scopes":"instagram_business_basic instagram_business_content_publish","clientIdEnv":"GEZEL_INSTAGRAM_CLIENT_ID","clientSecretEnv":"GEZEL_INSTAGRAM_CLIENT_SECRET","longLivedExchange":{"exchangeUrl":"https://graph.instagram.com/access_token","refreshUrl":"https://graph.instagram.com/refresh_access_token"},"clientSetup":{"providerLabel":"Instagram (Meta)","docsUrl":"https://developers.facebook.com/apps/","secretRequired":true,"appTypeNote":"Create a Meta app with Instagram API (Instagram Login) for a business or creator account. The app secret stays on this machine, in your OS keychain.","redirectPort":6242,"redirectNote":"Meta matches redirect URIs exactly and may require an https URI; if it refuses the http form, tell us — https loopback support is planned.","steps":["Sign in at developers.facebook.com, choose My Apps, then Create app (the Business type, or the default when not asked).","On the app dashboard, add the 'Instagram' product and choose 'API setup with Instagram Login'.","Your Instagram account must be Professional (business or creator) — switch it in the Instagram app under Settings if needed, then connect it as the tester account when prompted.","In the product's API setup, open 'Set up Instagram business login' and add the redirect URI shown below under OAuth redirect URIs, exactly.","Copy the Instagram App ID and Instagram App Secret from the same setup page (they also live under App settings, Basic).","Paste both below — the secret stays in your OS keychain. Your own account works while the Meta app stays in development mode."]}},"source":{"adapterId":"instagram-media"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"social-publish"}],"completeness":"window","notes":"Works only with Instagram professional accounts (business or creator) via the Instagram API with Instagram Login — personal accounts cannot link. Requires your own Meta developer app: set GEZEL_INSTAGRAM_CLIENT_ID and GEZEL_INSTAGRAM_CLIENT_SECRET, and note Meta requires app review before accounts other than the app's own testers can link in production. Meta issues no refresh token: linking trades the sign-in for a long-lived (~60 day) token that Gezel refreshes in place whenever a sync runs with under 7 days remaining; if the account sits unsynced past expiry, re-link it. Syncs the account's media (posts, reels, carousels) as caption plus permalink — media files are not downloaded in this version — with like and comment counts on posts from the last metricsWindowDays re-observed roughly every metricsRefreshHours (each observation also appends a per-day metrics-history record per post, the engagement curve over time, unless metricsHistory is turned off), plus a once-per-day account-stats record (followers, following, total posts). The first sync captures up to maxMedia recent posts; older history is not backfilled. Publishing works, with Meta's contract stated honestly: Instagram's publishing API has Meta's servers fetch the image from a public https URL, and Gezel is local-first and does not host your files — so publishing needs an image that is already hosted somewhere public (any CDN or static host will do), caption-only posts do not exist on Instagram, and drafts stay fully usable without any public URL. The caption travels as the draft's post text, so Gezel's draft flow requires one (Instagram itself allows empty captions). Publishing is off by default: a gezel can only draft until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Accounts linked before publishing existed must re-link from the connector's settings to grant the instagram_business_content_publish permission.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"http-json-rows","name":"JSON Rows over HTTP","description":"Mirror rows from any JSON or NDJSON HTTP endpoint into a queryable data table. For high-volume sources — request logs, exports, metrics — that are far too large to read as files. Gezels query the result with SQL instead of reading it.","tags":["data","table","json","http","script","observations"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-29T00:00:00Z","driver":"script","configSchema":{"type":"object","properties":{"url":{"type":"string","title":"Endpoint URL"},"itemsPath":{"type":"string","title":"Path to the rows in the response (e.g. data.results)"},"format":{"type":"string","title":"Response format","enum":["json","ndjson"]},"timeField":{"type":"string","title":"Field holding each row's timestamp"},"sinceParam":{"type":"string","title":"Query parameter for incremental fetches"},"nextPagePath":{"type":"string","title":"Path to the next-page URL in the response"},"maxPages":{"type":"integer","title":"Pages per sync","minimum":1,"maximum":20}},"required":["url"]},"secretShape":{"kind":"apikey","field":"token","label":"API token","required":false,"description":"Sent as a bearer token. Leave blank for an endpoint that needs no authentication."},"setupInstructions":{"title":"Point Gezel at a JSON endpoint","description":"Any endpoint that returns a list of rows works — an export URL, a query API, an NDJSON log feed. Gezel stores the rows as a data table and your gezels query it with SQL instead of reading files.","steps":["Paste the endpoint URL. If it needs a token, add one below; it is stored in your keychain and never reaches the model.","If the rows are nested in the response (for example under `data.results`), set the item path to where they live.","Set the timestamp field if the rows carry one. Gezel partitions by day on that field, which is what keeps queries fast as the table grows.","For an endpoint that supports incremental reads, name its since-parameter so each sync only fetches what is new."]},"source":{"inlineFetch":"import { defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'http-json-rows-fetch',\n description: 'Fetches a bounded page of rows from a JSON or NDJSON HTTP endpoint.',\n inputs: {\n cursor: { type: 'json', description: 'Opaque continuation value from the previous pass.' },\n config: { type: 'json', description: 'Endpoint URL, item path, and paging fields.', required: true },\n },\n outputs: {\n records: { type: 'array', description: 'Rows for the observation table.', itemType: 'object' },\n cursor: { type: 'string', description: 'Continuation value for the next pass.', nullable: true },\n rateLimited: { type: 'boolean', description: 'True when the source throttled the pass.', nullable: true },\n partial: { type: 'boolean', description: 'True when more pages remained after the page budget.', nullable: true },\n },\n requires: ['network', 'credential:$credential'],\n} as const);\n\ntype Config = {\n url?: unknown;\n itemsPath?: unknown;\n format?: unknown;\n timeField?: unknown;\n sinceParam?: unknown;\n pageParam?: unknown;\n nextPagePath?: unknown;\n maxPages?: unknown;\n};\n\nconst input = gezel.input as { cursor?: unknown; config?: Config };\nconst config = input.config ?? {};\n\nconst rawUrl = typeof config.url === 'string' ? config.url.trim() : '';\nif (!rawUrl) throw new Error('An endpoint URL is required.');\nlet origin: string;\ntry {\n const parsed = new URL(rawUrl);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') throw new Error('bad protocol');\n origin = parsed.origin;\n} catch {\n throw new Error('The endpoint URL must be an absolute HTTP or HTTPS URL.');\n}\n\nconst itemsPath = typeof config.itemsPath === 'string' ? config.itemsPath.trim() : '';\nconst format = config.format === 'ndjson' ? 'ndjson' : 'json';\nconst timeField = typeof config.timeField === 'string' ? config.timeField.trim() : '';\nconst sinceParam = typeof config.sinceParam === 'string' ? config.sinceParam.trim() : '';\nconst nextPagePath = typeof config.nextPagePath === 'string' ? config.nextPagePath.trim() : '';\nconst maxPages = Number.isFinite(Number(config.maxPages))\n ? Math.max(1, Math.min(Number(config.maxPages), 20))\n : 5;\n\n/** Walk a dotted `a.b.c` path (with or without a leading `$.`). */\nfunction pick(value: unknown, path: string): unknown {\n if (!path) return value;\n let current = value;\n for (const key of path.replace(/^\\$\\.?/, '').split('.')) {\n if (!key) continue;\n if (current === null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\nfunction rowsFrom(body: string): Record<string, unknown>[] {\n if (format === 'ndjson') {\n const out: Record<string, unknown>[] = [];\n for (const line of body.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const parsed = JSON.parse(trimmed) as unknown;\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n out.push(parsed as Record<string, unknown>);\n }\n }\n return out;\n }\n const parsed = JSON.parse(body) as unknown;\n const items = itemsPath ? pick(parsed, itemsPath) : parsed;\n if (!Array.isArray(items)) {\n throw new Error(\n itemsPath\n ? 'The item path did not resolve to a list. Check `itemsPath` against the response shape.'\n : 'The endpoint did not return a list. Set `itemsPath` to where the rows live in the response.',\n );\n }\n return items.filter(\n (item): item is Record<string, unknown> =>\n Boolean(item) && typeof item === 'object' && !Array.isArray(item),\n );\n}\n\nfunction isThrottle(status: number, headers: Record<string, string | undefined>): boolean {\n if (status === 429) return true;\n return status === 503 && Boolean(headers['retry-after']);\n}\n\nconst priorCursor = typeof input.cursor === 'string' ? input.cursor : '';\nlet nextUrl = rawUrl;\nif (sinceParam && priorCursor) {\n const target = new URL(rawUrl);\n target.searchParams.set(sinceParam, priorCursor);\n nextUrl = target.toString();\n}\n\nconst records: Record<string, unknown>[] = [];\nlet newestSeen = priorCursor;\nlet rateLimited = false;\nlet more = false;\n\nfor (let page = 0; page < maxPages && nextUrl; page++) {\n const target = new URL(nextUrl);\n // Never forward the credential across an origin change — a redirect or a\n // hostile `nextPagePath` would otherwise leak it to a third party.\n if (target.origin !== origin) {\n throw new Error('Pagination changed origin; refusing to forward the credential.');\n }\n const headers = { Accept: format === 'ndjson' ? 'application/x-ndjson' : 'application/json' };\n let response;\n try {\n response = await gezel.http.authed(target.toString(), { credential: '$credential', headers });\n } catch (error) {\n if ((error as { code?: string }).code !== 'CREDENTIAL_MISSING') throw error;\n response = await gezel.http.request(target.toString(), { headers });\n }\n if (isThrottle(response.status, response.headers)) {\n // Emit what this pass collected and flag the throttle rather than\n // throwing: a throw voids the batch and re-fetches the same window next\n // tick, which is how a rate-limited API gets hammered.\n rateLimited = true;\n break;\n }\n if (!response.ok) {\n throw new Error('Row fetch failed (' + response.status + '): ' + response.body.slice(0, 240));\n }\n\n const pageRows = rowsFrom(response.body);\n for (const row of pageRows) {\n records.push(row);\n if (timeField) {\n const value = pick(row, timeField);\n if (typeof value === 'string' && (!newestSeen || value > newestSeen)) newestSeen = value;\n }\n }\n\n let candidate = '';\n if (nextPagePath && format === 'json') {\n const link = pick(JSON.parse(response.body) as unknown, nextPagePath);\n if (typeof link === 'string' && link) candidate = link;\n }\n if (candidate && new URL(candidate, origin).origin === origin) {\n nextUrl = new URL(candidate, origin).toString();\n more = page + 1 >= maxPages;\n } else {\n nextUrl = '';\n }\n}\n\ngezel.output({\n records,\n cursor: newestSeen || null,\n ...(rateLimited ? { rateLimited: true } : {}),\n ...(more && !rateLimited ? { partial: true } : {}),\n});\n","table":"rows"},"normalize":{"kind":"observations"},"actions":[],"completeness":"window","notes":"An observation corpus: rows land as partitioned columnar files rather than one file per record, and gezels read them through list_tables/describe_table/query_table. The column schema is inferred from the first rows that arrive, so `describe_table` marks it inferred — cast explicitly if a comparison behaves oddly. Pagination is bounded per sync and never follows a link to a different origin, so the credential cannot leak to a third party. A throttled source is reported rather than thrown, so the engine backs off instead of re-fetching the same window.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"linear-issues","name":"Linear Issues","description":"Mirror recent Linear issues into a searchable corpus via Linear's MCP server. A rolling window of recent activity, not full history.","tags":["issues","mcp"],"maintainer":{"name":"Gezel"},"version":"1.0.1","releasedAt":"2026-08-13T00:00:00Z","driver":"mcp","configSchema":{"type":"object","properties":{}},"secretShape":{"kind":"apikey","field":"Authorization","label":"Linear personal API key","required":true,"x-gezel-priority":"primary","description":"Create a personal API key in Linear. Do not enter your Linear account password. Grant read access and limit the key to only the teams you want Gezel to sync.","helpUrl":"https://linear.app/docs/api-and-webhooks","helpLabel":"View Linear API-key instructions"},"setupInstructions":{"title":"Create a Linear personal API key","description":"Gezel uses this key only to read issues through Linear's API. Your normal Linear password does not belong here.","steps":["In Linear, open Settings, then Account, then Security & Access.","Under Personal API keys, create a new key named Gezel.","Grant read permissions and restrict team access to only the teams you want synced.","Copy the generated key and paste it into the Linear personal API key field."],"url":"https://linear.app/docs/api-and-webhooks","urlLabel":"View Linear API-key instructions"},"source":{"server":{"kind":"http","transport":"streamable-http","url":"https://mcp.linear.app/mcp","headersFromSecret":{"Authorization":"Bearer $secret"}},"list":{"tool":"list_issues","cursorArg":"updatedAfter","itemsPath":"$.issues","idPath":"$.id","tsPath":"$.updatedAt"},"fetch":{"tool":"get_issue","idArg":"id"}},"normalize":{"kind":"mapping","map":{"id":"$.id","title":"$.title","group":"$.team.name","timestamp":"$.updatedAt","body":"$.description","frontmatter":{"identifier":"$.identifier","state":"$.state.name","assignee":"$.assignee.name","url":"$.url"}}},"actions":[],"completeness":"window","availableVersions":["1.0.1","1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"linkedin-posts","name":"LinkedIn Posts","description":"Publish to LinkedIn from Gezel: your crew drafts posts in your voice, and with your permission queues them — every publish still asks for your consent before anything goes out. LinkedIn does not let ordinary apps read your feed (that part is reserved for its partner program), so nothing syncs back: reception and analytics stay on LinkedIn, and this connection is honestly posting-only.","tags":["linkedin","social","posts","native"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-12T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"allowPublish":{"type":"boolean","title":"Allow this project's gezels to queue posts for publishing (each publish still needs your consent)","default":false}}},"secretShape":{"kind":"oauth2","authorizeUrl":"https://www.linkedin.com/oauth/v2/authorization","tokenUrl":"https://www.linkedin.com/oauth/v2/accessToken","scopes":"openid profile w_member_social","clientIdEnv":"GEZEL_LINKEDIN_CLIENT_ID","clientSecretEnv":"GEZEL_LINKEDIN_CLIENT_SECRET","longLived":true,"clientSetup":{"providerLabel":"LinkedIn","docsUrl":"https://www.linkedin.com/developers/apps","secretRequired":true,"appTypeNote":"Create a LinkedIn developer app and request the 'Share on LinkedIn' and 'Sign in with LinkedIn using OpenID Connect' products. LinkedIn requires the client secret; it stays in your OS keychain.","redirectPort":6243,"redirectNote":"LinkedIn matches redirect URLs exactly. Register the URI shown here character-for-character. Tokens last about 60 days; reconnect when prompted.","steps":["Sign in at linkedin.com/developers/apps and create an app — it must be tied to a LinkedIn Page (create a free one if you have none).","On the Products tab, request 'Share on LinkedIn' and 'Sign in with LinkedIn using OpenID Connect'; both are approved instantly.","On the Auth tab, add the redirect URI shown below under 'Authorized redirect URLs for your app', exactly.","Copy the Client ID and Client Secret from the Auth tab.","Paste both below — the secret stays in your OS keychain."]}},"source":{"adapterId":"linkedin-posts"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"social-publish"}],"completeness":"window","notes":"Publishing-only, by LinkedIn's design: reading a member's posts requires LinkedIn's restricted Community Management partner program, which ordinary developer apps cannot join, so nothing syncs back from the feed — no post history, no engagement counts, no mentions. The corpus root holds only what Gezel itself did: the _meta.json marker plus a receipt for every published post under _actions/_sent/; reception and analytics stay on linkedin.com. Publishing works with the standard 'Share on LinkedIn' product: text posts up to 3000 characters, posted to the linked member's own feed with a permalink returned for the record. Publishing is off by default — a gezel can only draft until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Requires your own LinkedIn developer app: set GEZEL_LINKEDIN_CLIENT_ID and GEZEL_LINKEDIN_CLIENT_SECRET, or register the app under Settings — LinkedIn requires the client secret (it has no public-client PKCE support; Gezel still sends PKCE params, which LinkedIn ignores). LinkedIn issues no refresh token to ordinary apps, so the connection lasts about 60 days and then asks to be reconnected.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"mail-microsoft365","name":"Microsoft 365 Mail","description":"Mirror a Microsoft 365 (work/school) mailbox into a normalized, searchable corpus of messages (OAuth).","tags":["email","native","oauth"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-09T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"address":{"type":"string","title":"Email address"},"provider":{"type":"string","const":"microsoft365"},"tenant":{"type":"string","const":"organizations"},"syncFolders":{"type":"array","title":"Folders to sync","items":{"type":"string"}},"allowedRecipients":{"type":"array","title":"Allowed send recipients","items":{"type":"string"}},"allowedDomains":{"type":"array","title":"Allowed send domains","items":{"type":"string"}}},"required":["address"]},"secretShape":{"kind":"oauth2","authorizeUrl":"https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize","tokenUrl":"https://login.microsoftonline.com/organizations/oauth2/v2.0/token","scopes":"offline_access https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Send","clientIdEnv":"GEZEL_MICROSOFT_CLIENT_ID","clientSetup":{"providerLabel":"Microsoft","docsUrl":"https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade","secretRequired":false,"appTypeNote":"Register an app in Microsoft Entra (Azure portal) and add the Mobile and desktop applications platform — a public client needs no client secret. Any loopback port works for the redirect.","steps":["Sign in at portal.azure.com and open Microsoft Entra ID, then App registrations, then New registration.","For supported account types choose 'Accounts in any organizational directory and personal Microsoft accounts'.","Under Authentication, add the 'Mobile and desktop applications' platform and enable the loopback redirect — any 127.0.0.1 port works.","No client secret is needed: this registration is a public client.","Copy the Application (client) ID from the Overview page and paste it below."]}},"source":{"adapterId":"mail-microsoft365"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","notes":"Work/school accounts in the 'organizations' Azure AD tenant. Sending is deny-by-default: only recipients on the binding's allowed recipients/domains lists can be sent to, and night shift defers sends to the outbox for daytime approval.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"mail-outlook","name":"Outlook.com Mail","description":"Mirror a personal Outlook.com mailbox into a normalized, searchable corpus of messages (OAuth).","tags":["email","native","oauth"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-09T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"address":{"type":"string","title":"Email address"},"provider":{"type":"string","const":"outlook"},"tenant":{"type":"string","const":"consumers"},"syncFolders":{"type":"array","title":"Folders to sync","items":{"type":"string"}},"allowedRecipients":{"type":"array","title":"Allowed send recipients","items":{"type":"string"}},"allowedDomains":{"type":"array","title":"Allowed send domains","items":{"type":"string"}}},"required":["address"]},"secretShape":{"kind":"oauth2","authorizeUrl":"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize","tokenUrl":"https://login.microsoftonline.com/consumers/oauth2/v2.0/token","scopes":"offline_access https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Send","clientIdEnv":"GEZEL_MICROSOFT_CLIENT_ID","clientSetup":{"providerLabel":"Microsoft","docsUrl":"https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade","secretRequired":false,"appTypeNote":"Register an app in Microsoft Entra (Azure portal) and add the Mobile and desktop applications platform — a public client needs no client secret. Any loopback port works for the redirect.","steps":["Sign in at portal.azure.com and open Microsoft Entra ID, then App registrations, then New registration.","For supported account types choose 'Accounts in any organizational directory and personal Microsoft accounts'.","Under Authentication, add the 'Mobile and desktop applications' platform and enable the loopback redirect — any 127.0.0.1 port works.","No client secret is needed: this registration is a public client.","Copy the Application (client) ID from the Overview page and paste it below."]}},"source":{"adapterId":"mail-outlook"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","notes":"Personal Outlook.com accounts in the 'consumers' tenant. Sending is deny-by-default: only recipients on the binding's allowed recipients/domains lists can be sent to, and night shift defers sends to the outbox for daytime approval.","availableVersions":["1.0.0"]}},{"manifest":{"schemaVersion":1,"kind":"connector-type","id":"x-posts","name":"X Posts","description":"Keep a searchable copy of your X (Twitter) presence: your posts with their like, repost, and view counts and a daily engagement history, optional mentions, and a daily follower count. Links through X's own sign-in. X charges for API reads, so syncs are spaced out by default. Your crew can draft posts for review and — once you allow publishing on the connector — post them with your per-post consent; posting fits X's free tier.","tags":["x","twitter","social","posts","native"],"maintainer":{"name":"Gezel"},"version":"1.0.0","releasedAt":"2026-08-12T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"syncMentions":{"type":"boolean","title":"Sync mentions of the account (costs extra paid reads)","default":false},"allowPublish":{"type":"boolean","title":"Allow this project's gezels to queue posts for publishing (each publish still needs your consent)","default":false},"syncIntervalHours":{"type":"number","title":"Hours between post syncs (X reads are paid — keep this generous)","default":4,"minimum":0.25,"maximum":720},"metricsWindowDays":{"type":"integer","title":"Days of recent posts to keep engagement counts fresh for","default":7,"minimum":1,"maximum":365},"metricsRefreshHours":{"type":"number","title":"Hours between engagement-count refreshes","default":24,"minimum":0.25,"maximum":720},"metricsHistory":{"type":"boolean","title":"Record daily engagement history records","default":true},"statsIntervalHours":{"type":"number","title":"Hours between account-stats snapshots","default":6,"minimum":0.25,"maximum":720}}},"secretShape":{"kind":"oauth2","authorizeUrl":"https://x.com/i/oauth2/authorize","tokenUrl":"https://api.x.com/2/oauth2/token","scopes":"tweet.read users.read offline.access tweet.write","clientIdEnv":"GEZEL_X_CLIENT_ID","clientSecretEnv":"GEZEL_X_CLIENT_SECRET","clientSetup":{"providerLabel":"X (Twitter)","docsUrl":"https://developer.x.com/en/portal/dashboard","secretRequired":false,"appTypeNote":"Create a free developer app and set its type to a Native/Public client — PKCE needs no client secret. Your API tier and read limits are your own.","redirectPort":6241,"redirectNote":"X matches callback URLs exactly. Register the URI shown here character-for-character.","steps":["Sign in at developer.x.com and create a project with an app — the free plan works for publishing.","In the app's settings, open 'User authentication settings' and click Set up.","Choose the 'Native App' (public client) type and enable OAuth 2.0.","Paste the redirect URI shown below into Callback URI / Redirect URL, character-for-character.","Fill the required website URL field with any page of yours and save.","Copy the OAuth 2.0 Client ID from the app's Keys and tokens page and paste it below — native apps use PKCE, so there is no secret to copy."]}},"source":{"adapterId":"x-posts"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"social-publish"}],"completeness":"window","notes":"Syncs three things from the linked account: the account's own posts (text with links expanded, language, reply threading, plus like/repost/reply/quote/bookmark/view counts), mentions of the account when enabled, and a once-per-day account-stats record (followers, following, total posts). Be blunt about cost: X charges for API reads on every tier, and the free tier allows roughly 100 reads per month — that fits only the default generous intervals (posts every 4 hours, stats every 6, engagement refresh daily); for real day-to-day use you need the paid Basic tier or higher. Every read scope is interval-gated (syncIntervalHours for posts and mentions, statsIntervalHours for stats) so background syncing can never burn reads faster than configured, and mentions are OFF by default to conserve reads. Engagement counts on posts from the last metricsWindowDays are re-observed roughly every metricsRefreshHours as one extra page per refresh, updating existing files in place; each observation also appends a per-day metrics-history record per post (the engagement curve over time) unless metricsHistory is turned off. The first sync captures up to 500 recent posts; older history is not backfilled. Publishing works even on the free tier — X budgets writes separately at roughly 500 posts per month — and is off by default: a gezel can only draft posts until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Accounts linked before publishing existed must reconnect from the connector's settings to grant the new tweet.write scope. Requires your own X developer app: set GEZEL_X_CLIENT_ID (and GEZEL_X_CLIENT_SECRET for confidential apps) to enable linking.","availableVersions":["1.0.0"]}}]}
|
package/package.json
CHANGED
|
@@ -195,6 +195,68 @@
|
|
|
195
195
|
"quantization": {
|
|
196
196
|
"type": "string"
|
|
197
197
|
},
|
|
198
|
+
"drafter": {
|
|
199
|
+
"type": "object",
|
|
200
|
+
"properties": {
|
|
201
|
+
"huggingfaceRepo": {
|
|
202
|
+
"type": "string",
|
|
203
|
+
"pattern": "^[A-Za-z0-9_\\-.]+\\/[A-Za-z0-9_\\-.]+$"
|
|
204
|
+
},
|
|
205
|
+
"revision": {
|
|
206
|
+
"type": "string"
|
|
207
|
+
},
|
|
208
|
+
"kind": {
|
|
209
|
+
"default": "mtp",
|
|
210
|
+
"type": "string",
|
|
211
|
+
"enum": [
|
|
212
|
+
"mtp"
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
"files": {
|
|
216
|
+
"minItems": 1,
|
|
217
|
+
"type": "array",
|
|
218
|
+
"items": {
|
|
219
|
+
"type": "object",
|
|
220
|
+
"properties": {
|
|
221
|
+
"name": {
|
|
222
|
+
"type": "string",
|
|
223
|
+
"minLength": 1,
|
|
224
|
+
"maxLength": 1024
|
|
225
|
+
},
|
|
226
|
+
"sha256": {
|
|
227
|
+
"type": "string",
|
|
228
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
229
|
+
},
|
|
230
|
+
"sizeBytes": {
|
|
231
|
+
"type": "integer",
|
|
232
|
+
"exclusiveMinimum": 0,
|
|
233
|
+
"maximum": 9007199254740991
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
"required": [
|
|
237
|
+
"name",
|
|
238
|
+
"sha256",
|
|
239
|
+
"sizeBytes"
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"approxSizeBytes": {
|
|
244
|
+
"type": "integer",
|
|
245
|
+
"exclusiveMinimum": 0,
|
|
246
|
+
"maximum": 9007199254740991
|
|
247
|
+
},
|
|
248
|
+
"residentBytes": {
|
|
249
|
+
"type": "integer",
|
|
250
|
+
"exclusiveMinimum": 0,
|
|
251
|
+
"maximum": 9007199254740991
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
"required": [
|
|
255
|
+
"huggingfaceRepo",
|
|
256
|
+
"files",
|
|
257
|
+
"approxSizeBytes"
|
|
258
|
+
]
|
|
259
|
+
},
|
|
198
260
|
"chatTemplate": {
|
|
199
261
|
"type": "string"
|
|
200
262
|
},
|