Ima Knowledge Base
countbot-ai/CountBot
通过 IMA OpenAPI 处理知识库任务。支持知识库内容搜索、命中详情查看、条目浏览、列出知识库、上传文件、导入网页。用户提到知识库、资料库、上传到知识库、导入网页、搜知识库时使用。
Add or update a Sim knowledge base connector for syncing documents from an external source, including auth mode, config fields, pagination, document mapping, tags, and registry wiring.
$ npx skills add simstudioai/sim --skill add-connector -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install simstudioai/sim add-connector --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/simstudioai/sim.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/add-connector .claude/skills/add-connector && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "add-connector" agent skill from https://github.com/simstudioai/sim/tree/main/.agents/skills/add-connector into .claude/skills/add-connector/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-connector", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/simstudioai/sim/tree/main/.agents/skills/add-connectorType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add simstudioai/sim --skill add-connector -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install simstudioai/sim add-connector --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/simstudioai/sim.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.agents/skills/add-connector .agents/skills/add-connector && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "add-connector" agent skill from https://github.com/simstudioai/sim/tree/main/.agents/skills/add-connector into .agents/skills/add-connector/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-connector", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add simstudioai/sim --skill add-connector -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install simstudioai/sim add-connector --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/simstudioai/sim.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.agents/skills/add-connector .cursor/skills/add-connector && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "add-connector" agent skill from https://github.com/simstudioai/sim/tree/main/.agents/skills/add-connector into .cursor/skills/add-connector/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-connector", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/simstudioai/sim.git --path .agents/skills/add-connector--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add simstudioai/sim --skill add-connector -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install simstudioai/sim add-connector --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/simstudioai/sim.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.agents/skills/add-connector .gemini/skills/add-connector && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "add-connector" agent skill from https://github.com/simstudioai/sim/tree/main/.agents/skills/add-connector into .gemini/skills/add-connector/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-connector", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install simstudioai/sim add-connectorInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add simstudioai/sim --skill add-connector -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/simstudioai/sim.git skills-src && mkdir -p .github/skills && cp -r skills-src/.agents/skills/add-connector .github/skills/add-connector && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "add-connector" agent skill from https://github.com/simstudioai/sim/tree/main/.agents/skills/add-connector into .github/skills/add-connector/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-connector", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add simstudioai/sim --skill add-connector -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install simstudioai/sim add-connector --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/simstudioai/sim.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.agents/skills/add-connector .opencode/skills/add-connector && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "add-connector" agent skill from https://github.com/simstudioai/sim/tree/main/.agents/skills/add-connector into .opencode/skills/add-connector/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-connector", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
add-connectorAdd or update a Sim knowledge base connector for syncing documents from an external source, including auth mode, config fields, pagination, document mapping, tags, and registry wiring.
Add Connector is an agent skill from simstudioai/sim. Add or update a Sim knowledge base connector for syncing documents from an external source, including auth mode, config fields, pagination, document mapping, tags, and registry wiring. Use when working in apps/sim/connectors/{service}/ or adding a new external document source.
Its SKILL.md is about 7.8k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files (for example `agents/openai.yaml`).
It sits in Knowledge Management, covering Knowledge bases. The repository describes itself as: Sim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000+ builders. The licence is Apache-2.0.
4 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 546d4e7. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are typescript).
From the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
service.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Add Connector loads about 7.8k tokens when it runs. Until then it costs about 73 tokens; SKILL.md has 2,558 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from simstudioai/sim at commit 546d4e7, republished under its Apache-2.0 licence (© simstudioai). 2,558 words, ~7,794 tokens.
.claude/skills/add-connector/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.For Sim Search, use the live provider workflow in the federated Search developer guide. Its browser-safe provider catalog owns provider IDs, API origins, credential aliases, and account modes; its typed runtime registry requires both search and read handlers. ConnectorMeta remains the owner of logos and setup fields. Member mode has no admin resource filters. Service mode requires independent live source verification; resource pickers use shared selectors with canonical manual-input pairs, and plain inputs are fine where no picker applies. Do not implement a Search source by adding a crawler, embeddings, or a scheduled ACL build.
The ingestion instructions below apply to ordinary knowledge-base connectors. Sim Search always uses the live backend, so setting search: true in metadata does not implement federated search; a provider that serves both needs the live handlers and this KB ingestion path.
You are an expert at adding knowledge base connectors to Sim. A connector syncs documents from an external source (Confluence, Google Drive, Notion, etc.) into a knowledge base.
When the user asks you to create a connector:
meta.ts (declarative metadata) plus the runtime module that spreads itIf the service docs do not clearly show the document list response, document fetch response, pagination shape, or metadata fields, you MUST tell the user instead of guessing.
ExternalDocument content structure from partial docsIf the source schema is unknown, do one of these instead:
Each connector is split into a client-safe metadata file and a server-only runtime file. This mirrors the XBlockMeta / BLOCK_META_REGISTRY split in apps/sim/blocks — client components (the knowledge UI) only need the metadata (icon, name, auth, config fields), so the runtime functions (which pull server-only helpers like input-validation.server → undici → node:net) must stay out of the client bundle.
Create files in apps/sim/connectors/{service}/:
connectors/{service}/
├── index.ts # Barrel export (re-exports the runtime connector)
├── meta.ts # ConnectorMeta — client-safe declarative metadata
└── {service}.ts # ConnectorConfig — spreads the meta + adds runtime functionsmeta.ts exports {service}ConnectorMeta: ConnectorMeta. It imports ONLY the icon from @/components/icons, import type { ConnectorMeta } from '@/connectors/types', and any pure-data constants. It must NEVER import server/runtime code.{service}.ts exports {service}Connector: ConnectorConfig. It imports the meta via import { {service}ConnectorMeta } from '@/connectors/{service}/meta', spreads it as the first property, and holds the runtime functions (which may import server-only helpers like @/lib/knowledge/documents/utils).Connectors use a discriminated union for auth config (ConnectorAuthConfig in connectors/types.ts):
See ConnectorAuthConfig in apps/sim/connectors/types.ts: oauth takes provider, requiredScopes, and optional service-account/admin scopes; apiKey takes label, placeholder, and optional.
For services with existing OAuth providers in apps/sim/lib/oauth/types.ts. The provider must match an OAuthService. The modal shows a credential picker and handles token refresh automatically.
For services that use API key / Bearer token auth. The modal shows a password input with the configured label and placeholder. The API key is encrypted at rest using AES-256-GCM and stored in a dedicated encryptedApiKey column on the connector record. The sync engine decrypts it automatically — connectors receive the raw access token in listDocuments, getDocument, and validateConfig.
The declarative metadata lives in meta.ts (ConnectorMeta). The runtime functions live in {service}.ts (ConnectorConfig), which spreads the meta as its first property.
meta.ts — client-safe metadataimport { {Service}Icon } from '@/components/icons'
import type { ConnectorMeta } from '@/connectors/types'
export const {service}ConnectorMeta: ConnectorMeta = {
id: '{service}',
name: '{Service}',
description: 'Sync documents from {Service} into your knowledge base',
version: '1.0.0',
icon: {Service}Icon,
auth: {
mode: 'oauth',
provider: '{service}', // Must match OAuthService in lib/oauth/types.ts
requiredScopes: ['read:...'],
},
configFields: [
// Rendered dynamically by the add-connector modal UI
// Supports 'short-input', 'dropdown', and 'selector' types — see ConfigField Types below
],
// Optional: tag definitions are metadata too — declare them here
// tagDefinitions: [ ... ],
}Keep meta.ts free of any server/runtime import. Only the icon, the ConnectorMeta type, and pure-data constants belong here.
{service}.ts — runtime (OAuth example)import { createLogger } from '@sim/logger'
import { fetchWithRetry } from '@/lib/knowledge/documents/secure-fetch.server'
import { {service}ConnectorMeta } from '@/connectors/{service}/meta'
import type { ConnectorConfig, ExternalDocument, ExternalDocumentList } from '@/connectors/types'
const logger = createLogger('{Service}Connector')
export const {service}Connector: ConnectorConfig = {
...{service}ConnectorMeta,
listDocuments: async (accessToken, sourceConfig, cursor) => {
// Return metadata stubs with contentDeferred: true (if per-doc content fetch needed)
// Or full documents with content (if list API returns content inline)
// Return { documents: ExternalDocument[], nextCursor?, hasMore }
},
getDocument: async (accessToken, sourceConfig, externalId) => {
// Fetch full content for a single document
// Return ExternalDocument with contentDeferred: false, or null
},
validateConfig: async (accessToken, sourceConfig) => {
// Return { valid: true } or { valid: false, error: 'message' }
},
// Optional: map source metadata to semantic tag keys (translated to slots by sync engine)
mapTags: (metadata) => {
// Return Record<string, unknown> with keys matching tagDefinitions[].id
},
}Only map fields in listDocuments, getDocument, validateConfig, and mapTags when the source payload shape is documented or live-verified. If not, tell the user and stop rather than guessing.
The split is identical — auth lives in meta.ts, runtime functions in {service}.ts.
// meta.ts
export const {service}ConnectorMeta: ConnectorMeta = {
id: '{service}',
name: '{Service}',
description: 'Sync documents from {Service} into your knowledge base',
version: '1.0.0',
icon: {Service}Icon,
auth: {
mode: 'apiKey',
label: 'API Key', // Shown above the input field
placeholder: 'Enter your {Service} API key', // Input placeholder
},
configFields: [ /* ... */ ],
}
// {service}.ts
export const {service}Connector: ConnectorConfig = {
...{service}ConnectorMeta,
listDocuments: async (accessToken, sourceConfig, cursor) => { /* ... */ },
getDocument: async (accessToken, sourceConfig, externalId) => { /* ... */ },
validateConfig: async (accessToken, sourceConfig) => { /* ... */ },
}The add-connector modal renders these automatically — no custom UI needed.
Three field types are supported: short-input, dropdown, and selector.
// Text input
{
id: 'domain',
title: 'Domain',
type: 'short-input',
placeholder: 'yoursite.example.com',
required: true,
}
// Dropdown (static options)
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
required: false,
options: [
{ label: 'Pages only', id: 'page' },
{ label: 'Blog posts only', id: 'blogpost' },
{ label: 'All content', id: 'all' },
],
}Use type: 'selector' for a key declared in the browser-safe selector manifest at
apps/sim/lib/selectors/manifest.ts. Remote selectors execute through the authorized
selectors.execute server operation and a server attachment; connectors never call providers or
resolve credentials in the browser. Apply the add-selector skill when the key does not exist.
Selectors are paired with a manual fallback input using the canonical pair pattern — a
selector field (basic mode) and a short-input field (advanced mode) linked by
canonicalParamId.
The user sees a toggle button (ArrowLeftRight) to switch between the selector dropdown and manual text input. On submit, the modal resolves each canonical pair to the active mode's value, keyed by canonicalParamId.
short-input (or dropdown) field with the same canonicalParamId and mode: 'advanced'.required must be set identically on both fields in a pair. If the selector is required, the manual input must also be required.canonicalParamId must match the key the connector expects in sourceConfig (e.g. baseId, channel, teamId). The advanced field's id should typically match canonicalParamId (connector config fields differ from block subBlocks here; the block rule that canonicalParamId must not equal the id of a subblock without a canonicalParamId does not apply).dependsOn references the selector field's id, not the canonicalParamId. The modal propagates dependency clearing across canonical siblings automatically — changing either field in a parent pair clears dependent children.configFields: [
// Base: selector (basic) + manual (advanced)
{
id: 'baseSelector',
title: 'Base',
type: 'selector',
selectorKey: 'airtable.bases', // Must exist in lib/selectors/manifest.ts
canonicalParamId: 'baseId',
mode: 'basic',
placeholder: 'Select a base',
required: true,
},
{
id: 'baseId',
title: 'Base ID',
type: 'short-input',
canonicalParamId: 'baseId',
mode: 'advanced',
placeholder: 'e.g. appXXXXXXXXXXXXXX',
required: true,
},
// Table: selector depends on base (basic) + manual (advanced)
{
id: 'tableSelector',
title: 'Table',
type: 'selector',
selectorKey: 'airtable.tables',
canonicalParamId: 'tableIdOrName',
mode: 'basic',
dependsOn: ['baseSelector'], // References the selector field ID
placeholder: 'Select a table',
required: true,
},
{
id: 'tableIdOrName',
title: 'Table Name or ID',
type: 'short-input',
canonicalParamId: 'tableIdOrName',
mode: 'advanced',
placeholder: 'e.g. Tasks',
required: true,
},
// Non-selector fields stay as-is
{ id: 'maxRecords', title: 'Max Records', type: 'short-input', ... },
]When a selector depends on a plain short-input field (no canonical pair), dependsOn references
that field's id directly. Exact references such as {{JIRA_DOMAIN}} remain unresolved in the
browser and are resolved only after workspace authorization on the server.
configFields: [
{
id: 'domain',
title: 'Jira Domain',
type: 'short-input',
placeholder: 'yoursite.atlassian.net',
required: true,
},
{
id: 'projectSelector',
title: 'Project',
type: 'selector',
selectorKey: 'jira.projects',
canonicalParamId: 'projectKey',
mode: 'basic',
dependsOn: ['domain'],
placeholder: 'Select a project',
required: true,
},
{
id: 'projectKey',
title: 'Project Key',
type: 'short-input',
canonicalParamId: 'projectKey',
mode: 'advanced',
placeholder: 'e.g. ENG, PROJ',
required: true,
},
]dependsOn maps to SelectorContextThe shared connector context builder projects only active dependencies. A canonical dependency uses
its active basic or advanced value under canonicalParamId; a non-canonical dependency uses its
field id. The resulting key must be a SelectorContextKey in
apps/sim/lib/selectors/types.ts and must be explicitly allowed by that selector's manifest entry.
The browser sends the connector's workspace scope, not the complete connector configuration.
Check apps/sim/lib/selectors/manifest.ts for the exhaustive selector keys. Common ones for
connectors:
| SelectorKey | Context Deps | Returns |
|---|---|---|
airtable.bases | credential | Base ID + name |
airtable.tables | credential, baseId | Table ID + name |
slack.channels | credential | Channel ID + name |
gmail.labels | credential | Label ID + name |
google.calendar | credential | Calendar ID + name |
linear.teams | credential | Team ID + name |
linear.projects | credential, teamId | Project ID + name |
jira.projects | credential, domain | Project key + name |
confluence.spaces | credential, domain | Space key + name |
notion.databases | credential | Database ID + name |
asana.workspaces | credential | Workspace GID + name |
microsoft.teams | credential | Team ID + name |
microsoft.channels | credential, teamId | Channel ID + name |
webflow.sites | credential | Site ID + name |
outlook.folders | credential | Folder ID + name |
Every document returned from listDocuments/getDocument must include:
{
externalId: string // Source-specific unique ID
title: string // Document title
content: string // Extracted plain text (or '' if contentDeferred)
contentDeferred?: boolean // true = content will be fetched via getDocument
mimeType: 'text/plain' // extracted text; for a format the KB pipeline parses (PDF, Office), set `sourceFile` instead
contentHash: string // Change-detection hash (metadata-based when content is deferred)
sourceUrl?: string // Link back to original (stored on document record)
metadata?: Record<string, unknown> // Source-specific data (fed to mapTags)
}All connectors that require per-document API calls to fetch content MUST use contentDeferred: true. This is the standard pattern — listDocuments returns lightweight metadata stubs, and content is fetched lazily by the sync engine via getDocument only for new/changed documents.
This pattern is critical for reliability: the sync engine processes documents in batches and enqueues each batch for processing immediately. If a sync times out, all previously-batched documents are already queued. Without deferral, content downloads during listing can exhaust the sync task's time budget before any documents are saved.
contentDeferred: truecontentDeferredDeferred-content connectors (contentDeferred: true) must use a metadata-based contentHash derivable from the list response alone, so the sync engine can detect changes without downloading content. Inline-content connectors may hash the content they already hold (computeContentHash).
Good metadata hash sources:
modifiedTime / lastModifiedDateTime — changes when file is editedcontent_hash)Format: {service}:{id}:{changeIndicator}
// Google Drive: modifiedTime changes on edit
contentHash: `gdrive:${file.id}:${file.modifiedTime ?? ''}`
// GitHub: blob SHA is a content-addressable hash
contentHash: `gitsha:${item.sha}`
// Dropbox: API provides content_hash
contentHash: `dropbox:${entry.id}:${entry.content_hash ?? entry.server_modified}`
// Confluence: version number increments on edit
contentHash: `confluence:${page.id}:${page.version.number}`Critical invariant: The contentHash MUST be identical whether produced by listDocuments (stub) or getDocument (full doc). Both should use the same stub function to guarantee this.
// 1. Create a stub function (sync, no API calls)
function fileToStub(file: ServiceFile): ExternalDocument {
return {
externalId: file.id,
title: file.name || 'Untitled',
content: '',
contentDeferred: true,
mimeType: 'text/plain',
sourceUrl: `https://service.com/file/${file.id}`,
contentHash: `service:${file.id}:${file.modifiedTime ?? ''}`,
metadata: { /* fields needed by mapTags */ },
}
}
// 2. listDocuments returns stubs (fast, metadata only)
listDocuments: async (accessToken, sourceConfig, cursor) => {
const response = await fetchWithRetry(listUrl, { ... })
const files = (await response.json()).files
const documents = files.map(fileToStub)
return { documents, nextCursor, hasMore }
}
// 3. getDocument fetches content and returns full doc with SAME contentHash
getDocument: async (accessToken, sourceConfig, externalId) => {
const metadata = await fetchWithRetry(metadataUrl, { ... })
const file = await metadata.json()
if (file.trashed) return null
try {
const content = await fetchContent(accessToken, file)
if (!content.trim()) return null
const stub = fileToStub(file)
return { ...stub, content, contentDeferred: false }
} catch (error) {
logger.warn(`Failed to fetch content for: ${file.name}`, { error })
return null
}
}connectors/google-drive/google-drive.ts — file download/export with modifiedTime hashconnectors/github/github.ts — git blob SHA hashconnectors/notion/notion.ts — blocks API with last_edited_time hashconnectors/confluence/confluence.ts — version number hashDeclare which tags the connector populates using semantic IDs. Shown in the add-connector modal as opt-out checkboxes.
On connector creation, slots are dynamically assigned via getNextAvailableSlot — connectors never hardcode slot names.
tagDefinitions: [
{ id: 'labels', displayName: 'Labels', fieldType: 'text' },
{ id: 'version', displayName: 'Version', fieldType: 'number' },
{ id: 'lastModified', displayName: 'Last Modified', fieldType: 'date' },
],Each entry has:
id: Semantic key matching a key returned by mapTags (e.g. 'labels', 'version')displayName: Human-readable name shown in the UI (e.g. "Labels", "Last Modified")fieldType: 'text' | 'number' | 'date' | 'boolean' — determines which slot pool to draw fromUsers can opt out of specific tags in the modal. Disabled IDs are stored in sourceConfig.disabledTagIds.
The assigned mapping (semantic id → slot) is stored in sourceConfig.tagSlotMapping.
@/connectors/utils HelpersReuse these instead of inlining the same logic:
htmlToPlainText(html) — strip HTML to plain text before indexing ExternalDocument.content. Never index raw HTML.computeContentHash(content) — stable content hash for change detection.parseTagDate(value) — parse to a valid Date or undefined (guards Invalid Date). Use in mapTags for date fields.joinTagArray(value) — validate an array and join to a comma-separated string, or undefined. Use in mapTags for array/label fields.parseMultiValue(value) — normalize a value into a string[].Maps source metadata to semantic tag keys. Required if tagDefinitions is set.
The sync engine calls this automatically and translates semantic keys to actual DB slots
using the tagSlotMapping stored on the connector.
Return keys must match the id values declared in tagDefinitions.
Use the @/connectors/utils helpers for the common transforms — don't hand-roll date/array validation:
import { joinTagArray, parseTagDate } from '@/connectors/utils'
mapTags: (metadata: Record<string, unknown>): Record<string, unknown> => {
const result: Record<string, unknown> = {}
// joinTagArray validates the array and joins to a comma-separated string (undefined if empty)
const labels = joinTagArray(metadata.labels)
if (labels) result.labels = labels
// Validate numbers — guard against NaN
if (metadata.version != null) {
const num = Number(metadata.version)
if (!Number.isNaN(num)) result.version = num
}
// parseTagDate returns a valid Date or undefined (guards against Invalid Date)
const lastModified = parseTagDate(metadata.lastModified)
if (lastModified) result.lastModified = lastModified
return result
}fetchWithRetryAll external API calls must use fetchWithRetry from @/lib/knowledge/documents/secure-fetch.server instead of raw fetch(). It does not validate the host (on a direct outbound route it calls plain fetch), so use secureFetchWithRetry for user-controlled hosts. This provides exponential backoff with retries on 429/502/503/504 errors. It returns a standard Response — all .ok, .json(), .text() checks work unchanged.
For validateConfig (user-facing, called on save), pass VALIDATE_RETRY_OPTIONS to cap wait time at ~7s. Background operations (listDocuments, getDocument) use the built-in defaults (5 retries within a 150s budget).
import { fetchWithRetry } from '@/lib/knowledge/documents/secure-fetch.server'
import { VALIDATE_RETRY_OPTIONS } from '@/lib/knowledge/documents/utils'
// Background sync — use defaults
const response = await fetchWithRetry(url, {
method: 'GET',
headers: { Authorization: `Bearer ${accessToken}` },
})
// validateConfig — tighter retry budget
const response = await fetchWithRetry(url, { ... }, VALIDATE_RETRY_OPTIONS)If ExternalDocument.sourceUrl is set, the sync engine stores it on the document record. Always construct the full URL (not a relative path).
syncContext.listingCapped (REQUIRED)If listDocuments can ever return less than the full source set on a non-incremental sync — a maxItems/maxDocuments-style cap, or a transient per-item error that drops a still-existing document from the listing — it MUST set syncContext.listingCapped = true when that happens.
The engine reconciles deletions only when the listing is marked safe (checkpoint.unsafe in lib/knowledge/connectors/listing-checkpoint.ts): syncContext.listingCapped, syncContext.listingTruncated, syncContext.reconciliationUnsafe, ExternalDocumentList.reconciliationSafe: false (required for offset/unstable pagination), or a non-null listingFailures marks it unsafe. Anything absent from a safe listing is tombstoned on that sync and hard-deleted when the next sync still does not see it — so a truncated listing without this flag eventually removes every real document beyond the cap.
if (hitLimit && syncContext) {
syncContext.listingCapped = true
}Rules:
The sync engine (lib/knowledge/connectors/sync-engine.ts) is connector-agnostic. It:
listDocuments with pagination until hasMore is falsecontentHash to detect new/changed/unchanged documentssourceUrl and calls mapTags on insert/update automaticallyencryptedApiKey columnYou never need to modify the sync engine when adding a connector.
The icon field on ConnectorConfig is used throughout the UI — in the connector list, the add-connector modal, and as the document icon for connector-sourced documents in the knowledge base table. The icon is read from CONNECTOR_META_REGISTRY[connectorType].icon (the client-safe registry) at runtime — no separate icon map to maintain.
If the service already has an icon in apps/sim/components/icons.tsx (from a tool integration), reuse it. Otherwise, ask the user to provide the SVG.
Register in BOTH registries, keeping the same alphabetical-by-id ordering in each.
apps/sim/connectors/registry.server.ts (server-only full registry; holds full connectors with runtime functions, imported by the sync engine and knowledge API routes):import { {service}Connector } from '@/connectors/{service}'
export const CONNECTOR_REGISTRY: ConnectorRegistry = {
// ... existing connectors ...
{service}: {service}Connector,
}apps/sim/connectors/registry.ts (imports each connector's meta.ts only, so client components can use it without pulling server-only code; the metadata counterpart to BLOCK_META_REGISTRY):import { {service}ConnectorMeta } from '@/connectors/{service}/meta'
export const CONNECTOR_META_REGISTRY: ConnectorMetaRegistry = {
// ... existing connector metas ...
{service}: {service}ConnectorMeta,
}registry.ts exports CONNECTOR_META_REGISTRY: ConnectorMetaRegistry plus the helpers getConnectorMeta(id) and getAllConnectorMeta(), importing each @/connectors/{service}/meta directly — never the runtime module. registry.server.ts exports CONNECTOR_REGISTRY: ConnectorRegistry.
apps/sim/connectors/google-drive/google-drive.ts — file download with metadata-based hash, orderBy for deterministic paginationapps/sim/connectors/notion/notion.ts — complex block content extraction deferred to getDocumentapps/sim/connectors/github/github.ts — blob SHA hash, tree listingapps/sim/connectors/airtable/airtable.ts — list API returns record fields inline; listDocuments and getDocument share recordToDocument, which hashes that contentapps/sim/connectors/confluence/confluence.ts — multiple config field types, mapTags, label fetchingapps/sim/connectors/fireflies/fireflies.ts — GraphQL API with Bearer token authconnectors/{service}/meta.ts with {service}ConnectorMeta: ConnectorMeta (icon, name, auth, configFields, tagDefinitions) — no server/runtime importsconnectors/{service}/{service}.ts with {service}Connector: ConnectorConfig spreading the meta + runtime functionsconnectors/{service}/index.ts barrel exportauth.provider matches an existing OAuthService in lib/oauth/types.tsauth.label and auth.placeholder set appropriatelytype: 'selector' field has a canonical pair (short-input or dropdown with same canonicalParamId and mode: 'advanced')required is identical on both fields in each canonical pairselectorKey exists in apps/sim/lib/selectors/manifest.tsdependsOn references selector field IDs (not canonicalParamId)SelectorContextKey allowed by the selector manifestvalidate-selector skilllistDocuments handles pagination; deferred-content connectors use metadata-based content hashessyncContext.listingCapped = true set whenever the listing is truncated (max-items cap or transient per-item error) — required to prevent the engine's deletion reconciliation from removing unseen documentscontentDeferred: true used if content requires per-doc API calls (file download, export, blocks fetch)contentHash is metadata-based for deferred-content connectors (inline-content ones may use computeContentHash) and identical between stub and getDocumentsourceUrl set on each ExternalDocument (full URL, not relative)metadata includes source-specific data for tag mappingtagDefinitions declared for each semantic key returned by mapTagsmapTags implemented if source has useful metadata (labels, dates, versions)validateConfig verifies the source is accessiblefetchWithRetry (not raw fetch)validateConfigcomponents/icons.tsx (or asked user to provide SVG)connectors/registry.server.tsconnectors/registry.ts (same alphabetical-by-id ordering as registry.server.ts)© simstudioai, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in .agents/skills/add-connector of simstudioai/sim.
Open the folder on GitHubat commit 546d4e7
Add Connector next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Add Connector this skillsimstudioai/sim | 30k | — | ~7.8k | Automated safety check: Pass | Apache-2.0 | |
| Ima Knowledge Basecountbot-ai/CountBot | 781 | — | ~506 | Automated safety check: Pass | MIT | |
| Wiki Viewerrohitg00/pro-workflow | 2.9k | — | ~1.1k | Automated safety check: Pass | None | |
| HypatiaMarchLiu/hypatia | 239 | — | ~7.9k | Automated safety check: Notes | MIT | |
| Psr Autoloading Knowledgedykyi-roman/awesome-claude-code | 103 | — | ~2.1k | Automated safety check: Pass | MIT | |
| Psr Overview Knowledgedykyi-roman/awesome-claude-code | 103 | — | ~2.7k | Automated safety check: Pass | MIT |
countbot-ai/CountBot
通过 IMA OpenAPI 处理知识库任务。支持知识库内容搜索、命中详情查看、条目浏览、列出知识库、上传文件、导入网页。用户提到知识库、资料库、上传到知识库、导入网页、搜知识库时使用。
rohitg00/pro-workflow
Render a self-contained HTML viewer for a pro-workflow wiki.
MarchLiu/hypatia
Interact with the Hypatia AI memory system using natural language.
dykyi-roman/awesome-claude-code
PSR-4 autoloading standard knowledge base for PHP 8.4 projects.
dykyi-roman/awesome-claude-code
PHP Standards Recommendations (PSR) overview knowledge base.
Tommy-yw/RunbookHermes
SiYuan Note API for searching, reading, creating, and managing blocks and documents in a self-hosted knowledge base via curl.
simstudioai/sim
Install, upgrade, and operate the Sim Helm chart on Kubernetes.
simstudioai/sim
Add a new table column type to Sim — registry entry, icon, storage shape, coercion, and the behavioral hooks the grid and API read.
simstudioai/sim
Add a code-defined table enrichment (registry entry) under apps/sim/enrichments/ backed by an ordered provider cascade, ensuring every provider tool it calls has hosted-key support.
simstudioai/sim
Add hosted API key support to a tool so Sim provides the key (metered and billed to the workspace) when a user has not brought their own.
simstudioai/sim
Add or upgrade a curated, immutable managed CLI for Sim Function sandboxes, including client-safe catalog metadata, a pinned server-only installation recipe, checksum and executable verification…
simstudioai/sim
Add or update a Sim dynamic selector using the shared manifest, server attachment, and selectors.execute path.
Categories
Add or update a Sim knowledge base connector for syncing documents from an external source, including auth mode, config fields, pagination, document mapping, tags, and registry wiring. Add Connector is an agent skill from simstudioai/sim. Add or update a Sim knowledge base connector for syncing documents from an external source, including auth mode, config fields, pagination, document mapping, tags, and registry wiring.
Add Connector fits situations like: working in apps/sim/connectors/{service}/; adding a new external document source.
Run `npx skills add simstudioai/sim --skill add-connector -a claude-code`. Or copy the skill folder (.agents/skills/add-connector in simstudioai/sim) into .claude/skills/add-connector in your project. Claude Code loads it when a task matches its description.
Run `npx skills add simstudioai/sim --skill add-connector -a codex`. Or copy the skill folder (.agents/skills/add-connector in simstudioai/sim) into .agents/skills/add-connector in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add simstudioai/sim --skill add-connector -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/add-connector, .gemini/skills/add-connector, .github/skills/add-connector and .opencode/skills/add-connector in your project.
SKILL.md names no scripts, command-line tools or credentials: Add Connector is instructions for the agent only.
SKILL.md names 1 domain. In commands or code: service.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Add Connector is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.8k tokens (SKILL.md is roughly 31k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Add Connector: Ima Knowledge Base (countbot-ai/CountBot, 781 stars), Wiki Viewer (rohitg00/pro-workflow, 2.9k stars), Hypatia (MarchLiu/hypatia, 239 stars) and Psr Autoloading Knowledge (dykyi-roman/awesome-claude-code, 103 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
simstudioai (a GitHub organization) maintains it in simstudioai/sim, which has 29,785 GitHub stars. The repository holds 40 skills in this directory. The repository was last updated on October 7, 2026.
Source: simstudioai/sim on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.