Mirror the contents of a Feishu knowledge base to a local Markdown knowledge base. The default direction is Feishu → local/Git/Obsidian/VitePress; this skill does not write to Feishu by default, and two-way synchronization must first establish document ownership, conflict strategy, and write permissions.
Customer-readable description
What this skill can do
| Customer wants | Skill will do | Customer can see |
|---|---|---|
| Synchronize a Feishu knowledge base to local | Traverse knowledge space nodes, read readable documents, and generate Markdown | Local mirror directory, directory hierarchy, source links, synchronization manifest |
| Exclude a specified knowledge base subtree | Skip the root node and all descendants by stable node ID or exact title | Do not read the body, tables, images, or attachments of that subtree, and do not add it to the sidebar or retry queue |
| Back up to Git | Place the generated content into the customer-specified Git repository and check for differences | commit/push receipts, file changes, and failure list |
| View with Obsidian | Save rules, mirrors, and local supplements in a separate vault | A vault that can be opened directly with Obsidian |
| Display with VitePress | Generate the site sidebar and build a static site | Local development service or build artifacts |
| Check table/bitable export capabilities | Parse the real resource type, permissions, and available export formats | Read-only probe results; no Excel file will be generated by default |
| Synchronize standalone or embedded Sheets | Read Sheets within the scope explicitly authorized by the private configuration; can automatically cover all standalone Sheet nodes, and embedded Sheets are identified from the document XML before reading | Bounded Markdown table snapshots; authorized Sheet nodes are no longer written as body placeholders |
| Preserve Sheet formulas, styles, comments, and chart information | Save metadata snapshots of cells, layout, charts, and floating images for the selected range | Local fidelity JSON alongside the Markdown table; not disguised as an editable workbook |
| Initialize complete Sheets and reports | After confirmation, export the entire Sheet workbook in batches | .xlsx Fidelity copy, preserving formulas, styles, comments, charts, pivots, and cell images |
| Mirror a specified bitable | Read the fields, limited records, and optional views of the specified Base table, and generate Markdown and snapshots | Capped table content, schema/record snapshots; no Base data is read by default |
| Initialize bitables and multi-person reports | After confirmation, export the complete Base in batches; for selected tables, dashboard and report block metadata can also be read | .base Fidelity copy; dashboard snapshots are written to JSON, not disguised as interactive web pages |
| Localize resources and navigation | After confirmation, download document images/attachments, or download attachments of selected bitable records, and change internal document links and subpage lists to local navigation | Local resources, relative links, and optional subpage navigation |
| Initialize the knowledge base files | After confirmation, download the original binary of Wiki file nodes in batches |
Local links; ZIP, DMG, EXE, etc. are only saved, never executed, mounted, or extracted |
| View sync changes | After configuration, generate a local change ledger and restricted diffs for additions, modifications, moves, and remote deletions | Statistics, change list, and diff details for this sync |
| Plan bidirectional sync | Distinguish read-only mirrors, managed documents, and local supplements | Conflict/permission risk explanation, never automatically overwrite Feishu |
How customers use it
- Confirm that
soia-cwork-feishu-clihas completed Feishu app credential login, and that the bot can read the target knowledge space. - Fill in the knowledge space ID, output directory, and source URL template in the local private configuration; do not commit the App Secret, token, or enterprise private paths to a public skill repository.
- On first use, run dry-run first to verify the space, node count, and target directory.
- First use an isolated single-node pilot to verify tables, resources, and style snapshots;
--pilot-node-tokenwrites only the explicitly selected node to a separate pilot directory, and will not fill an empty directory with placeholder files for other nodes. - Run the mirror sync. By default, it writes only local files and sync metadata, does not modify Feishu content, and does not delete local historical files.
- To exclude an entire directory tree, set
sync.exclude_subtrees.enabled: truein the private configuration, and inrootsfill in the stablenode_tokenfirst; when cleaning up existing Markdown, full exports, and downloaded attachments for the first time, explicitly run--rebuild-treeonce; the local supplement directory is never affected. - After sync writing, it automatically verifies the manifest, file existence, frontmatter, failure placeholders, sidebar coverage, excluded subtree remnants, resource references, unarchived embedded Sheets, unarchived Base embedded in Sheets,
all_docxhistorical documents whose XML semantic scan has not yet been completed in mode, andall_nodesstandalone Sheets that have still not generated real tables in mode; whenfailed/staleor semantic gaps are found, it returns a non-zero result, and blank placeholders or partial content must not be treated as complete success. - To check table exports, first do
drive +inspect/help/schema probing; capability probing does not equal authorized export. - Only after the customer explicitly confirms the export scope, format, file count, and local directory, call
drive +exportordrive +export-download。 - To mirror Sheets, first specify the scope in the private configuration: use
sync.sheets.enabled: trueandall_nodes: truefor all standalone Sheets to automatically discover each grid subtable, or inselectionsspecify item by itemnode_token, stablesheet_id, and bounded A1range; both methods must set row, column, cell, and returned character limits. When the response reachesmax_chars, continue reading from the next row according toactual_range; truncation must not be treated as failure of the entire table. Mixed workbooks also require explicitly settinginclude_bitable_tabs: trueand the record limit, and then routing Base subtables to multidimensional table reads. After enablingsync.embedded_sheets.enabledfor Sheets embedded in documents, you must also chooseall_docx: trueornode_tokens. Enable on demandsync.sheets.preservePreserve metadata such as formulas, styles, comments, layouts, and charts. When not enabled, semantic gaps must be displayed and validated, and must not be silently deleted<sheet>or treat an independent Sheet placeholder as a complete archive. - To mirror a Bitable, specify item by item
sync.bitables.selectionsofnode_token、table_idandmax_records, then enablesync.bitables.enabledor pass--sync-bitables; attachment binaries also require separately enablingdownload_attachments, and dashboard/report metadata requiresinclude_dashboards。 - Only after the user explicitly confirms the source, format, file count, output directory, and Git strategy can a full initialization be performed: Sheet settings
sync.sheets.workbook_exports.enabled=trueandall_nodes=true, Base settingssync.bitables.base_exports.enabled=trueandall_nodes=true, Wiki file settingssync.files.downloads.enabled=trueandall_nodes=true. Each item must be configured withbatch_size, and repeated until deferred reaches zero. - If offline resources, local jumps between documents, subpage navigation, or a change ledger are needed, first enable item by item in the private configuration
download_assets、localize_internal_links、render_sub_page_navigation、change_ledger; they are disabled by default for compatibility with existing mirrors. - Before batch initialization, first pilot with one representative scope to confirm the resource count, failure categories, and local rendering; after synchronization is complete, then run Git diff, site build, and necessary manual spot checks.
Recommended commands:
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --dry-run
# 试点只写所选节点到独立目录;仍会核验它是否属于当前知识库
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --output-dir <pilot-output-dir> \
--pilot-node-token <node_token> --incremental
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental
# 没有事件订阅时,按 wiki +node-get 的远端更新时间判断正文是否变化
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental --probe-remote-metadata
# 复用已完整正文,优先补独立 Sheet stub、未扫描的内嵌 Sheet 文档,再退避重试失败项
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --retry-failed
# 事件适配器已经拿到变动 ID 时,只拉对应节点;可重复传入多个 ID
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--changed-node-token <node_token> --changed-obj-token <obj_token>
# 只修复指定节点的本地格式,复用 manifest 中的其他文档,不重试历史失败项
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--rebuild-tree --rebuild-tree-only --only-node-token <node_token> --skip-assets
# 官方 webhook/长连接适配器写入 JSON/NDJSON 后,按事件目标增量拉取
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--event-file <events.ndjson>
# 仅在确认历史生成目录曾经扁平化时执行一次结构迁移
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --retry-failed --rebuild-tree
# 大型知识库遇到限流时分批补偿;重复执行直到 --validate-only 通过
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> \
--retry-failed --retry-batch-size 100 --skip-assets
# 如果只需要修复本地目录层级、暂时不请求飞书
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --rebuild-tree --rebuild-tree-only
# 从飞书刷新最新目录层级和兄弟节点顺序,但复用现有本地正文
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--rebuild-tree --refresh-tree-only
# 应用私有配置中的整棵子树排除,并清理该子树既有 Markdown、完整导出和已下载附件
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--rebuild-tree --refresh-tree-only --skip-assets
# 下载图片到本地镜像并把正文中的远程 URL 改成相对路径
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--download-assets
# 将私有配置中明确选择的 Sheet 范围渲染为 Markdown 表格
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--sync-sheets
# 将已授权文档中的嵌入式 Sheet 追加为有界 Markdown 表格快照
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--sync-embedded-sheets
# 将私有配置中明确选择的多维表格镜像为 Markdown 与 JSON 快照
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--sync-bitables
# 完整初始化仍使用同一同步命令;私有配置中 workbook_exports/base_exports/files.downloads
# 的 enabled 与 all_nodes 必须都为 true,并按 batch_size 分批重复执行
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --incremental \
--sync-sheets --sync-bitables
# 只校验最近一次同步生成的本地镜像,不访问飞书
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --validate-only
Dependencies and installation
Install the entire domain (Claude Code and Codex share the same domain plugin):
claude plugin marketplace add soia-team/soia-open-skills
claude plugin install soia-cwork-office@soia
Install only this one skill:
npx skills add soia-team/soia-open-cwork-office-skills -g -a '*' -s soia-cwork-feishu-doc-git-sync -y
WorkBuddy is loaded as role-based experts rather than a plugin,npx skills add -a '*' cannot cover it, so it needs to be installed separately, see docs/install/workbuddy.md。
Three working modes
mirror: default mode. The knowledge base is the source, and the locally generated10_knowledge-base/should not be edited manually.local: maintain only the local20_本地补录/, which will not be overwritten by mirror synchronization and will not be automatically uploaded to Feishu.managed: intended in the future for explicitly designated bidirectional hosted documents. Write permissions, conflict rules, and publishing actions must be confirmed document by document; the current script only provides a read-only mirror foundation and does not disguise it as already-completed bidirectional synchronization.
ID incremental synchronization and event push
node_tokenis the synchronization primary key,obj_tokenis the object key for body reading and event mapping; title changes, moves, and duplicate names should not change these two IDs.sync.exclude_subtreesmust be explicitly enabled in the private configuration; prefer excluding by stablenode_token,exact_titlewill exclude all root nodes with exactly the same name. The synchronizer identifies it only in the parent list of the root node, no longer enumerates descendants, and simultaneously excludes the entire subtree from the body, Sheet, Base, images, attachments, retries, active manifest, and sidebar.- Generated content of excluded subtrees will not be silently deleted by ordinary incremental synchronization; after the user explicitly requests cleanup, run
--rebuild-tree, the synchronizer records known members asexcludedrather than falsely reporting them as remote deletions, and cleans up and validates the corresponding Markdown, directories,_exports/complete export and_assets/downloaded attachments are all left with no residue;20_本地补录/is never deleted. --only-node-tokenis a single-document repair switch; when used with--rebuild-tree-only, it only locates nodes from the existing manifest, does not re-traverse the Feishu tree, and will not retry other nodes just because of their historical failures.- The first synchronization establishes a complete baseline, recording
obj_edit_time/remote_updated_atanddocs +fetchreturned byrevision_id。 - Subsequent
--incrementalwill still first followparent_node_tokenRebuild the tree, but only read the document bodies that are newly added, failed, event-matched, or whose remote edit time has changed; for unchanged nodes, reuse the local Markdown. - The sibling node order directly preserves the
wiki +node-listorder returned by Feishu, without re-sorting by title; therefore the VitePress/Obsidian directory should match the manual ordering of the Feishu knowledge base. - When there is no event target, by default use
wiki +node-getfor metadata probing; this generates more lightweight metadata requests, but avoids repeatedly downloading document bodies. For large spaces, you can switch to the official event subscription and pass--event-file。 - Events only provide a hint of "which object may have changed" and cannot replace Wiki tree reconciliation; creation, deletion, title changes, and unrecognized events still require re-reconciling the node tree.
- Official event subscription, permissions, and
drive.file.*coverage boundaries are described in references/events.yml. Currentlylark-cli event listdoes not expose cloud documentdrive.file.*events, so this script does not claim to already be resident listening within the CLI; an external long-connection/webhook adapter can hand JSON/NDJSON to--event-file。
Dependencies and installation
Install this skill and its same-repository dependencies:
claude plugin marketplace add soia-team/soia-open-skills
claude plugin install soia-cwork-office@soia
When you only want this one skill, you can use the npx route. Note that the skill will land in the shared source of truth ~/.agents/skills; if the plugin is also installed, the same skill will appear in two indexes and each will drift, so it is recommended to choose one of the two:
npx skills add soia-team/soia-open-cwork-office-skills -g -a '*' -s soia-cwork-feishu-doc-git-sync -y
| Dependency | Type | Installation / configuration | What to do if missing |
|---|---|---|---|
soia-cwork-feishu-cli |
Hard dependency | Install and configure the official Feishu lark-cli app credentials |
Stop, and first complete bot login and permission checks |
lark-cli |
Hard dependency | See the installation instructions for soia-cwork-feishu-cli installation instructions |
Stop and report the installation command |
| Python 3.10+ | Hard dependency | Use the system Python or the project Python | Stop |
| PyYAML | Hard dependency | python3 -m pip install pyyaml |
Stop and report the missing dependency |
| Git | Optional enhancement | Install Git | Can still generate a local mirror, but cannot commit/push |
| VitePress | Optional enhancement | Provided by the target document repository | Can still sync to Obsidian |
| Obsidian | Optional enhancement | Installed on the user's local machine | Can still generate ordinary Markdown |
Default location for private configuration:
~/.config/soia-skills/soia-cwork-feishu-doc-git-sync/config.yml
You can also use SOIA_CWORK_FEISHU_DOC_GIT_SYNC_CONFIG_FILE to specify the configuration file. Reference assets/config.example.yml。
Minimal configuration example:
version: 1
provider:
cli: lark-cli
profile: <configured-cli-profile>
brand: feishu
identity: bot
space:
id: <wiki-space-id>
source_url_template: https://<tenant>.feishu.cn/wiki/{node_token}
paths:
output_dir: <git-repository>/docs/feishu-knowledge
generated_dir: 10_<knowledge-base-name>
sync:
mode: mirror
prune: false
Permission recommendation: for the first round, request only read-only permissions for the knowledge base, documents, and Sheets. Image and attachment downloads are optional enhancements; when cloud drive/export permissions are involved, request them separately; bidirectional write permission should never be a default permission.
Sync rules
- Only use the app identity of
--as botto read; by default, a user identity token is not required. - Traverse the knowledge space via node tokens, and use document tokens to read
docxcontent. paths.generated_dirmust use the target knowledge base's real name or stable English name; for example10_后端技术支持库, do not nest another generalizedfeishu-knowledgedirectory inside it.- If the existing output directory contains only one
10_*generated directory and the configuration does not fill inpaths.generated_dir, the skill will reuse it; if multiple candidate directories are found, it will stop and require explicit configuration first, to avoid automatically creating duplicate directories. - Each generated Markdown writes the source URL, space ID, node token, object token, parent node, and content hash.
- Use
sync-state.jsonto keep the mapping from node token to local path; when titles change, keep paths as stable as possible, and the tree position is recalculated from the latestparent_node_token. manifest.jsonandsync-state.jsonrecordobj_edit_time、remote_updated_at、revision_id, used for incremental selection and auditing.- A single local path component is at most 48 characters; overly long titles keep the full title in frontmatter/sidebar, and a short node ID suffix is appended to the folder or file name, to avoid overly long paths in Obsidian, macOS, and VitePress/Rollup.
- The synchronizer converts custom
figure/source/grid/callout, media tokens, and XML fragments exported from Feishu into a form that can be parsed by Markdown/VitePress; this only changes local rendering and does not write back to Feishu. - Feishu document references are resolved into clickable Feishu Wiki links with
node_tokentaking priority andobj_tokenas a fallback; user references are kept as@显示名. Static Markdown does not copy Feishu's hover cards and member avatar interactions, but it no longer incorrectly degrades to code style. - A Feishu node with child nodes must generate a directory of the same name, and place the body in an index Markdown of the same name inside the directory:
父目录/节点名/节点名.md; only leaf nodes directly generate节点名.md. Do not generate a sibling "file of the same name + folder of the same name". - If Feishu itself has same-name leaves and expandable nodes, parent-child same names, or duplicate expandable nodes at the same level, a stable short node ID suffix will be appended to the directory/file; this is to avoid same-level conflicts in the local file system, and the manifest still uses
node_tokento distinguish real nodes. --retry-failedwill reuse existing complete bodies and add failed nodes, standalone Sheet stubs, and historical documents that have not completed XML semantic scanning to the compensation queue; this is suitable for continuing to fill in gaps after encountering Feishu API rate limiting or enabling a new Sheet policy.- If a historical mirror retains an old placeholder such as "the current synchronizer only reads document bodies", even if the Feishu node later changes from Sheet to Docx and the manifest status is still
ok, acceptance will still be judged as failed, and it will be re-fetched with the highest priority in the--retry-failedqueue; an old placeholder after a type drift cannot be treated as a successful body. --retry-batch-size Nis used only together with--retry-failed, compensating at most N failed or semantic-gap nodes each time; large spaces should be run repeatedly, with--validate-only'sfailed_records=0、unmirrored_sheet_nodes=0、unscanned_embedded_sheet_documents=0as the termination condition.--retry-faileduses the previous manifest as the node list and no longer performs metadata probing on the full document set first; when the full Sheet policy is enabled, it prioritizes filling standalone Sheet stubs and historical Docx files missing semantic scan markers, then retries ordinary failed items, and continues to be protected by global throttling. For large spaces, run repeatedly together with--retry-batch-sizeuntil bothunmirrored_sheet_nodesandunscanned_embedded_sheet_documentsare 0.- Only one synchronization process is allowed at a time for the same output directory; if the previous round is still exiting or the user starts it repeatedly, subsequent processes will stop and report, and will not concurrently overwrite the manifest.
- Global request throttling and exponential backoff take effect across synchronization workers;
sync.min_request_interval_secondsdefaults to 0.5 seconds, to avoid large knowledge bases concurrently triggering99991400rate limiting. - When body reading fails but an old local body already exists, the node is marked as
staleand the old body is retained; the next incremental sync will continue to retry, and validation will not treat it as successful. - Local acceptance runs at the end of every non-dry-run sync;
--validate-onlycan independently recheck the most recent result. On acceptance failure, the exit code is 2, and a machine-readable summary is retained in thevalidationnode of the manifest. prune: false, local files corresponding to nodes that have disappeared are not deleted; the nodes are marked as deleted in the manifest, avoiding data loss caused by a single permission or network anomaly.20_本地补录/and90_同步元数据/will not be overwritten by knowledge base sync.--rebuild-treeonly processespaths.generated_dirold flat files generated by the synchronizer and known resources of excluded nodes, without touching20_本地补录/。--rebuild-tree-onlyonly reuses the existing manifest and generated files to perform directory migration, without initiating Feishu body requests; if resource localization is also enabled, it may still read documents containing resources solely to refresh expired media URLs.--refresh-tree-onlywill re-read the Feishu node tree and sibling order, rebuilding the local directory and sidebar according to the latestparent_node_token, but reusing the existing local body; when resource localization is enabled, it will additionally refresh documents that still contain unlocalized resources; it must be used together with--rebuild-tree.manifest.json/sync-state.jsonoftree_order: feishu_node_listindicates that the directory order comes from the Feishu node list, not title sorting.- Images retain remote URLs by default; after setting
sync.download_assets: trueor passing--download-assets, the skill will download remote images in the body and<source token="...">media blocks topaths.generated_dir/_assets/, and rewrite Markdown/HTML references to relative paths. Downloaded Feishu attachment cards must further be rewritten as standard Markdown local links; if the link is wrapped by a separate<p>, that HTML container must also be removed, ensuring both Obsidian and VitePress can click to open it. Cards that have not been downloaded still retain the original remote reference and report failure. - When downloading resources, deduplicate by Feishu media token first; the same attachment or image, even with different short-term signed URLs, keeps only one local resource. Resources without a token are still addressed by URL content.
sync.localize_internal_links: true, synced Wiki/document references will be changed to relative local Markdown links;sync.render_sub_page_navigation: true, the Feishu-exported<sub-page-list>will be changed to local Markdown subpage navigation. Both are disabled by default and do not affect existing external link behavior.sync.change_ledger: true, a change ledger of additions, modifications, moves, and remote deletions is generated per run under the sync metadata; modification entries retain only the diff limited bychange_ledger_max_diff_lines, without copying the full document text, and without changing the generated mirror or local supplementary directories.sheetgenerates only a metadata stub by default. After the user explicitly authorizes a bounded snapshot of the entire library, setsync.sheets.enabled: trueandall_nodes: true, and the synchronizer will usesheets +workbook-infoto automatically discover the grid subtable of each independent Sheet, then callmax_rows、max_columns、max_cells、max_charsup to thesheets +csv-getlimit; when the response is truncated, continue reading row by row according toactual_range. You can also continue to useselectionsto precisely declare a single range. Base subtables in a mixed workbook are read in a bounded manner according toinclude_bitable_tabs: trueonly whenmax_bitable_records; otherwise the node fails and cannot be silently skipped; if Base permissions are insufficient, the ordinary Sheets already read are still written to disk, but the node remainsfailedand an explicit gap marker is written.all_nodesacceptance will reject anymetadata_stub, missing a Sheet node with real table markup or an unarchived Base subtable. See the full contract at references/sheet-mirroring.yml。- Feishu Markdown may return only
<sheet>references without returning any cells. The synchronizer must not delete that reference and still report complete success: when the drill-down is not authorized, keep the visible gap and fail acceptance; only whensync.embedded_sheets.enabledandall_docx: trueornode_tokensis explicitly chosen, read the document XML and generate an "embedded table" snapshot within the configured limit. Enabling it for the first time will backfill legacy documents; the snapshot will not masquerade as the interactive original table. See the full scope contract at references/sheet-mirroring.yml。 sync.sheets.preserve.enabled: truewill save cell values, formulas, styles, comments, as well as a JSON snapshot of worksheet layout, charts, and floating image metadata in the same selected scope; pivot, filter, conditional formatting, and sparkline metadata can additionally be enabled. It does not disguise charts or images as native Markdown. The complete workbook requires explicitly setting bothsync.sheets.workbook_exports.enabled: trueandall_nodes: trueafter explicit user approval; the synchronizer will usedrive +exportto generate.xlsxin batches and link them in the Sheet index.- When an asynchronous Sheet export does not complete in the first polling round, the export task ticket is saved in a private snapshot in the generation directory; subsequent batches poll only the same task, and once it is ready use
drive +export-downloadto download, without repeatedly creating the same workbook export task.manifest.stats.sheet_workbooks_pendingindicates the quantity still being processed on the Feishu side. - Image/attachment localization is an explicitly opt-in local data download; you must not download all assets just because the user only asked to "check the images". In persistent configuration,
sync.download_assets: truecan only be regarded as the user's prior explicit authorization for that resource scope, and must not be extended to authorize table or Base export. If you first perform the structural initialization of Sheet/Base/file and use--skip-assetsto avoid output lock conflicts, you must run the resource localization batch separately after initialization ends;--skip-assetswill not mark images or attachments as downloaded. bitableand unauthorizedsheetgenerate only metadata stubs by default and do not read the data in the table; a standalone Sheet withsync.sheets.all_nodesenabled is not allowed to keep a stub. After settingsync.bitables.enabled: true, you must declarenode_token、table_idandmax_recordsitem by item; the synchronizer generates Markdown and JSON snapshots withbase +field-list、base +record-listand the optionalbase +view-list/base +dashboard-list/base +dashboard-block-list.sync.bitables.download_attachments: trueis an independent binary download authorization, usesbase +record-download-attachmentand processes only attachments of read records in the selected tables. A complete Base export still requires explicitly setting bothsync.bitables.base_exports.enabled: trueandall_nodes: trueafter explicit user approval; the synchronizer usesdrive +exportin batches..base。- Wiki
filenodes generate only metadata stubs by default; only after the user explicitly confirms full initialization and bothsync.files.downloads.enabled: trueandall_nodes: trueare set will the synchronizer invokedrive +downloadin batches. Files are saved as raw binaries; formats such as ZIP, DMG, and EXE will not be executed, mounted, decompressed, or parsed. - Feishu image URLs may expire quickly. When localization is enabled and
--refresh-asset-urlsis used, the URLs of associated documents are refreshed before downloading, rather than unconditionally re-fetching all body content; after downloading, they are uniformly written as local HTMLaltwith escaped<img>, and PNG/JPEG/GIF/WebP/SVG are identified by file binary, correcting erroneous cached suffixes. - Event targets,
--changed-*-token、--only-node-tokenor isolated pilots process only the resources of documents reread in the current round, and will not re-add the entire library's historical resources to the download queue; full resource backfill, by contrast, passes no target and followsasset_batch_sizeExecute in batches. - Image downloads use URL content-addressed filenames, so repeated syncs reuse existing resources; both ordinary Markdown URLs and
(<https://…>)angle-bracket URLs enter the same download queue. You can useasset_workers、asset_timeout_seconds、asset_download_attempts、max_asset_bytesto limit concurrency, timeouts, media retries, and per-file size; the default per-resource limit is 50 MiB, which can be lowered or raised in private configuration according to the confirmed download scope. Attachments carrying Feishu media tokens are retried within the limit on transient or unclassified CLI failures; explicit insufficient permissions, resource-not-found, orasset_too_largewill not be retried blindly. For large existing mirrors, set a positive integerasset_batch_size: each time only that many resources not yet written to disk are requested, and already-downloaded resources are still rewritten to local references;asset_refreshed_batch_sizecan limit the number of new links downloaded for the same batch of refreshed documents. Run repeatedly and use--validate-onlyto confirm completion. The manifest reports this round'sassets_deferred. Failed downloads keep only the original URL and are reported in theassets_failedcount in the manifest; authentication URLs are not written to logs or manifests. <source token="...">or<img token="...">without a URL will call the officialdocs +media-download; when a remote URL cannot be read directly, you need to adddocs:document.media:downloadordrive:file:downloadaccording to the permission list, and enable local resource download in the private configuration. Without download permission, you must not assume local resources are complete.
Security Rules
- Do not write App Secret, access token, or cookie into public skills, Obsidian vaults, Markdown, Git commits, terminal output, or final replies.
- Terminal logs, progress receipts, and final replies must not output local absolute paths, specific local filenames, operating system usernames, usernames, passwords, App Secret, access token, or private download URLs; use redacted placeholders uniformly, and report only status, counts, and error categories. See references/output-redaction.yml。
- Do not call Feishu create, update, or delete APIs by default.
drive +export、drive +export-download、docs +media-downloadand attachment downloads are both data export/download actions; when the user says "see if it can be exported," only do inspect, help, schema, or dry-run, and do not directly create local files.- Mirroring Sheet and Base values both involve writing sensitive data to disk: it may only be enabled after the user explicitly confirms the need to sync the selected range/table to the local workspace and writes a bounded
sync.sheets.selectionsor restrictedsync.bitables.selectionsinto the private configuration; that confirmation does not authorize any workbook export, download of unselected attachments, or automatic Git commit. - Before a real export, you must clearly acknowledge the source, type, format, estimated file count, output directory, and Git tracking policy; "checking capabilities" does not equal "authorizing export."
- Exported files are placed in a temporary directory or a directory explicitly specified by the user by default; only fully confirmed complete mirror initialization by the user may write
_exports/or_assets/under the generated directory, and must not automatically commit to Git, automatically push to remote, or write back to Feishu. - Personal cloud drives or private resources that bot has no permission to access must be reported as invisible, and must not switch to user OAuth to read on its behalf.
- Do not overwrite local supplementary records, delete historical files, or push to remote Git by default; these are write/publish actions that require explicit confirmation.
- Before execution, check the target repository, current branch, and remote address; if they do not match expectations, stop and report.
Private Information and Intermediate Data
- Write excluded node IDs, knowledge space IDs, credential aliases, and local output locations only into skill private configuration, and do not commit them to public repositories.
- Store the active/excluded node list, sync status, sidebar, and change ledger in the sync metadata directory of the user-specified mirror; exclusion records are used only to prove the scope not fetched and do not contain body content.
- Hand temporary downloads and API intermediate results to the system temporary directory; write only user-authorized Markdown, table snapshots, and resource files to the delivery directory.
Logs and Completion Receipt
The terminal and final reply must report at least:
started: space, identity, configuration source, and target directory (do not print secrets).processed: number of nodes, documents, skips, and failures.created/updated: number of Markdown, manifest, and sidebar files generated or updated.skipped/failed: failed nodes, reasons, and whether they are retryable.verification: Git diff, VitePress build, sampled documents, and source link check results.next_step: whether additional permissions are needed, Git push needs confirmation, or two-way sync needs planning.- For file changes, report only counts and categories, not local paths or file names; for identity, report only
bot identity/user identityand other non-sensitive statuses.
Resources
- Layered permissions and permission requests:references/permissions.yml
- Feishu CLI commands and permission request process: same repository
soia-cwork-feishu-cli/references/cli-workflows.mdandsoia-cwork-feishu-cli/references/permissions.md。 - Event subscription and incremental targets:references/events.yml
- Sync strategy:references/sync-policy.yml
- Document format conversion:references/block-mapping.yml
- Sheet range mirroring:references/sheet-mirroring.yml
- Rich resource mirroring capabilities and boundaries:references/rich-resource-mirroring.yml
- Table/Bitable export security policy:references/export-policy.yml
- Log and reply redaction policy:references/output-redaction.yml
- Git and VitePress integration:references/git-vitepress.yml
- Private configuration templates:assets/config.example.yml
Validation
python3 scripts/sync_feishu_wiki.py --help
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --dry-run
python3 scripts/sync_feishu_wiki.py --config <private-config.yml> --validate-only
git diff --check
Forward test
Before a real sync, run a dry-run or a small authorized representative scope and verify the tree, stable node-ID mapping, ordering, incremental selection, asset references, and failure receipt. After every write, require the built-in validation gate to pass; a zero exit code alone is not evidence that the mirror is complete.