@bendyline/gilde 0.1.21 → 0.1.22
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/authoring/chat-models/nemotron3.5-lightning-30b-q4.json +3 -2
- package/authoring/chat-models/nemotron3.5-lightning-30b-q8.json +3 -2
- package/authoring/page-demo-stub.js +85 -0
- package/data/chat-models/index.json +1 -1
- package/data/chat-models/ne/nemotron3.5-lightning-30b-q4/manifest.json +3 -2
- package/data/chat-models/ne/nemotron3.5-lightning-30b-q8/manifest.json +3 -2
- package/data/connector-types/bl/bluesky-posts/manifest.json +16 -0
- package/data/connector-types/bl/bluesky-posts/versions/1.0.0/manifest.json +70 -0
- package/data/connector-types/gi/github-issues/versions/1.0.1/manifest.json +67 -0
- package/data/connector-types/in/instagram-media/manifest.json +17 -0
- package/data/connector-types/in/instagram-media/versions/1.0.0/manifest.json +70 -0
- package/data/connector-types/index.json +1 -1
- package/data/connector-types/x-/x-posts/manifest.json +17 -0
- package/data/connector-types/x-/x-posts/versions/1.0.0/manifest.json +66 -0
- package/data/craftbook-templates/dr/draft-social-post/manifest.json +23 -0
- package/data/craftbook-templates/dr/draft-social-post/versions/1.0.0/craftbook.json +176 -0
- package/data/craftbook-templates/dr/draft-social-post/versions/1.0.0/test.json +161 -0
- package/data/craftbook-templates/index.json +1 -1
- package/data/craftbook-templates/re/reception-report/manifest.json +21 -0
- package/data/craftbook-templates/re/reception-report/versions/1.0.0/craftbook.json +170 -0
- package/data/craftbook-templates/re/reception-report/versions/1.0.0/test.json +168 -0
- package/data/craftbook-templates/so/social-digest/manifest.json +21 -0
- package/data/craftbook-templates/so/social-digest/versions/1.0.0/craftbook.json +133 -0
- package/data/craftbook-templates/so/social-digest/versions/1.0.0/test.json +168 -0
- package/data/gezel-templates/index.json +1 -1
- package/data/gezel-templates/om/omroeper/manifest.json +20 -0
- package/data/gezel-templates/om/omroeper/versions/1.0.0/about.md +33 -0
- package/data/gezel-templates/om/omroeper/versions/1.0.0/manifest.json +23 -0
- package/data/project-types/im/image-feed/manifest.json +20 -0
- package/data/project-types/im/image-feed/versions/1.0.0/about.md +21 -0
- package/data/project-types/im/image-feed/versions/1.0.0/manifest.json +330 -0
- package/data/project-types/im/image-feed/versions/1.0.0/mission.md +10 -0
- package/data/project-types/im/image-feed/versions/1.0.0/pages/gallery/index.html +831 -0
- package/data/project-types/im/image-feed/versions/1.0.0/posts/index.json +4 -0
- package/data/project-types/im/image-feed/versions/1.0.0/style-guide.md +24 -0
- package/data/project-types/index.json +1 -1
- package/data/project-types/so/social-feed/manifest.json +20 -0
- package/data/project-types/so/social-feed/versions/1.0.0/about.md +19 -0
- package/data/project-types/so/social-feed/versions/1.0.0/manifest.json +330 -0
- package/data/project-types/so/social-feed/versions/1.0.0/mission.md +10 -0
- package/data/project-types/so/social-feed/versions/1.0.0/pages/dashboard/index.html +754 -0
- package/data/project-types/so/social-feed/versions/1.0.0/posts/index.json +4 -0
- package/data/project-types/so/social-feed/versions/1.0.0/voice-guide.md +22 -0
- package/package.json +1 -1
- package/schemas/connector-type-version.schema.json +6 -0
- package/schemas/craftbook-test.schema.json +1 -0
- package/schemas/project-type-version.schema.json +4 -0
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"version": "1.0.0",
|
|
26
26
|
"updatedAt": "2026-08-11T00:00:00Z",
|
|
27
|
+
"minGezelVersion": "1.26223",
|
|
27
28
|
"license": "OpenMDW-1.1",
|
|
28
29
|
"licenseClass": "open",
|
|
29
30
|
"licenseShortName": "OpenMDW 1.1",
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
"profiles": {
|
|
69
70
|
"thinking-general": {
|
|
70
71
|
"sampling": {
|
|
71
|
-
"temperature":
|
|
72
|
+
"temperature": 0.6,
|
|
72
73
|
"topP": 0.95,
|
|
73
74
|
"maxTokens": 16384
|
|
74
75
|
},
|
|
@@ -79,7 +80,7 @@
|
|
|
79
80
|
},
|
|
80
81
|
"thinking-coding": {
|
|
81
82
|
"sampling": {
|
|
82
|
-
"temperature":
|
|
83
|
+
"temperature": 0.6,
|
|
83
84
|
"topP": 0.95,
|
|
84
85
|
"maxTokens": 32768
|
|
85
86
|
},
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"version": "1.0.0",
|
|
26
26
|
"updatedAt": "2026-08-11T00:00:00Z",
|
|
27
|
+
"minGezelVersion": "1.26223",
|
|
27
28
|
"license": "OpenMDW-1.1",
|
|
28
29
|
"licenseClass": "open",
|
|
29
30
|
"licenseShortName": "OpenMDW 1.1",
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
"profiles": {
|
|
69
70
|
"thinking-general": {
|
|
70
71
|
"sampling": {
|
|
71
|
-
"temperature":
|
|
72
|
+
"temperature": 0.6,
|
|
72
73
|
"topP": 0.95,
|
|
73
74
|
"maxTokens": 16384
|
|
74
75
|
},
|
|
@@ -79,7 +80,7 @@
|
|
|
79
80
|
},
|
|
80
81
|
"thinking-coding": {
|
|
81
82
|
"sampling": {
|
|
82
|
-
"temperature":
|
|
83
|
+
"temperature": 0.6,
|
|
83
84
|
"topP": 0.95,
|
|
84
85
|
"maxTokens": 32768
|
|
85
86
|
},
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "connector-type",
|
|
4
|
+
"id": "bluesky-posts",
|
|
5
|
+
"name": "Bluesky Posts",
|
|
6
|
+
"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.",
|
|
7
|
+
"tags": [
|
|
8
|
+
"bluesky",
|
|
9
|
+
"social",
|
|
10
|
+
"posts",
|
|
11
|
+
"native"
|
|
12
|
+
],
|
|
13
|
+
"maintainer": {
|
|
14
|
+
"name": "Gezel"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"releasedAt": "2026-08-12T00:00:00Z",
|
|
5
|
+
"driver": "native",
|
|
6
|
+
"source": {
|
|
7
|
+
"adapterId": "bluesky-posts"
|
|
8
|
+
},
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"handle": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"title": "Bluesky handle (e.g. yourname.bsky.social)"
|
|
15
|
+
},
|
|
16
|
+
"service": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"title": "Service URL",
|
|
19
|
+
"default": "https://bsky.social"
|
|
20
|
+
},
|
|
21
|
+
"syncMentions": {
|
|
22
|
+
"type": "boolean",
|
|
23
|
+
"title": "Sync mentions, replies, and quotes",
|
|
24
|
+
"default": true
|
|
25
|
+
},
|
|
26
|
+
"allowPublish": {
|
|
27
|
+
"type": "boolean",
|
|
28
|
+
"title": "Allow this project's gezels to queue posts for publishing (each publish still needs your consent)",
|
|
29
|
+
"default": false
|
|
30
|
+
},
|
|
31
|
+
"metricsWindowDays": {
|
|
32
|
+
"type": "integer",
|
|
33
|
+
"title": "Days of recent posts to keep engagement counts fresh for",
|
|
34
|
+
"default": 30,
|
|
35
|
+
"minimum": 1,
|
|
36
|
+
"maximum": 365
|
|
37
|
+
},
|
|
38
|
+
"metricsRefreshHours": {
|
|
39
|
+
"type": "number",
|
|
40
|
+
"title": "Hours between engagement-count refreshes",
|
|
41
|
+
"default": 6
|
|
42
|
+
},
|
|
43
|
+
"statsIntervalHours": {
|
|
44
|
+
"type": "number",
|
|
45
|
+
"title": "Hours between account-stats snapshots",
|
|
46
|
+
"default": 6
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"required": [
|
|
50
|
+
"handle"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"secretShape": {
|
|
54
|
+
"kind": "apikey",
|
|
55
|
+
"field": "appPassword",
|
|
56
|
+
"label": "Bluesky app password",
|
|
57
|
+
"required": true
|
|
58
|
+
},
|
|
59
|
+
"normalize": {
|
|
60
|
+
"kind": "native"
|
|
61
|
+
},
|
|
62
|
+
"actions": [
|
|
63
|
+
{
|
|
64
|
+
"name": "publish",
|
|
65
|
+
"consentScope": "social-publish"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"completeness": "window",
|
|
69
|
+
"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. The first sync captures up to 500 recent posts; older history is not backfilled. Reposts are skipped in this version, and 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)."
|
|
70
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"releasedAt": "2026-08-12T00:00:00Z",
|
|
5
|
+
"driver": "script",
|
|
6
|
+
"source": {
|
|
7
|
+
"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",
|
|
8
|
+
"idPath": "$.id"
|
|
9
|
+
},
|
|
10
|
+
"configSchema": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"owner": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"title": "Repository owner"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"title": "Repository name"
|
|
20
|
+
},
|
|
21
|
+
"apiBaseUrl": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"title": "API base URL (GitHub Enterprise only)"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": [
|
|
27
|
+
"owner",
|
|
28
|
+
"repository"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"secretShape": {
|
|
32
|
+
"kind": "apikey",
|
|
33
|
+
"field": "token",
|
|
34
|
+
"label": "GitHub personal access token",
|
|
35
|
+
"required": false
|
|
36
|
+
},
|
|
37
|
+
"normalize": {
|
|
38
|
+
"kind": "mapping",
|
|
39
|
+
"map": {
|
|
40
|
+
"id": "$.id",
|
|
41
|
+
"title": "$.title",
|
|
42
|
+
"group": "$.gezel_repository",
|
|
43
|
+
"timestamp": "$.updated_at",
|
|
44
|
+
"body": "$.body",
|
|
45
|
+
"frontmatter": {
|
|
46
|
+
"number": "$.number",
|
|
47
|
+
"state": "$.state",
|
|
48
|
+
"stateReason": "$.state_reason",
|
|
49
|
+
"author": "$.user.login",
|
|
50
|
+
"assignees": "$.gezel_assignees",
|
|
51
|
+
"labels": "$.gezel_labels",
|
|
52
|
+
"milestone": "$.milestone.title",
|
|
53
|
+
"comments": "$.comments",
|
|
54
|
+
"url": "$.html_url",
|
|
55
|
+
"createdAt": "$.created_at",
|
|
56
|
+
"closedAt": "$.closed_at",
|
|
57
|
+
"locked": "$.locked"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"completeness": "window",
|
|
62
|
+
"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.",
|
|
63
|
+
"allowedOrigins": [
|
|
64
|
+
"https://api.github.com",
|
|
65
|
+
"$config.apiBaseUrl"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "connector-type",
|
|
4
|
+
"id": "instagram-media",
|
|
5
|
+
"name": "Instagram Media",
|
|
6
|
+
"description": "Keep a searchable copy of your Instagram presence: your posts, reels, and carousels with captions, links, like and comment counts, 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 you, but Gezel never publishes to Instagram itself.",
|
|
7
|
+
"tags": [
|
|
8
|
+
"instagram",
|
|
9
|
+
"social",
|
|
10
|
+
"media",
|
|
11
|
+
"posts",
|
|
12
|
+
"native"
|
|
13
|
+
],
|
|
14
|
+
"maintainer": {
|
|
15
|
+
"name": "Gezel"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"releasedAt": "2026-08-12T00:00:00Z",
|
|
5
|
+
"driver": "native",
|
|
6
|
+
"source": {
|
|
7
|
+
"adapterId": "instagram-media"
|
|
8
|
+
},
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"downloadMedia": {
|
|
13
|
+
"type": "boolean",
|
|
14
|
+
"title": "Download media files (reserved — this version stores captions and links only)",
|
|
15
|
+
"default": false
|
|
16
|
+
},
|
|
17
|
+
"maxMedia": {
|
|
18
|
+
"type": "integer",
|
|
19
|
+
"title": "Most recent posts to keep synced",
|
|
20
|
+
"default": 200,
|
|
21
|
+
"minimum": 1,
|
|
22
|
+
"maximum": 10000
|
|
23
|
+
},
|
|
24
|
+
"metricsWindowDays": {
|
|
25
|
+
"type": "integer",
|
|
26
|
+
"title": "Days of recent posts to keep like/comment counts fresh for",
|
|
27
|
+
"default": 30,
|
|
28
|
+
"minimum": 1,
|
|
29
|
+
"maximum": 365
|
|
30
|
+
},
|
|
31
|
+
"metricsRefreshHours": {
|
|
32
|
+
"type": "number",
|
|
33
|
+
"title": "Hours between like/comment-count refreshes",
|
|
34
|
+
"default": 12,
|
|
35
|
+
"minimum": 0.25,
|
|
36
|
+
"maximum": 720
|
|
37
|
+
},
|
|
38
|
+
"statsIntervalHours": {
|
|
39
|
+
"type": "number",
|
|
40
|
+
"title": "Hours between account-stats snapshots",
|
|
41
|
+
"default": 6,
|
|
42
|
+
"minimum": 0.25,
|
|
43
|
+
"maximum": 720
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"secretShape": {
|
|
48
|
+
"kind": "oauth2",
|
|
49
|
+
"authorizeUrl": "https://www.instagram.com/oauth/authorize",
|
|
50
|
+
"tokenUrl": "https://api.instagram.com/oauth/access_token",
|
|
51
|
+
"scopes": "instagram_business_basic",
|
|
52
|
+
"clientIdEnv": "GEZEL_INSTAGRAM_CLIENT_ID",
|
|
53
|
+
"clientSecretEnv": "GEZEL_INSTAGRAM_CLIENT_SECRET",
|
|
54
|
+
"longLivedExchange": {
|
|
55
|
+
"exchangeUrl": "https://graph.instagram.com/access_token",
|
|
56
|
+
"refreshUrl": "https://graph.instagram.com/refresh_access_token"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"normalize": {
|
|
60
|
+
"kind": "native"
|
|
61
|
+
},
|
|
62
|
+
"actions": [
|
|
63
|
+
{
|
|
64
|
+
"name": "publish",
|
|
65
|
+
"consentScope": "manual-export"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"completeness": "window",
|
|
69
|
+
"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, 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 is manual-export only: gezels can draft posts for review, but Gezel never publishes to Instagram on your behalf."
|
|
70
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schemaVersion":1,"kind":"connector-type","count":11,"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.0","releasedAt":"2026-07-08T00: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"},"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.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.0","releasedAt":"2026-07-08T00: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"},"source":{"adapterId":"mail-imap"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","availableVersions":["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.0","releasedAt":"2026-07-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},"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 },\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[] = [];\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\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 (!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\ngezel.output({ records, cursor: nextCursor });\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.","availableVersions":["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.0","releasedAt":"2026-08-10T00: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 token (optional; uses your GitHub sign-in)","required":false},"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.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.0","releasedAt":"2026-08-09T00: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 token (optional; uses your GitHub sign-in)","required":false},"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.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.0","releasedAt":"2026-07-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},"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.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"}},"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"}},"source":{"adapterId":"calendar-google"},"normalize":{"kind":"native"},"actions":[],"completeness":"mirror","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.0","releasedAt":"2026-07-08T00:00:00Z","driver":"mcp","configSchema":{"type":"object","properties":{}},"secretShape":{"kind":"apikey","field":"Authorization"},"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.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"},"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"},"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"]}}]}
|
|
1
|
+
{"schemaVersion":1,"kind":"connector-type","count":14,"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.0","releasedAt":"2026-07-08T00: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"},"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.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.0","releasedAt":"2026-08-12T00:00:00Z","driver":"native","configSchema":{"type":"object","properties":{"handle":{"type":"string","title":"Bluesky handle (e.g. yourname.bsky.social)"},"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}},"required":["handle"]},"secretShape":{"kind":"apikey","field":"appPassword","label":"Bluesky app password","required":true},"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. The first sync captures up to 500 recent posts; older history is not backfilled. Reposts are skipped in this version, and 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).","availableVersions":["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.0","releasedAt":"2026-07-08T00: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"},"source":{"adapterId":"mail-imap"},"normalize":{"kind":"native"},"actions":[{"name":"send","consentScope":"recipient-allowlist"}],"completeness":"mirror","availableVersions":["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.1","releasedAt":"2026-08-12T00: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},"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.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.0","releasedAt":"2026-08-10T00: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 token (optional; uses your GitHub sign-in)","required":false},"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.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.0","releasedAt":"2026-08-09T00: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 token (optional; uses your GitHub sign-in)","required":false},"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.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.0","releasedAt":"2026-07-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},"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.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"}},"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"}},"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, 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 you, but Gezel never publishes to Instagram itself.","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},"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},"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","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"}},"source":{"adapterId":"instagram-media"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"manual-export"}],"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, 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 is manual-export only: gezels can draft posts for review, but Gezel never publishes to Instagram on your behalf.","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.0","releasedAt":"2026-07-08T00:00:00Z","driver":"mcp","configSchema":{"type":"object","properties":{}},"secretShape":{"kind":"apikey","field":"Authorization"},"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.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"},"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"},"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, 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 you, but Gezel never posts to X itself — you copy a draft and publish it yourself.","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},"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},"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","clientIdEnv":"GEZEL_X_CLIENT_ID","clientSecretEnv":"GEZEL_X_CLIENT_SECRET"},"source":{"adapterId":"x-posts"},"normalize":{"kind":"native"},"actions":[{"name":"publish","consentScope":"manual-export"}],"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. The first sync captures up to 500 recent posts; older history is not backfilled. Publishing is manual-export only in this version: gezels can draft posts for review, but Gezel never posts to X on your behalf — you copy a draft and publish it yourself. 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"]}}]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "connector-type",
|
|
4
|
+
"id": "x-posts",
|
|
5
|
+
"name": "X Posts",
|
|
6
|
+
"description": "Keep a searchable copy of your X (Twitter) presence: your posts with their like, repost, and view counts, 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 you, but Gezel never posts to X itself — you copy a draft and publish it yourself.",
|
|
7
|
+
"tags": [
|
|
8
|
+
"x",
|
|
9
|
+
"twitter",
|
|
10
|
+
"social",
|
|
11
|
+
"posts",
|
|
12
|
+
"native"
|
|
13
|
+
],
|
|
14
|
+
"maintainer": {
|
|
15
|
+
"name": "Gezel"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"releasedAt": "2026-08-12T00:00:00Z",
|
|
5
|
+
"driver": "native",
|
|
6
|
+
"source": {
|
|
7
|
+
"adapterId": "x-posts"
|
|
8
|
+
},
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"syncMentions": {
|
|
13
|
+
"type": "boolean",
|
|
14
|
+
"title": "Sync mentions of the account (costs extra paid reads)",
|
|
15
|
+
"default": false
|
|
16
|
+
},
|
|
17
|
+
"syncIntervalHours": {
|
|
18
|
+
"type": "number",
|
|
19
|
+
"title": "Hours between post syncs (X reads are paid — keep this generous)",
|
|
20
|
+
"default": 4,
|
|
21
|
+
"minimum": 0.25,
|
|
22
|
+
"maximum": 720
|
|
23
|
+
},
|
|
24
|
+
"metricsWindowDays": {
|
|
25
|
+
"type": "integer",
|
|
26
|
+
"title": "Days of recent posts to keep engagement counts fresh for",
|
|
27
|
+
"default": 7,
|
|
28
|
+
"minimum": 1,
|
|
29
|
+
"maximum": 365
|
|
30
|
+
},
|
|
31
|
+
"metricsRefreshHours": {
|
|
32
|
+
"type": "number",
|
|
33
|
+
"title": "Hours between engagement-count refreshes",
|
|
34
|
+
"default": 24,
|
|
35
|
+
"minimum": 0.25,
|
|
36
|
+
"maximum": 720
|
|
37
|
+
},
|
|
38
|
+
"statsIntervalHours": {
|
|
39
|
+
"type": "number",
|
|
40
|
+
"title": "Hours between account-stats snapshots",
|
|
41
|
+
"default": 6,
|
|
42
|
+
"minimum": 0.25,
|
|
43
|
+
"maximum": 720
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"secretShape": {
|
|
48
|
+
"kind": "oauth2",
|
|
49
|
+
"authorizeUrl": "https://x.com/i/oauth2/authorize",
|
|
50
|
+
"tokenUrl": "https://api.x.com/2/oauth2/token",
|
|
51
|
+
"scopes": "tweet.read users.read offline.access",
|
|
52
|
+
"clientIdEnv": "GEZEL_X_CLIENT_ID",
|
|
53
|
+
"clientSecretEnv": "GEZEL_X_CLIENT_SECRET"
|
|
54
|
+
},
|
|
55
|
+
"normalize": {
|
|
56
|
+
"kind": "native"
|
|
57
|
+
},
|
|
58
|
+
"actions": [
|
|
59
|
+
{
|
|
60
|
+
"name": "publish",
|
|
61
|
+
"consentScope": "manual-export"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"completeness": "window",
|
|
65
|
+
"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. The first sync captures up to 500 recent posts; older history is not backfilled. Publishing is manual-export only in this version: gezels can draft posts for review, but Gezel never posts to X on your behalf — you copy a draft and publish it yourself. Requires your own X developer app: set GEZEL_X_CLIENT_ID (and GEZEL_X_CLIENT_SECRET for confidential apps) to enable linking."
|
|
66
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "craftbook-template",
|
|
4
|
+
"id": "draft-social-post",
|
|
5
|
+
"role": "general",
|
|
6
|
+
"name": "Draft a Social Post",
|
|
7
|
+
"description": "Draft one social post from idea to approved copy. Reads your voice guide and recent posts, grounds the angle in what your feeds actually responded to, drafts the base message, then fits a variant to each platform's limits — Bluesky 300 graphemes, X 280 characters, Instagram captions with the hook in the first 125 characters — with hashtags where idiomatic, alt text for media, and thread splits when the copy runs long. Then it stops for your review: approve, ask for a revision round, or approve and queue to Bluesky through the outbox. Covers social media copywriting, tweet and thread drafting, Bluesky posts, Instagram captions, post scheduling, and a human approval gate. Nothing is ever published without your say-so.",
|
|
8
|
+
"tags": [
|
|
9
|
+
"social",
|
|
10
|
+
"post",
|
|
11
|
+
"copywriting",
|
|
12
|
+
"bluesky",
|
|
13
|
+
"twitter",
|
|
14
|
+
"instagram",
|
|
15
|
+
"approval"
|
|
16
|
+
],
|
|
17
|
+
"maintainer": {
|
|
18
|
+
"name": "Gezel"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"yankedVersions": [],
|
|
22
|
+
"workflow": "build-loop"
|
|
23
|
+
}
|