diff --git a/.github/workflows/pages-preview.yml b/.github/workflows/pages-preview.yml
index d5342b2..f2b415a 100644
--- a/.github/workflows/pages-preview.yml
+++ b/.github/workflows/pages-preview.yml
@@ -78,12 +78,28 @@ jobs:
permissions:
contents: read
deployments: write
+ statuses: write
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: preview
url: ${{ steps.pages.outputs.pages-deployment-alias-url }}
steps:
+ - name: Mark the pull-request preview pending
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ PREVIEW_HEAD_SHA: ${{ needs.identity.outputs.head_sha }}
+ with:
+ script: |
+ await github.rest.repos.createCommitStatus({
+ ...context.repo,
+ sha: process.env.PREVIEW_HEAD_SHA,
+ context: 'docs preview',
+ state: 'pending',
+ description: '正在构建并部署预览',
+ target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
+ })
+
- name: Checkout the trusted preview controller
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -142,3 +158,24 @@ jobs:
CLOUDFLARE_PAGES_DEPLOYMENT_ID: ${{ steps.pages.outputs.pages-deployment-id }}
CLOUDFLARE_PAGES_DEPLOYMENT_URL: ${{ steps.pages.outputs.pages-deployment-alias-url }}
INKCRE_PAGES_SMOKE_MODE: preview
+
+ - name: Report the preview result on the pull-request commit
+ if: ${{ always() }}
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ PREVIEW_HEAD_SHA: ${{ needs.identity.outputs.head_sha }}
+ PREVIEW_RESULT: ${{ job.status }}
+ PREVIEW_URL: ${{ steps.pages.outputs.pages-deployment-alias-url }}
+ with:
+ script: |
+ const succeeded = process.env.PREVIEW_RESULT === 'success'
+ await github.rest.repos.createCommitStatus({
+ ...context.repo,
+ sha: process.env.PREVIEW_HEAD_SHA,
+ context: 'docs preview',
+ state: succeeded ? 'success' : 'error',
+ description: succeeded ? '预览已就绪,点击查看' : '预览失败或取消,点击查看日志',
+ target_url: succeeded
+ ? process.env.PREVIEW_URL
+ : `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
+ })
diff --git a/20-product-tdd/cross-unit-contracts.md b/20-product-tdd/cross-unit-contracts.md
index 4d2263e..ce265fc 100644
--- a/20-product-tdd/cross-unit-contracts.md
+++ b/20-product-tdd/cross-unit-contracts.md
@@ -25,6 +25,14 @@ Record durable data and behavior contracts that span more than one unit reposito
- `running` means the current runtime has actually started that extension and applied its runtime side effects.
- Starting or stopping an extension is not a pure flag flip; it changes runtime capabilities and API surface for that client.
+## Extension Documentation Hosting Contract
+
+- Author-built static documentation, exact Release/scope identity, conditional whole-site
+ corrections, snapshot-origin isolation, and consumer discovery follow the
+ [Extension Documentation Hosting Contract](extension-documentation-hosting.md).
+- Registry owns hosting and executable API details; authors own site content and structure.
+ Documentation does not become an installable Distribution or alter Host lifecycle authority.
+
## Info-Base Ownership Contract
- Collection, organization, application, graph authority, resolver/storage composition, and
diff --git a/20-product-tdd/extension-documentation-hosting.md b/20-product-tdd/extension-documentation-hosting.md
new file mode 100644
index 0000000..5d0e926
--- /dev/null
+++ b/20-product-tdd/extension-documentation-hosting.md
@@ -0,0 +1,113 @@
+# Extension Documentation Hosting
+
+## Purpose And Ownership
+
+This contract defines how Extension authors, Registry, developer tooling, and consuming Hosts
+exchange hosted documentation. Registry owns the executable HTTP contract and hosting mechanics;
+this document owns the boundaries that those implementations must preserve.
+
+Authors build a static site with their chosen tools. Registry accepts and serves the resulting
+files without running builds, interpreting document structure, or rewriting HTML and JavaScript.
+Navigation, search, localization, themes, and interface-specific instructions belong to the
+author's site. Documentation is not an executable Extension Distribution and cannot make a Release
+installable by itself.
+
+## Identity And Document Ownership
+
+A documentation set belongs to one Extension name, one exact Release version, and one scope:
+
+| Scope | Meaning | First-party source owner |
+| --- | --- | --- |
+| `global` | Extension-wide concepts, workflows, and limits across channels | `core-py` |
+| `python` | Python Distribution operation and configuration | The Python producer in `core-py` |
+| `module-federation` | Web Distribution interfaces and behavior | The MF producer in `client-web` |
+
+These are separate, optional sites, not inheritance or override layers. Registry does not merge
+them. A channel-specific set requires the corresponding Distribution association on that Release;
+global documentation does not require a Web Distribution. Web, CLI, and Agent instructions are
+reader interfaces, not additional Distribution channels.
+
+First-party sites share a template maintained in `InKCre/docs`, with producer repositories using
+an explicit template revision. This does not require ecosystem authors to adopt that template or
+its static-site generator. The main documentation site owns application onboarding, self-hosting,
+and cross-Extension guidance; detailed Extension instructions retain one source owner rather than
+being copied into each consuming site.
+
+## Discovery And Consumption
+
+Registry exposes documentation discovery separately from Release installation metadata. Consumers
+ask their configured Registry for the exact installed Release and receive the available scopes,
+stable entry addresses, current content identities, update times, and publication provenance.
+Adding documentation must not invalidate existing strict Release consumers.
+
+An absent scope is genuinely absent. Consumers may explicitly offer global documentation instead
+of a missing channel site, but must not silently substitute another scope or version. Page paths
+inside a site remain an agreement between its author and consumers, not Registry-defined content
+semantics. Consumers follow Registry-provided entry addresses rather than constructing content
+hostnames or assuming documentation shares the management origin.
+
+## Publication And Corrections
+
+Publishing a set uploads a complete static bundle and identifies its entry file. Registry validates
+the bundle's paths, entry, file types, and resource bounds; it does not accept server-side execution
+or author-defined server configuration. Detailed archive, routing, MIME, and error contracts belong
+to Registry's executable API and local documentation.
+
+Namespace publication authority also controls that namespace's documentation. Creating a set
+requires a conditional create; replacing it requires the observed current entity tag. Only after
+all files are available may Registry atomically move that set's entry to the new snapshot. A failed
+upload or conflicting replacement leaves the previous entry intact. Clients resolve uncertain
+responses by retrying the same saved publication candidate. Its snapshot identity also identifies
+that publication: once committed, the exact same target, content, provenance, and original write
+precondition return the original commit receipt without changing the current-set pointer. Reusing
+the identity for a different committed request is a conflict. A receipt confirms a historical commit,
+not that its snapshot is still current; another publication may already have superseded it.
+
+Clients may make bounded best-effort retries, but hosting does not guarantee eventual delivery.
+When a receipt cannot be obtained, the outcome remains unknown and the candidate must remain
+available for a later retry. Clients do not refresh the write precondition, generate a replacement
+identity, or infer failure merely because a response was lost. Authentication and Release lifecycle
+rules also apply when confirming an earlier commit.
+
+Documentation can be corrected without a new Extension Release. There is no separate documentation
+semantic version, automatic inheritance between Releases, or many-to-many applicability mapping.
+Updating one scope does not update another or mutate Python/MF artifacts. Snapshot content is
+immutable; its identity serves integrity and resource consistency, not a second author-managed
+version scheme.
+
+Previously published snapshots remain available while their owning Release permits public reads,
+so an already opened page can finish loading its original resources. Each public snapshot remains
+bound to its exact Release and scope even when underlying byte storage is deduplicated.
+
+## Static Content Boundary
+
+Each snapshot is served at the root of its own content origin, separate from Registry management
+and from other snapshots. A stable Release/scope entry selects the current snapshot; navigation and
+assets within that site continue to use that snapshot. This prevents mixed-version assets and
+separates script, browser storage, and Service Worker authority. Separate URL directories on one
+origin do not satisfy this boundary.
+
+Content origins do not expose publication APIs or receive Registry publication credentials or
+shared authentication cookies. Registry controls response headers and MIME handling; uploaded
+files cannot supply server headers or override host routing. Registry UI must not execute author
+content within its management origin, and links to hosted sites sever opener access.
+
+The serving topology must support root-relative static assets without rewriting the author's build.
+When an author needs an absolute hostname during a build, tooling may reserve an opaque snapshot
+address before computing the final content digest. An address can never be rebound to different
+bytes. Concrete domain, DNS, TLS, and storage deployment choices remain Registry-owned.
+
+## Release Lifecycle
+
+Documentation may be prepared alongside a preparing Release, but preparing content is not public.
+Published Releases expose their documentation normally. Yanked Releases retain exact-version
+documentation, and Registry entry surfaces identify that withdrawal; documentation corrections do
+not restore the Release's recommendation or installability.
+
+Blocked Releases expose neither discovery, stable entries, nor current or historical snapshot
+files. Publishers cannot bypass a block by correcting documentation. Lifecycle checks apply before
+cache revalidation, and private object storage must not offer an unguarded public bypass.
+
+These rules govern new network responses, not copies already downloaded by a reader or stored by
+an author's Service Worker. Hosting does not promise remote deletion of offline content. First-party
+templates do not enable offline Service Workers.
diff --git a/tasks/user-getting-started/implementation-plan.md b/tasks/user-getting-started/implementation-plan.md
new file mode 100644
index 0000000..63e6b5e
--- /dev/null
+++ b/tasks/user-getting-started/implementation-plan.md
@@ -0,0 +1,97 @@
+# Memos 初始化与插件文档入口:实施计划
+
+## 授权与目标
+
+2026-09-21,用户确认架构重查后的方案,授权实施、提交、推送与验证。先独立提交本计划,再实施。沿用 Core #110、Docs #28、Web #115、Registry #41 的分支和 worktree;不合并、不正式发布、不修改生产配置。本文件是本地总任务 packet 的实施附件,不另立任务控制入口。
+
+用户应能在 client-web 中准备 Memos 的 URL 与 PAT,通过普通帮助链接查看对应发行的作者文档。Extension 初始化不包办 Source 的配置和采集。Twitter 功能/UI 保持不动,RSS/GitHub 不增加 setup,Mail/Telegram 的 Source 改进不在本轮。2026-09-21 用户进一步回复“纳入”,授权第一方 Python 的 SDK 0.3 兼容调整及必要配套发行意图,包括 Twitter;不扩大合并、正式发布或生产部署权限。
+
+## 实施边界
+
+单个 deployment 主要服务一个用户。在线 Peer + enabled 是正常流程采用的 best-effort 运行假设;观察到具体失败再处理,不建立 running 数据库字段或额外运行证明。PAT 沿用普通配置,浏览器生成或复用,通过既有 patch_config 保存;不新增原子 prepare、锁、操作收据或同步系统。不建设通用 Wizard、Setup Context、Shell,也不改变 setup.component。
+
+### 1. Core 公共地址与 Memos 服务地址
+
+从 Web Memos 直接读取 peer.config 并拼接路径,改为 Core 提供进程内公共 HTTP 基址读取,Memos Python 负责形成自身完整服务地址,Web 通过 Memos 自有的只读 Peer capability 获取。
+
+实现位于 Core app/http.py 和 extensions/memos,复用既有 Core-owned 配置、Peer JWT、固定 inbound 和 Extension 生命周期。公共基址仍只有一个配置权威,保留路径前缀。不新增环境变量、插件 server_url 配置、通用 Core HTTP-info API 或 endpoint registry。只读返回地址,不重复返回 PAT,不将其作为运行状态证明;地址失败不回滚已保存配置或 enabled。
+
+影响限于 Core 地址消费和新 Memos 发行;原 Memos 公共协议与 PAT 鉴权边界保持不变。通过真实 HTTP 验证认证、路径前缀、启停后 capability 以及缺少地址时的反馈。
+
+### 2. 共用管理协议与文档发现
+
+从应用和 Memos 各自维护 Core Extension 管理 wire,改为 ext-reg 的现有独立 Web Runtime 包提供无状态的管理入口:在线且宣告管理 capability 的 Peer 列表,以及精确目标的 typed management command。PeerManager 继续拥有传输、路由与失败分类;应用继续拥有 current-runtime / remote-host / desired-state 策略。
+
+Runtime 同时提供独立的 exact Release 文档发现读取,不借可执行发行预检读取帮助。复用生成合同和现有 Registry origin resolver;不修改 Registry 服务协议,不新增 SDK 公共模型接口。需要用真实 @inkcre/core 0.3 产物完成类型与构建验证,纠正 Runtime 陈旧的 peer dependency 声明,不能仅靠 ambient stub 通过。
+
+所有 mutation 坚持精确目标、不自动换 Peer、不自动重试未知结果。错误不包含可能回显 PAT 的原始响应。Twitter 本轮不迁移。
+
+### 3. Memos 与应用 UI
+
+Memos 主流程缩减为选择 Core(单候选自动选)、准备连接、复制 URL/PAT。PAT 默认遮蔽,不要求 JSON 或手工生成;已有 PAT 复用,仅尚未 enabled 才启用。保留 pending、真实错误、部分成功和未知结果的现场反馈。重开页面不自动执行 mutation。
+
+Extension 卡片提供实际存在的 global / python / module-federation 文档链接;Memos 提供对应 MF 教程页面或锚点的帮助链接。不嵌入正文、不解析 Markdown、不建立导航模型。严格使用已安装版本,缺文档、旧 Registry 或网络故障不能阻断初始化;明确区分不存在与暂时不可用。外链不带凭据,断开 opener。
+
+### 4. 普通配置的运行时生效
+
+已发现的顺序操作问题是 Web Config 直写数据库而 Memos 鉴权读取进程内配置。经现有合同和 advisor 复核,Memos 原有 Unit TDD 已承诺无需重建路由的替换/撤销,因此在每个受保护请求通过既有 EXTENSION_HOST.get 读取 canonical config,再恢复 MemosConfig 类型。读取失败不回退旧配置,不新增 fresh getter、广播或缓存。有效保存完成后发起的请求采用新值,不追溯取消已通过鉴权的请求。共享合同无需更改。
+
+验收必须覆盖一个用户正常修改/撤销 PAT 后的声明行为;不要求两窗口并发初始化唯一性或多 Core 下一请求立即一致。
+
+### 5. 文档与发行准备
+
+在原有 Unit TDD 修正 Web StatePort/独立 Runtime 的已证实漂移,并更新原 Memos global/python/MF 教程。新共享合同若确有必要,先在 Docs Hub 修改并推送,再分别提交 Spoke 引用;不从 Spoke 编辑 docs/_shared。
+
+新 Memos Python 与 MF 使用同一新 Extension Release,正常提交 release intent,不覆盖公开 0.2.0。验证真实 Runtime/SDK 产物和发行预检。本轮的提交推送授权不等于合并、包发布或生产部署授权。
+
+Core pyproject.toml 的服务发行版本是 0.5.0,现有 fragment 准备到 0.6.0;app/version.py 的 Host SDK 是独立轴,release.py 不会更新它。用户确认纳入后,c215705 已显式将 Host SDK 推进到0.3,并为 GitHub、Learn English、Mail、RSS、Telegram、Twitter 保留 >=0.2 下界、扩展上界到<0.4;Memos 使用新接口,继续要求>=0.3 <0.4。兼容性变化使用项目本地 changed/patch intent,旧发行不改写。Twitter/Mail 的 MF 同步增加 patch intent,确保共享 exact Release 仍同时具有两种 Distribution,不改功能/UI/Web SDK 范围。
+
+另外,Web Memos 当前版本 0.1 的单次 minor intent 只准备到 0.2,而新 Python 功能发行目标为 0.3;普通 Changesets 不支持任意目标版本。正式 Version PR 必须对齐二者,不能发布一个需要新 Python API 的旧 0.2 MF。新 Web Runtime 也须先经正常发布,消费者才能根据真实产物更新冻结依赖;本地候选通过不表示公开依赖已可用。
+
+Runtime 的 SDK 类型依赖已改为固定 client-web 源码 SHA 的真实构建:只检出 packages/core,使用原 SDK 源码和构建配置、ext-reg 自身冻结依赖构建,将产物置于 Runtime 私有 node_modules 并只替换本 workspace 的链接,不写 pnpm 共享 store。已删除 ambient SDK stub;源码类型、声明构建与 HTTP 边界检查均使用真实 SDK。这是本仓库锁定环境的兼容验证,不宣称复现尚未发布的官方 SDK 制品,也不建立新 SDK 发布渠道。
+
+## 提交顺序与验证
+
+1. Docs:独立提交本计划。
+2. Registry Runtime:实现管理与文档消费接口,真实 SDK 类型/产物集成;运行包级与仓库门禁,提交推送 #41。
+3. Core:公共地址、Memos 只读入口及收敛后的配置行为;声明新发行意图,更新局部文档,运行 pdm run check 和真实 HTTP 验证,提交推送 #110。
+4. Web:消费 Runtime、精简 Memos、添加文档链接,更新教程与旧 Unit TDD;运行 pnpm check、pnpm build 和浏览器验收,提交推送 #115。
+5. Docs:按实际交付校正用户教程与交付记录,运行网站门禁,推送 #28。跨仓库引用与源码提交分开。
+
+独立代码可并行,但所有消费者必须以真实候选产物验证,不能把本地 alias 或 stub 当成发行已经可用。若上游包尚未发布,保留明确的交付阻塞和可重复的候选集成证据,不提交指向本机路径的生产依赖。
+
+验收覆盖首次准备、已有 PAT、重开与重复点击,保存/启用/地址读取分别失败,精确目标与未知结果,普通 Config 修改/撤销,以及 exact Release 文档的各 scope、缺失、不可用和作者锚点。用 disposable 数据库和真实 Core/Web/Memos HTTP 完成登录与 memo 读写;桌面和窄屏检查复制、遮蔽、键盘操作。此证据不扩大为 MoeMemos 设备 UI 已验收。
+
+## 完成标准与回退
+
+各仓库改动、文档、候选产物集成和门禁有可回读证据;PR 保持现有 draft/ready 状态,列明相关 PR、未发布依赖及剩余限制。每次提交只包含当前任务文件,不带本地凭据、运行数据或他人改动。代码回退以各仓库任务提交为单位;本轮不改 DB schema,因此无需数据迁移回退。
+
+## 本轮实现与验收结果
+
+实施计划 efe5cef 先行提交。Core b3626a1、Registry Runtime 5789d4a、Web 2f06923 已推送原 PR。Core 全门禁通过(14 passed、62 skipped),数据库项目另在 disposable PostgreSQL/pgvector 17 运行,Memos 集成 16 passed。Registry 完整 PG/Moto 门禁及 Web 完整门禁均使用真实 SDK/Runtime 候选通过,不能替代下述公开依赖门禁。
+
+真实隔离 Core、PostgREST、Registry 与 MF 候选完成首次准备、保存失败、启用结果未知、启用失败、地址缺失后的恢复,以及重开无 mutation、复制、键盘、390px 和 memo CRUD。Registry 通过真实发布 API 接收 Python/MF 与三组 VitePress 文档,浏览器点击三种 scope 外链和 MF 锚点成功;文档 404/503 不阻断设置。普通 Web Config 直接产生 PostgREST PATCH,PAT A→B 后 A 401/B 200,撤销后均 401,无 Core 重启或重复 enable。PAT 已撤销。MoeMemos 设备 UI 未验收。
+
+原实现安装整个 producer workspace,其锁文件元数据要求读取无关的 UI 包;只读 token 仍403。9615b0d 改为 ext-reg 自身冻结构建环境,不安装 Web 根 workspace,并撤回额外包认证。无 token、禁用用户 npmrc、全新 store 的冻结安装下载212包且复用0包;真实 SDK/Runtime 构建、类型、HTTP边界与完整本地 PG/Moto 门禁通过。远端运行35587933605全通过,含完整门禁、镜像构建和真实HTTP/数据库smoke,未关闭供应链检查。Web在2f06923重新消费最终Runtime候选,完整pnpm check通过;没有改源码、manifest或lock来伪造正式依赖。
+
+SDK兼容调整已获确认并实现。剩余发行协调限制:Runtime 新接口尚未正式发布,Web 保留现有公开 0.1 依赖,因此干净 CI/preview 尚不能通过;Memos MF/Python 0.3 需在正式 Version PR 对齐。用户教程按目标发行编写,不能先于这些依赖单独交付。本轮不合并、不发布、不部署生产。
+
+验收结束已关闭本轮 Core/Web/Registry/Moto、SSH 转发,并清理三个精确命名的专用测试容器及其可重建数据。没有清理用户既有开发或预览实例;任务脚本、构建候选、截图和活动 packet 保留。
+
+## SDK 0.3 兼容调整的补充验收
+
+Core c215705 已提交推送,Web d5949f5 仅新增 Twitter/Mail patch changeset。Core 本地 pdm run check(14 passed、62 skipped)、release admission、pip check及远端仓库/数据库检查通过。未新增依赖或修改插件行为,任务分支的包版本和生成changelog未改。
+
+隔离目录 `/tmp/inkcre-sdk03.hNrOQD` 从真实提交准备版本、构建并finalize七个wheel。移开该副本的producer源码后,实际Core SDK0.3通过静态Registry HTTP下载、依赖预检、pip安装、标准entry point加载及wheel文件归属检查;七个均通过,pip check无缺失依赖。SDK预检确认六个旧插件接受0.2/0.3、拒绝0.4,Memos拒绝0.2、接受0.3。脚本为Core工作区 `tasks/heroku-self-hosting/check-sdk03-wheels.py`。这是发行与加载兼容证据,不是第三方账号采集或所有插件生命周期的重复验收。
+
+| Python候选 | 版本 | 配套MF候选 |
+| --- | --- | --- |
+| GitHub | 0.3.1 | 不需要 |
+| Learn English | 0.2.1 | 不需要 |
+| Mail | 0.3.1 | 0.3.1 |
+| Memos | 0.3.0 | 既有0.3目标,版本准备协调仍待完成 |
+| RSS | 0.2.1 | 不需要 |
+| Telegram | 0.3.1 | 不需要 |
+| Twitter | 0.4.1 | 0.4.1 |
+
+Twitter/Mail MF 在独立副本中通过正常Changesets准备上述版本、构建及资源闭包检查;功能源码、构建配置、导出与Web SDK范围未变。真实Python+MF组合静态Registry由Toolkit构建,在同一exact Release上通过Core SDK0.3和Web Runtime SDK0.3预检,MF manifest返回200。证据在Web task的 `evidence/sdk03-mf-companion.json`;原Web工作区完整pnpm check通过,但公开Runtime依赖阻塞仍然存在。教程同步采用新兼容版本,生态作者示例以SDK0.3为目标;不再把已授权的兼容范围列为待确认。
diff --git a/website/.vitepress/config.mts b/website/.vitepress/config.mts
index 24bd4d9..02c527f 100644
--- a/website/.vitepress/config.mts
+++ b/website/.vitepress/config.mts
@@ -49,12 +49,63 @@ export default defineConfig({
},
themeConfig: {
nav: [
+ { text: 'Getting Started', link: '/getting-started' },
{ text: 'Developer', link: '/developer/' },
{ text: 'About', link: '/about/' },
{ text: 'GitHub', link: 'https://github.com/InKCre' },
],
sidebar: {
+ '/': [
+ { text: 'Getting Started', link: '/getting-started' },
+ {
+ text: 'User Guide',
+ items: [
+ { text: 'Connect to Your Instance', link: '/guide/connect' },
+ { text: 'CLI / Agent Connection', link: '/guide/connect-cli' },
+ { text: 'Prepare an Extension', link: '/guide/extensions' },
+ { text: 'Collect Your First Source', link: '/guide/first-source' },
+ { text: 'Find What You Saved', link: '/guide/search' },
+ { text: 'Schedule Collection and Indexing', link: '/guide/schedules' },
+ {
+ text: 'Connect More Sources',
+ link: '/guide/sources',
+ items: [
+ { text: 'RSS and Atom', link: '/guide/sources/rss' },
+ { text: 'GitHub Stars and Lists', link: '/guide/sources/github' },
+ { text: 'Email over IMAP', link: '/guide/sources/mail' },
+ { text: 'Telegram Inbox', link: '/guide/sources/telegram' },
+ { text: 'Twitter / X Bookmarks', link: '/guide/sources/twitter' },
+ { text: 'Memos-Compatible Capture', link: '/guide/sources/memos' },
+ { text: 'Run a Collection', link: '/guide/collect' },
+ ],
+ },
+ {
+ text: 'Use Your Information',
+ link: '/guide/daily-use',
+ items: [{ text: 'Sinks: ChatGPT via MCP', link: '/guide/sinks/chatgpt' }],
+ },
+ { text: 'Troubleshooting', link: '/guide/troubleshooting' },
+ ],
+ },
+ {
+ text: 'Self-Hosted',
+ items: [
+ { text: 'Overview', link: '/self-hosted/' },
+ { text: 'Getting Started', link: '/self-hosted/getting-started' },
+ { text: 'Render and Neon', link: '/self-hosted/render-neon' },
+ { text: 'Heroku and Neon', link: '/self-hosted/heroku-neon' },
+ { text: 'Advanced', link: '/self-hosted/advanced' },
+ ],
+ },
+ ],
'/developer/': [
+ {
+ text: 'Ecosystem Developers',
+ items: [
+ { text: 'Build on InKCre', link: '/developer/ecosystem/' },
+ { text: 'Build a Source Extension', link: '/developer/ecosystem/source-extension' },
+ ],
+ },
{
text: 'Developer Guide',
items: [
diff --git a/website/.vitepress/theme/InterfaceGuide.vue b/website/.vitepress/theme/InterfaceGuide.vue
new file mode 100644
index 0000000..9fed22c
--- /dev/null
+++ b/website/.vitepress/theme/InterfaceGuide.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+
Your choice follows you between guide pages.
+
+
+
+
+
+
+
+
+
+
diff --git a/website/.vitepress/theme/index.ts b/website/.vitepress/theme/index.ts
index 42fe9a9..9ba1962 100644
--- a/website/.vitepress/theme/index.ts
+++ b/website/.vitepress/theme/index.ts
@@ -1,4 +1,13 @@
import DefaultTheme from 'vitepress/theme'
+import type { EnhanceAppContext } from 'vitepress'
+import { ref } from 'vue'
+import InterfaceGuide from './InterfaceGuide.vue'
import './custom.css'
-export default DefaultTheme
+export default {
+ extends: DefaultTheme,
+ enhanceApp({ app }: EnhanceAppContext) {
+ app.provide('guide-interface', ref('web'))
+ app.component('InterfaceGuide', InterfaceGuide)
+ },
+}
diff --git a/website/README.md b/website/README.md
index d3a8492..1b8d8ec 100644
--- a/website/README.md
+++ b/website/README.md
@@ -29,8 +29,16 @@ pnpm --dir website audit --audit-level high
- Future Chinese source will live under `content/zh/` and be published under `/zh/`.
- Only English is active until the Chinese route set is complete or the locale switch has a
deliberate fallback.
-- The current published routes are `/`, `/developer/`, `/developer/architecture`,
- `/developer/contributing`, and `/about/`.
+- `/getting-started` owns the application-level What, Why, and How introduction.
+- `/self-hosted/` contains its Getting Started path, Render/Heroku quick-deployment guides, and
+ Advanced guide. Its Getting Started page orders the journey rather than duplicating procedures.
+- `/guide/` leaf pages own reusable client, collection, retrieval, scheduling, source, daily-use,
+ and troubleshooting procedures. Link to these pages from any onboarding path rather than to
+ sections buried inside the self-hosted walkthrough.
+- `/guide/sources` selects independent source tutorials under `/guide/sources/`; `/guide/collect`
+ owns shared collection and Job observation. Memos is documented separately as write-in capture.
+- `/developer/` separates ecosystem integration guidance under `/developer/ecosystem/` from
+ architecture and core contribution guidance; `/about/` describes the project.
- Section indexes use trailing-slash routes, such as `/developer/`.
- Leaf pages use lowercase ASCII kebab-case routes without an extension, such as
`/developer/architecture`.
@@ -44,6 +52,15 @@ are resolved from the rewritten route, not the source file location.
## Page Authoring
+- User procedures default to client-web. Use the shared `InterfaceGuide` component with `#web` and
+ `#cli` slots for alternate steps on the same route; CLI instructions primarily serve Agents and
+ operators. The choice survives client-side navigation, not a full reload. Without JavaScript, both
+ sections remain readable. Keep shared prerequisites and limitations outside the slots.
+- Set `outline: false` on interface-switching pages: the default VitePress outline includes hidden
+ slot headings. Do not expose links to invisible instructions. The site sidebar remains available.
+- State actual interface gaps instead of implying feature parity. Core package installation and
+ lexical maintenance still need CLI/operator steps; a browser wizard requires a compatible native
+ distribution as well as its Core collector. Check both against published Registry releases.
- Keep exactly one H1 per page.
- Add a concise page `description` in frontmatter.
- Give headings explicit custom anchors only when another page or external consumer needs a durable
@@ -70,10 +87,13 @@ same-repository run, the trusted Preview workflow checks out that exact head, bu
publishes an isolated, deterministic, short-lived preview. Fork pull requests receive no preview
credentials, preview origins remain `noindex`, and closing the pull request replaces the live
preview with a trusted closed-preview tombstone. The stable `preview-docs-pr-N` branch alias is the
-user-facing preview URL and is recorded against the pull-request head in GitHub; Cloudflare retains
-the underlying immutable deployments in its history. If automatic retirement fails, the cleanup
-workflow can be run manually for the closed pull-request number. A preview build is never promoted
-to production.
+user-facing preview URL. The `docs preview` commit status on the exact pull-request head links to
+that URL after deployment and smoke checks succeed; pending, failed, or cancelled runs link to the
+workflow logs. The workflow's automatic environment deployment record belongs to its trusted `main`
+controller, so the explicit commit status provides the PR-facing entry point. Changes to this
+`workflow_run` controller take effect after merging into `main`. Cloudflare retains the underlying
+immutable deployments in its history. If automatic retirement fails, the cleanup workflow can be run
+manually for the closed pull-request number. A preview build is never promoted to production.
Protected `main` is the publication authority. `Pages deployment` runs for a push to `main`; failed
runs can be rerun for the same commit, while rollback starts by reverting `main` through a pull
diff --git a/website/content/en/developer/architecture.md b/website/content/en/developer/architecture.md
index 41d9365..3e1ae4a 100644
--- a/website/content/en/developer/architecture.md
+++ b/website/content/en/developer/architecture.md
@@ -80,8 +80,10 @@ activity are separate states:
- **enabled**: a particular client is permitted to run it;
- **running**: the current runtime has started it and applied its side effects.
-The lifecycle and ownership model exists, but a stable third-party package format and SDK guide are
-not yet public contracts.
+Core supports native Python wheels with versioned Registry releases and explicit Host compatibility.
+The [Source Extension tutorial](/developer/ecosystem/source-extension) covers the current Core Host
+0.2 path. Its Source programming interfaces still import Core modules; the delivery Toolkit is not a
+standalone Source SDK or a promise of compatibility with every future Host.
### APIs
diff --git a/website/content/en/developer/contributing.md b/website/content/en/developer/contributing.md
index 038217b..6e44a0e 100644
--- a/website/content/en/developer/contributing.md
+++ b/website/content/en/developer/contributing.md
@@ -8,6 +8,10 @@ description: Route an InKCre change to its canonical owner and repository-specif
InKCre welcomes code, documentation, design, and ecosystem contributions. Start by identifying the
owner of the change; then follow that owner's current development and review contract.
+If you want to connect another service without changing InKCre itself, use the
+[ecosystem developer guide](/developer/ecosystem/) instead. Your Extension can live in your own
+repository; it does not need to become a Core contribution.
+
> [!NOTE] There is no copied, universal setup or contribution procedure. Toolchains and commands
> belong to the repository that runs and verifies them.
diff --git a/website/content/en/developer/ecosystem/index.md b/website/content/en/developer/ecosystem/index.md
new file mode 100644
index 0000000..8c2d7cd
--- /dev/null
+++ b/website/content/en/developer/ecosystem/index.md
@@ -0,0 +1,45 @@
+---
+title: Ecosystem Developers
+description: Add integrations to InKCre without contributing to its core implementation.
+---
+
+# Ecosystem Developers
+
+Build an integration for your own workflow or distribute it to other InKCre operators. You do not
+need to contribute it to the Core repository. Start with the boundary your integration needs:
+
+| Goal | Integration path |
+| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| Collect from a service InKCre does not support yet | [Build a Source Extension](/developer/ecosystem/source-extension) |
+| Use existing information from ChatGPT | [Connect the MCP Sink](/guide/sinks/chatgpt) |
+| Build a new runtime around the shared info-base | Read the [Peer architecture](/developer/architecture); this requires the admitted database protocol, not arbitrary SQL access |
+| Change InKCre's own runtime or product behavior | Follow [Contributing](/developer/contributing) |
+
+## What an Extension supplies
+
+A Source fetches and maps external information. A Resolver interprets stored content and relations.
+A Sink makes information useful downstream. An Extension packages one or more such capabilities for
+a particular Host; not every integration needs all three or a custom browser interface.
+
+The Python path uses a native wheel, the `inkcre.core.extensions` entry point, and an exact Registry
+Release. Core installs that release, a Peer enables it, and the running Host activates its behavior.
+These are separate steps. Browser code is a separate native distribution, not automatically produced
+by a Python wheel.
+
+The Source tutorial targets **Core Host SDK 0.3.x**. Its Python programming interfaces currently
+import Core modules; they are not an independent, universally stable Source SDK. The Extension
+Toolkit builds delivery metadata and preview registries; it does not run collectors or replace Core.
+
+## Trust and delivery
+
+An admitted Extension is trusted in-process code, not sandboxed user content. A malicious package
+could access the runtime's information and credentials. Operators must review what they install;
+Registry publication is not proof of isolation. Use a separate test deployment and non-sensitive
+fixtures during development, with its own database and credentials.
+
+Package identity, Host compatibility, dependencies, and immutable releases are part of delivering a
+usable integration. Keep your own package, tests, release history, and user setup guide in your
+repository. A public Registry requires its operator's namespace and publishing authorization; a
+private development preview does not grant those rights.
+
+Continue with [Build a Source Extension](/developer/ecosystem/source-extension).
diff --git a/website/content/en/developer/ecosystem/source-extension.md b/website/content/en/developer/ecosystem/source-extension.md
new file mode 100644
index 0000000..850f53e
--- /dev/null
+++ b/website/content/en/developer/ecosystem/source-extension.md
@@ -0,0 +1,286 @@
+---
+title: Build a Source Extension
+description:
+ Build an independent Python collector and try its wheel in a Core Host SDK 0.3 test deployment.
+---
+
+# Build a Source Extension
+
+This is for Python developers adding an integration, not contributors changing Core. Your Extension
+lives in its own repository. It runs inside a matching **Core Host SDK 0.3.x** environment because
+Source and graph APIs currently import Core's `app.*` modules. Installing the delivery Toolkit alone
+does not provide those runtime APIs.
+
+We will collect one JSON document, store its text, and link it to a Source anchor. Repeating an
+unchanged response does not add a new snapshot. This deliberately small example has no OAuth,
+pagination, custom Resolver, or UI; it is not a general-purpose JSON importer.
+
+## 1. Prepare an isolated test setup
+
+Use Python 3.12 and a separate [Core deployment](/self-hosted/) with disposable data. Connect the
+[CLI](/guide/connect-cli) to that instance and ensure it is running Host SDK 0.3.x. Do not use your
+personal information store to experiment with trusted in-process code.
+
+Prepare an HTTP endpoint reachable **from Core** that returns this JSON:
+
+```json
+{ "id": "note-1", "text": "Notebook verification: blue heron" }
+```
+
+For a local-only experiment, save it as `note.json` in a new folder containing no other files and
+serve that folder with `python -m http.server 8765 --bind 127.0.0.1`. Its URL is
+`http://127.0.0.1:8765/note.json` only when Core runs on that same machine outside a container. A
+remote Core needs an endpoint it can actually reach; use a non-sensitive HTTPS fixture under your
+control instead of exposing private directories or assuming your laptop's localhost is remote Core.
+
+## 2. Create your own package
+
+Create this layout in a new project directory:
+
+```text
+notebook-extension/
+ pyproject.toml
+ extensions/
+ notebook/
+ __init__.py
+ source.py
+```
+
+Do **not** add `extensions/__init__.py`: `extensions` is a shared namespace package. Before
+distributing, replace `yourname` with a namespace you control and choose a unique module/entry-point
+name to avoid collisions with other installed Extensions.
+
+Save `pyproject.toml`:
+
+```toml
+[build-system]
+requires = ["setuptools>=80,<81"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "yourname-inkcre-notebook"
+version = "0.1.0"
+description = "One-document notebook collector for InKCre"
+requires-python = ">=3.12,<3.13"
+dependencies = ["httpx>=0.28.1,<0.29", "pydantic>=2.10.6,<3"]
+
+[project.entry-points."inkcre.core.extensions"]
+notebook = "extensions.notebook:Extension"
+
+[tool.inkcre-extension]
+name = "yourname/notebook"
+nickname = "Notebook"
+host-sdk = "core-py"
+host-sdk-version = ">=0.3.0 <0.4.0"
+
+[tool.setuptools.packages.find]
+include = ["extensions.notebook*"]
+namespaces = true
+```
+
+The product coordinate (`yourname/notebook`), Python project name, and module path serve different
+purposes. The entry-point name and Extension's `ext_id` must agree. Declare direct dependencies;
+Core checks them against its existing environment and will not fetch arbitrary missing dependencies
+while enabling your Extension. A new dependency may require an operator-built Core image.
+
+Save `extensions/notebook/__init__.py`:
+
+```python
+from app.business.extension.main import EmptyConfig, ExtensionBase
+
+
+class Extension(ExtensionBase[EmptyConfig], ext_id="notebook", config_cls=EmptyConfig):
+ @classmethod
+ def _init_sources(cls):
+ from .source import Source # Registers the class when the Host starts it.
+```
+
+Import-time registration must not connect to the database or fetch provider data. The Host handles
+startup and catalog synchronization. No extra HTTP endpoint or scheduler is needed.
+
+## 3. Implement collection
+
+Save `extensions/notebook/source.py`:
+
+```python
+import httpx
+from pydantic import BaseModel, ConfigDict, Field, HttpUrl
+
+from app.business.info_base.commands import persist_stars
+from app.business.info_base.resolver import TextResolver
+from app.business.source import SourceBase, SourceManager
+from app.persistence.source.uow import source_uow
+from app.schemas.info_base.relation import RelationModel
+from app.schemas.job import JobModel
+
+
+class SourceConfig(BaseModel):
+ model_config = ConfigDict(extra="forbid")
+ url: HttpUrl
+
+
+class Note(BaseModel):
+ model_config = ConfigDict(extra="forbid", strict=True)
+ id: str = Field(min_length=1)
+ text: str = Field(min_length=1, max_length=100_000)
+
+
+class Source(SourceBase[SourceConfig], config_cls=SourceConfig):
+ """Save changes to one operator-selected JSON document as text snapshots."""
+
+ async def collect(self, job: JobModel, config: BaseModel) -> None:
+ source_config = await self.get_config()
+ url = str(source_config.url)
+ async with httpx.AsyncClient(timeout=20) as client:
+ response = await client.get(url)
+ response.raise_for_status()
+ note = Note.model_validate(response.json())
+ snapshot = {"url": url, **note.model_dump()}
+
+ async with source_uow() as uow:
+ source = await uow.sources.get(self._id, lock=True)
+ if source is None:
+ raise ValueError("Source was deleted")
+ if SourceConfig.model_validate(source.config) != source_config:
+ raise ValueError("Source config changed during collection; run again")
+ if (source.state or {}).get("last_snapshot") == snapshot:
+ return
+ anchor = await SourceManager.ensure_block_async(source, uow)
+ block = await persist_stars(TextResolver.create_graph(note.text), uow.graph)
+ if anchor.id is None or block.id is None:
+ raise RuntimeError("Persisted Block has no ID")
+ await uow.graph.relations.fetchsert(
+ RelationModel(from_=anchor.id, to_=block.id, content="snapshot")
+ )
+ # ponytail: remembers one snapshot; use native-ID reconciliation for a multi-item feed.
+ source.state = {**(source.state or {}), "last_snapshot": snapshot}
+ await uow.sources.save(source)
+```
+
+HTTP and input errors fail the Job instead of reporting a false success. Network I/O finishes before
+the database transaction. The anchor, text, relation, and state commit together; state is not
+advanced if persistence fails. URL participates in snapshot identity so changing the endpoint does
+not reuse the old endpoint's state. The built-in text Resolver makes the saved text readable and
+indexable.
+
+This is a snapshot collector: a changed response creates another snapshot; returning to older text
+can create another one too. It neither reconciles a whole remote collection nor deletes earlier
+snapshots. The endpoint is selected by the trusted operator, not exposed as a public URL-fetching
+API. For a real service, add its authentication, bounded response handling, native identity,
+pagination, rate limits, and incremental state according to that service's contract.
+
+Source configuration is long-lived input, Source state remembers progress, and Job parameters/state
+belong to one execution. Use `collect_config_cls` for typed per-run options and
+`backfill_config_cls` plus `backfill()` only when you actually implement a historical collection
+mode. Keep indexing and organization separate from collection.
+
+## 4. Build and finalize the wheel
+
+In the project directory, create a build environment. The Toolkit is a delivery tool, not a runtime
+Source SDK:
+
+```sh
+python3.12 -m venv .venv
+. .venv/bin/activate
+python -m pip install build 'inkcre-extension-toolkit[cli] @ https://github.com/InKCre/ext-reg/releases/download/toolkit-v0.2.1/inkcre_extension_toolkit-0.2.1-py3-none-any.whl'
+python -m build --wheel --outdir dist/raw
+inkcre-ext python wheel finalize --project pyproject.toml --wheel dist/raw/yourname_inkcre_notebook-0.1.0-py3-none-any.whl --output-dir dist/final
+```
+
+On Windows, activate with `.venv\Scripts\Activate.ps1`. If you renamed the project, substitute the
+actual wheel filename. Finalization adds the installed `.dist-info/inkcre-extension.json` metadata
+required by Core. Keep raw and finalized output separate; ship the finalized wheel.
+
+## 5. Try a private preview without publisher credentials
+
+Save `preview.json` beside `pyproject.toml`:
+
+```json
+{
+ "schema_version": 1,
+ "distributions": [
+ {
+ "kind": "python",
+ "producer": "pyproject.toml",
+ "artifact": "dist/final/yourname_inkcre_notebook-0.1.0-py3-none-any.whl"
+ }
+ ]
+}
+```
+
+For Core running on the same machine, build and serve the preview in a separate terminal:
+
+```sh
+inkcre-ext preview build --inventory preview.json --public-origin http://127.0.0.1:8766 --output dist/registry
+python -m http.server 8766 --bind 127.0.0.1 --directory dist/registry
+```
+
+For remote Core, publish **only** the generated `dist/registry` directory to an isolated static
+HTTPS origin you control and use that origin as `--public-origin`. Keep it available for
+installation and restart. This facade contains only the supplied releases; it is not a mirror of the
+public Registry.
+
+In the terminal with your CLI, inspect `inkcre-cli peer get self` and
+`inkcre-cli config get extension.registry`. Record the prior setting (a missing config is normal). A
+Peer-level `extension_registry_url` override takes precedence; use a test Peer without an override
+or have its operator adjust that override. On this **isolated test instance only**, set the
+deployment Registry origin, replacing the URL if Core is remote:
+
+```sh
+inkcre-cli config replace extension.registry --schema-id extension.registry.config.v1 --input-json '{"extension_registry_url":"http://127.0.0.1:8766"}'
+inkcre-cli extension install yourname/notebook --version 0.1.0
+inkcre-cli extension enable yourname/notebook
+inkcre-cli source types
+```
+
+Confirm `extensions.notebook.source.Source` appears. Save `notebook.json`, using the fixture URL
+reachable from Core:
+
+```json
+{
+ "nickname": "Notebook test",
+ "config": { "url": "http://127.0.0.1:8765/note.json" }
+}
+```
+
+```sh
+inkcre-cli source create --type extensions.notebook.source.Source --input notebook.json
+```
+
+## 6. Verify behavior before distributing
+
+1. [Collect](/guide/collect) using the returned Source ID and wait for the Job to finish.
+2. [Index and search](/guide/search) for `Notebook verification: blue heron`; read the text and
+ inspect its Source relation.
+3. Collect the same fixture again. Confirm no second snapshot was added.
+4. Change the fixture's text, collect again, and confirm a new snapshot is readable.
+5. Make the fixture return invalid JSON. The Job must fail without advancing Source state; fix the
+ fixture and confirm a later run succeeds.
+
+This is a small runnable acceptance journey against your actual wheel and Host, not just an import
+test. Do it on the disposable instance before inviting others to install your code. The example does
+not provide complete multi-item reconciliation or guarantee ordering of overlapping fetches; avoid
+overlapping runs and design that policy before using it as a multi-item collector.
+
+Stop any test Crons, disable/uninstall the test Extension, restore the prior Registry configuration
+(delete the config only if it did not exist before), and stop the fixture/preview servers when done.
+Removing the Extension does not itself erase collected graph data. For iteration, use a new release
+version, disable all enabled Peers, and restart Core when replacing already-imported code; do not
+overwrite published bytes or treat disable/re-enable as Python module reload.
+
+## Deliver your integration
+
+Keep the package in your own repository. To distribute through a Registry, obtain permission for
+your namespace, prepare the exact release association, upload the finalized wheel, and publish the
+release using the [Extension Toolkit](https://github.com/InKCre/ext-reg/tree/main/toolkit).
+Operators then install your exact coordinate/version and follow your source-specific setup guide.
+The static preview is a development path, not authorization to publish to `registry.inkcre.dev`.
+
+For larger collectors, study the
+[RSS implementation](https://github.com/InKCre/core-py/tree/main/extensions/rss) for incremental
+reconciliation and the
+[Source runtime](https://github.com/InKCre/core-py/blob/main/app/business/source/main.py) for
+current signatures. The
+[native distribution contract](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/native-extension-distribution.md)
+owns Host installation, dependency admission, restart, and release rules. Pin and test the Host
+compatibility you declare; a working example is not a promise that every `app.*` API is stable.
diff --git a/website/content/en/developer/index.md b/website/content/en/developer/index.md
index 6ab9b3e..a423971 100644
--- a/website/content/en/developer/index.md
+++ b/website/content/en/developer/index.md
@@ -9,7 +9,8 @@ InKCre is a multi-repository system organized around a shared info-base. This gu
common mental model and routes you to the repository that owns the details.
> [!IMPORTANT] InKCre is under active development. The contributor path is usable today, while
-> third-party database, Extension, and API contracts are still being documented and may change.
+> ecosystem interfaces remain version-sensitive. Check the Host compatibility of an Extension rather
+> than assuming a stable, standalone SDK.
## Choose a path
@@ -28,9 +29,12 @@ The foundational ecosystem path is participation as an authenticated peer of the
Native PostgreSQL and PostgREST are transports over the same admitted, versioned database protocol.
Extensions and APIs provide additional integration shapes.
-This is not yet a promise of a stable public SDK, unrestricted database access, or a complete API
-compatibility policy. The [Architecture guide](/developer/architecture#ecosystem-surfaces) explains
-what is real now and where its canonical contracts live.
+Start with [Ecosystem Developers](/developer/ecosystem/) to choose an integration path. To collect
+from a new service, follow [Build a Source Extension](/developer/ecosystem/source-extension):
+develop your own Python package and test it on a matching Core Host without contributing to Core.
+
+This is not a promise of unrestricted database access or a complete API compatibility policy. The
+[Architecture guide](/developer/architecture) explains the wider model and canonical contracts.
## Primary repositories
diff --git a/website/content/en/getting-started.md b/website/content/en/getting-started.md
new file mode 100644
index 0000000..eec100f
--- /dev/null
+++ b/website/content/en/getting-started.md
@@ -0,0 +1,84 @@
+---
+title: Getting Started
+description:
+ Understand what InKCre does, why you might use it, and how to start with your information.
+---
+
+# Getting Started
+
+InKCre helps you turn information scattered across your tools into a collection you can return to
+and use. Start here to understand the experience, then choose how you will access an instance. You
+do not need to be an InKCre developer to begin.
+
+## What is InKCre?
+
+InKCre collects information, organizes it in an **info-base**, and makes it available for retrieval
+and use in other tools. Think of the info-base as your reusable collection, including connections
+between pieces of information rather than only a folder of copies.
+
+For example, you might collect articles from RSS feeds, keep track of saved GitHub repositories, and
+capture messages you forward to a Telegram bot. Later, you can find an article from a phrase you
+remember, follow its related information, or make it available to a trusted assistant.
+
+These are connected capabilities, not mandatory stages: you can collect and retrieve information
+without first configuring AI organization. Extensions provide integrations with different sources
+and tools; each integration has its own setup and limits.
+
+## Why use it?
+
+Saving information is useful only if you can find and reuse it. An article in one app, a repository
+in another, and a note in a third can become difficult to bring together when you need them.
+
+InKCre gives that information a common home without making its usefulness depend on the original
+collector or a single client. You choose the sources that matter, then access the collection from
+the Web app, the command line, or connected tools in your workflow. Self-hosting also lets you
+choose where the instance runs and where its information is stored.
+
+Start with a concrete need—such as finding useful articles from your subscriptions—rather than
+connecting every source at once. One working source and a successful search are a better first
+milestone than a large collection you cannot yet use.
+
+## How do I start?
+
+### 1. Choose how to access an instance
+
+An **instance** stores your info-base and runs capabilities such as collection. A **client**, such
+as the [Web app](https://app.inkcre.dev/settings), connects to that instance. Opening the Web app
+does not create an instance for you.
+
+- **Run your own instance:** choose [Self-Hosted](/self-hosted/). Its
+ [Getting Started](/self-hosted/getting-started) guide walks through quick deployment and your
+ first collection. [Advanced](/self-hosted/advanced) covers manual deployment and operating it on
+ infrastructure you choose.
+- **Already have access to an instance:** obtain connection details from the person operating it,
+ then [connect the Web app or your Agent](/guide/connect), [collect a source](/guide/first-source),
+ and [connect your everyday tools](/guide/daily-use). Skip the deployment steps. Only connect
+ information and tools you are authorized to use with that instance.
+
+This guide does not assume a hosted sign-up service or separate private user accounts inside an
+instance. Access arrangements and trust matter; do not treat a shared instance as an isolated
+personal account.
+
+### 2. Collect one useful source
+
+Choose [your first source](/guide/first-source); a public RSS feed is an easy starting point. Each
+[source guide](/guide/sources) covers its own prerequisites, Extension setup, and first collection.
+Once you can retrieve a known item, add personal sources one at a time, with the credentials and
+permissions each requires.
+
+### 3. Find and use what you collected
+
+Follow [Find What You Saved](/guide/search) to maintain the search index, search for something you
+remember, and open a result. Then [Use Your Information](/guide/daily-use) to connect the Web app or
+a tool you already use. These guides work independently of your deployment choice.
+
+AI organization and semantic retrieval are optional next steps with their own provider and
+maintenance setup. InKCre does not automatically configure a daily digest or Telegram/email push
+notifications: making information available in your tools is distinct from proactively sending it.
+
+## Your next step
+
+If you do not have an instance yet, open [Self-Hosted](/self-hosted/) and choose a deployment path.
+If you want to understand or contribute to the implementation, use the
+[Developer Guide](/developer/) instead. For the project's values and direction, read
+[About InKCre](/about/).
diff --git a/website/content/en/guide/collect.md b/website/content/en/guide/collect.md
new file mode 100644
index 0000000..fc415cd
--- /dev/null
+++ b/website/content/en/guide/collect.md
@@ -0,0 +1,67 @@
+---
+title: Run a Collection
+outline: false
+description: Execute one Source collection and distinguish its Source ID from its Job ID.
+---
+
+# Run a Collection
+
+Start with a [connected interface](/guide/connect). Choose Web for your own setup, or CLI / Agent
+for terminal instructions.
+
+
+
+
+## Collect in the Web app
+
+1. Open **Sources** and select the Source you created.
+2. Open its new-collection Job dialog (**New Job**, or **New task** in newer layouts).
+3. Select ordinary collection and leave run **Config** as `{}`, unless your source guide supplies
+ other options. Confirm once. This configuration applies to the run, not the Source's saved
+ account settings.
+4. Open the resulting Job from the Source's Jobs list. Wait for **finished**; refresh its details
+ while it remains pending/running. Read **State** and **Logs**, including source-specific
+ diagnostics.
+5. If creation could not be confirmed, check the Jobs list before submitting again. A timeout does
+ not prove the first request did nothing.
+
+The Source ID persists across runs; each run has its own Job ID. A successful empty collection can
+be normal. Do not repeatedly create Jobs to work around a provider error.
+
+Next: [index and search](/guide/search), then [schedule collection](/guide/schedules).
+
+
+
+
+First create a Source using [its setup guide](/guide/sources). Keep the returned Source ID.
+
+1. Replace `42` with that Source ID and run:
+
+ ```sh
+ inkcre-cli source collect 42 --input-json '{}'
+ ```
+
+ The empty object uses that collector's default run options. Source-specific options belong to the
+ individual guide; they are not a replacement for the Source's saved account configuration.
+
+2. The response contains a new **Job ID**. Replace `17` with it:
+
+ ```sh
+ inkcre-cli job wait 17 --for 30s
+ ```
+
+3. Look for `status: finished`. If still `pending` or `running`, observe the same Job again. Ending
+ observation does not cancel it. If it failed, inspect:
+
+ ```sh
+ inkcre-cli job get 17 --json
+ ```
+
+Read source-specific diagnostics even after success. Do not repeatedly create Jobs to work around a
+provider error or an observation timeout; fix the reported cause first. Review output for private
+information before sharing it.
+
+Next: [index and search for a known item](/guide/search), then [schedule](/guide/schedules).
+
+
+
diff --git a/website/content/en/guide/connect-cli.md b/website/content/en/guide/connect-cli.md
new file mode 100644
index 0000000..9572698
--- /dev/null
+++ b/website/content/en/guide/connect-cli.md
@@ -0,0 +1,63 @@
+---
+title: Connect the CLI
+description: Connect the command-line tool to an existing InKCre instance.
+---
+
+# Connect the CLI
+
+You need a ready Core URL and the instance's private JWT secret. Obtain them from your deployment or
+its operator. Keep the secret private; it grants instance authority, not an isolated personal login.
+
+The CLI is primarily for your trusted Agent, and also supports manual terminal use. For your own
+interactive setup, start with [Connect to Your Instance](/guide/connect) and choose the Web app. The
+CLI connects over HTTPS; it does not run another server on your computer.
+
+1. Install [Python](https://www.python.org/downloads/) 3.12 or later if needed. Check
+ `python --version` in a terminal; use `python3` if that is your system's command name.
+2. Create a local environment:
+
+ ```sh
+ python -m venv .venv
+ ```
+
+ Activate it with `source .venv/bin/activate` on macOS/Linux, or `.venv\Scripts\Activate.ps1` in
+ Windows PowerShell. Then install the published CLI:
+
+ ```sh
+ python -m pip install inkcre-cli
+ inkcre-cli --help
+ ```
+
+3. In a local folder outside any Git repository, use a text editor to create `connection.json`:
+
+ ```json
+ {
+ "base_url": "https://YOUR-CORE-HOST",
+ "jwt_secret": "YOUR-SAVED-JWT-SECRET"
+ }
+ ```
+
+ Replace both values, preserving the quotes. Use the Core base URL without `/readyz`. This file
+ contains a credential; keep it private.
+
+4. From that folder, save and check the connection:
+
+ ```sh
+ inkcre-cli connection set personal --input connection.json
+ inkcre-cli connection use personal
+ inkcre-cli connection check
+ ```
+
+ Both readiness and the authenticated read should succeed. You can remove the temporary
+ `connection.json` afterward: the CLI retains it at `.inkcre/cli/connections.json` in your home
+ directory. Protect that file too.
+
+**Checkpoint:** `connection check` can read your instance. For `401`, check the secret; for a
+connection failure, check the Core URL and wake `/readyz`. In later terminal sessions, reactivate
+the environment before using `inkcre-cli`.
+
+The [CLI reference](https://github.com/InKCre/core-py/blob/main/cli/README.md) owns command details.
+`--help` explains a command; `--schema` on input-taking commands shows the configuration accepted by
+your running instance.
+
+Next: [Collect your first source](/guide/first-source).
diff --git a/website/content/en/guide/connect.md b/website/content/en/guide/connect.md
new file mode 100644
index 0000000..95fc69f
--- /dev/null
+++ b/website/content/en/guide/connect.md
@@ -0,0 +1,52 @@
+---
+title: Connect to Your Instance
+outline: false
+description: Use the Web app yourself, or connect a trusted Agent through the CLI.
+---
+
+# Connect to Your Instance
+
+Use **client-web** for your own day-to-day setup and reading. The **CLI** is primarily an interface
+for your trusted Agent, and also works for operators who prefer a terminal. You do not need to
+configure every interface before starting. The selector on these guides changes the instructions,
+not your instance, and keeps your choice while navigating the site.
+
+
+
+
+## Connect the Web app
+
+1. Retain your deployment's **PostgREST URL**, private **JWT secret**, and Core URL. Wake Core by
+ opening its `/readyz` endpoint. Ask the operator for access if this is not your deployment.
+2. Open [Web app Settings](https://app.inkcre.dev/settings). Only enter your secret into a Web app
+ you trust: it grants instance authority, not an isolated personal login.
+3. Enter the PostgREST base URL in **PostgreSQL REST URL**, and the secret in **JWT Secret**. Do not
+ substitute the Core URL for PostgREST.
+4. Keep the generated **Client ID**. It is this browser's identity; do not reuse Core's Peer ID.
+ Current Settings registers the browser when you save, so no manual SQL insert is needed.
+5. Set **Extension Registry URL** to `https://registry.inkcre.dev`, then choose **Save**. Check the
+ **Clients** list for your Core instance and its online state. If it is offline, wake Core and
+ refresh before trying a delegated operation.
+
+The connection belongs to this browser origin. Set up another browser/device separately. An
+**Export** excludes the secret and is not a backup of your information.
+
+**Checkpoint:** Settings saves successfully and you can open **Sources**. An empty list is normal
+for a new instance; a connection error is not an empty list.
+
+
+
+
+## Connect your Agent or terminal
+
+Follow [Connect the CLI](/guide/connect-cli) to install `inkcre-cli`, configure a private named
+connection, and verify access. Give that connection only to an Agent you trust with instance
+authority. Its model provider may receive the information it reads.
+
+You can ask your Agent to perform a specific operation from a guide, then inspect the resulting
+Source or Job in the Web app. Do not share connection files or JWT secrets in public conversations.
+
+
+
+
+Next: [Collect Your First Source](/guide/first-source).
diff --git a/website/content/en/guide/daily-use.md b/website/content/en/guide/daily-use.md
new file mode 100644
index 0000000..8f004f7
--- /dev/null
+++ b/website/content/en/guide/daily-use.md
@@ -0,0 +1,62 @@
+---
+title: Use Your Information
+description: Connect the Web app and everyday tools, with optional AI organization.
+---
+
+# Use Your Information
+
+Start with a working instance and [a successful search](/guide/search). Retain your Core URL,
+PostgREST URL, and private JWT secret. If someone else operates the instance, ask them to authorize
+your access rather than assuming you may connect a browser Peer.
+
+## Read and explore in the Web app
+
+First [connect the Web app](/guide/connect). Current Settings generates and registers the browser's
+own Client ID; do not manually create a Peer or reuse Core's identity.
+
+1. Open **Info Base** and search for the phrase that worked in the [search guide](/guide/search).
+2. Select a result, use **View content** where supported, and explore its relationships in the
+ graph. The list is a search surface, not every stored Block. Some rich renderers need a
+ compatible browser Extension; an Agent can use the CLI's `get_text` to read Core-resolved text.
+3. Bookmark the app. A new browser/device needs its own connection. **Export** omits the secret and
+ is not an info-base backup.
+
+## Use your information from a terminal or AI tool
+
+Keep `inkcre-cli recall 'your clue' --mode lexical` available wherever you work. A terminal-based
+assistant you trust can use the installed CLI and named connection. For example:
+
+> Use my InKCre personal connection to find articles I saved about distributed systems. Read the
+> matches and cite their original links. Do not change my sources or data.
+
+The connection has owner authority; only give it to a tool you trust. Its model provider may receive
+the content it reads.
+
+## Connect a Sink to ChatGPT
+
+A Sink exposes InKCre capabilities to an external tool. Follow
+[Connect ChatGPT through MCP](/guide/sinks/chatgpt) to create the MCP Sink, run Secure MCP Tunnel,
+add the ChatGPT connection, and verify a real retrieval. It explains the separate credentials and
+what must keep running; no CLI `sink` command is required.
+
+For another MCP host that can send a Bearer PAT directly, the
+[Core MCP Sink reference](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/mcp-sink.md)
+owns the endpoint and authentication contract.
+
+These paths make your saved information available on demand. They do not configure proactive
+notifications or a daily briefing.
+
+## Add AI organization when you need it
+
+Once collection and reading work, configure a model provider and Agent for rumination, then
+explicitly reconsider a Block. Follow the
+[Core organization configuration](https://github.com/InKCre/core-py/blob/main/docs/30-unit-tdd/organization.md)
+for the Agent and deployment settings before using **Ruminate** or
+`inkcre-cli organization ruminate BLOCK_ID`.
+
+Inspect the Job and graph afterward. A valid result may add nothing. Re-index after new content is
+added to find it by words. Semantic search additionally needs an embedding provider/profile and
+maintenance; an LLM API key alone does not enable it. Selected content leaves your instance for
+configured AI providers and may incur charges.
+
+Next: [Troubleshooting](/guide/troubleshooting).
diff --git a/website/content/en/guide/extensions.md b/website/content/en/guide/extensions.md
new file mode 100644
index 0000000..bc70e72
--- /dev/null
+++ b/website/content/en/guide/extensions.md
@@ -0,0 +1,67 @@
+---
+title: Prepare an Extension
+outline: false
+description: Install a collector and enable it on the client that will run it.
+---
+
+# Prepare an Extension
+
+Start with a [connected interface](/guide/connect). Your source guide supplies an Extension name, an
+exact compatible version, and its Source type. An Extension is installed once in the deployment;
+enabled clients share that version and configuration. Only install code you trust with the host's
+authority. Do not change a working installation merely to match an example.
+
+
+
+
+## Prepare the Core collector
+
+1. Open **Extensions**. In **Control Extension on Client**, select your online **Core** client.
+2. Look for the name from your source guide. If it is absent, enter its **Extension Name** and exact
+ **Version** in **Install New Extension**, then select **Install Extension**. The selected Core
+ validates the Python release; Python-only collectors such as RSS do not need a browser package.
+3. Turn on the Extension's switch while Core is still selected. Installation alone does not start
+ it. Enabling only **This browser** does not start a Core collector.
+4. If the guide requires Extension-wide settings, open **Edit Config**, enter its configuration
+ object, and save. Source account settings belong in **Sources**, unless the guide says otherwise.
+5. Open **Sources**, create a Source, and check that its **Type** is available. A listed
+ installation alone does not prove that the collector is running.
+
+An offline client cannot validate an installation. If installation fails, keep your entered name and
+version, read the error, and check the selected client and release compatibility before trying
+again. An older Core may need an update to support installation through this Web control. Do not
+switch to **This browser** as a workaround for a Python-only collector.
+
+For an existing installation at another version, use **Change Version** only after checking both
+Hosts and disabling every client using it. Version and configuration are shared across the instance.
+
+## Browser Extensions and setup wizards
+
+A browser Extension adds capabilities such as content rendering or a **Setup** wizard. For a release
+with compatible Python **and** browser distributions, enable it on Core, then select **This
+browser** and enable it there too. Open **Setup** when that button appears.
+
+Do not install a different shared version to obtain a wizard without checking both Hosts. The
+[Twitter guide](/guide/sources/twitter) walks through its paired Core and browser setup.
+
+
+
+
+## Install and enable on Core
+
+Use the name and version in your source guide. For example, RSS on Core Host SDK `0.3.x`:
+
+```sh
+inkcre-cli extension install inkcre/rss --version 0.2.1
+inkcre-cli extension enable inkcre/rss
+inkcre-cli source types
+```
+
+For an existing installation, inspect `inkcre-cli extension get inkcre/rss` first. Version changes
+affect all clients using the installation. Configuration and command output may contain credentials;
+keep them private.
+
+
+
+
+Return to [your source's guide](/guide/sources) for its settings and first-run checkpoint.
diff --git a/website/content/en/guide/first-source.md b/website/content/en/guide/first-source.md
new file mode 100644
index 0000000..6590c23
--- /dev/null
+++ b/website/content/en/guide/first-source.md
@@ -0,0 +1,39 @@
+---
+title: Collect Your First Source
+description: Choose a source and complete your first collection and search.
+---
+
+# Collect Your First Source
+
+Start with a working instance and a [connected Web app or Agent](/guide/connect). The guides default
+to Web instructions, with a CLI / Agent alternative. You do not need to connect every account at
+once: choose one small source with an item you will recognize.
+
+## Choose your first source
+
+[RSS or Atom](/guide/sources/rss) is a useful first choice because a public feed needs no account
+credentials. If you prefer your own saved information, start with
+[GitHub Stars](/guide/sources/github), [email](/guide/sources/mail),
+[Telegram](/guide/sources/telegram), or [Twitter / X bookmarks](/guide/sources/twitter). Each guide
+includes its own prerequisites and setup.
+
+An **Extension** supplies a collector implementation; a **Source** is one configured use of it. For
+example, install the RSS Extension once, then create one Source per feed. Installing it does not
+automatically enable it or collect anything. [Prepare the Extension](/guide/extensions) on Core,
+then configure the Source in the Web app. Select the online Core client when installing a
+Python-only collector; the browser does not need to run that Extension itself.
+
+## Complete the first loop
+
+1. Follow your chosen source's guide to enable its Extension and create the Source. Open its details
+ in the Web app, or retain the returned **Source ID** when using the CLI.
+2. [Run a Collection](/guide/collect) and wait for the returned **Job ID** to finish. These are
+ different IDs: the Source persists across runs; each Job represents one run.
+3. [Find What You Saved](/guide/search): maintain the lexical index and search for a known item.
+4. Only after that works, [schedule collection and indexing](/guide/schedules).
+
+**Done means you retrieved a real item**, not just that installation or a Job succeeded. A
+successful empty collection may be normal; each source guide explains what is eligible for
+collection.
+
+Next: [Connect More Sources](/guide/sources), or [Use Your Information](/guide/daily-use).
diff --git a/website/content/en/guide/schedules.md b/website/content/en/guide/schedules.md
new file mode 100644
index 0000000..d4430dc
--- /dev/null
+++ b/website/content/en/guide/schedules.md
@@ -0,0 +1,87 @@
+---
+title: Schedule Collection and Indexing
+outline: false
+description: Keep sources and search indexes current with explicit schedules.
+---
+
+# Schedule Collection and Indexing
+
+Start with a [connected interface](/guide/connect). Choose Web for your own setup, or CLI / Agent
+for terminal instructions.
+
+
+
+
+## Schedule collection in the Web app
+
+First complete [a manual collection](/guide/collect) and [find a known item](/guide/search).
+
+1. Open the Source's details and inspect existing schedules. Twitter's wizard already creates a
+ daily schedule; do not add a duplicate.
+2. In **Cron schedule**, enter `0 * * * *` and choose **Schedule ordinary collect**. Newer layouts
+ put this under **Schedules → Add schedule**. It collects at minute zero of every hour.
+3. Check the resulting schedule and subsequent Job. Core must be running; closing the browser is
+ fine. Sleeping hosts miss occurrences and do not automatically catch up.
+4. To stop future runs from the Web, delete the specific schedule after checking its Source.
+ Existing Jobs and collected information remain. An Agent can instead disable a Cron through the
+ CLI if you want to retain it for later re-enabling.
+
+## Keep the search index current
+
+The Source schedule does **not** maintain the lexical index. Ask your operator or Agent to create
+one separate index-maintenance schedule if the instance does not already have it:
+
+```sh
+inkcre-cli cron create --job-type core.feature_retrieval.lexical.maintain.v1 --input-json '{"schedule":"*/10 * * * *","job_parameters":{}}'
+```
+
+This is currently a CLI operation. Inspect the resulting Cron and its `last_job`, and avoid
+duplicate schedules. Independent indexing means newly collected items may not be searchable
+immediately.
+
+Next: [Connect More Sources](/guide/sources).
+
+
+
+
+First complete [one collection](/guide/first-source) and [a successful search](/guide/search). Keep
+the Source ID returned when you created the Source.
+
+After the manual run works, create `collect-hourly.json`, replacing `42` with your Source ID:
+
+```json
+{
+ "schedule": "0 * * * *",
+ "job_parameters": { "source": 42, "config": {} }
+}
+```
+
+```sh
+inkcre-cli cron create --job-type core.source.collect.v1 --input collect-hourly.json
+```
+
+This five-field schedule means “at minute zero of every hour.” Add independent index maintenance so
+later items become searchable. Save this as `index-periodically.json`:
+
+```json
+{
+ "schedule": "*/10 * * * *",
+ "job_parameters": {}
+}
+```
+
+```sh
+inkcre-cli cron create --job-type core.feature_retrieval.lexical.maintain.v1 --input index-periodically.json
+inkcre-cli cron list
+```
+
+Create each schedule once. Inspect its returned Cron ID with `inkcre-cli cron get ID`; `last_job`
+identifies the Job to check. Pause it with `inkcre-cli cron disable ID`. Replace `ID` with the
+actual number. Your terminal may be closed, but Core must run. Sleeping hosts miss occurrences and
+do not automatically catch up. Independent indexing also means new items may not become searchable
+immediately.
+
+Next: [Add more sources](/guide/sources).
+
+
+
diff --git a/website/content/en/guide/search.md b/website/content/en/guide/search.md
new file mode 100644
index 0000000..ac3e18f
--- /dev/null
+++ b/website/content/en/guide/search.md
@@ -0,0 +1,82 @@
+---
+title: Find What You Saved
+outline: false
+description: Index collected information and retrieve it by remembered words.
+---
+
+# Find What You Saved
+
+Start with a [connected interface](/guide/connect). Choose Web for your own setup, or CLI / Agent
+for terminal instructions.
+
+
+
+
+## Find information in the Web app
+
+First finish [one collection](/guide/first-source).
+
+1. Ask your operator or trusted Agent to run lexical-index maintenance after collection. This is
+ currently a CLI operation, not a button in the Web app:
+
+```sh
+inkcre-cli job create --type core.feature_retrieval.lexical.maintain.v1 --input-json '{"parameters":{}}'
+inkcre-cli job wait JOB_ID --for 30s
+```
+
+Replace `JOB_ID` with the returned number. Inspect its state and diagnostics; maintenance handles a
+bounded batch, so a larger import can need another run. An [index schedule](/guide/schedules) avoids
+needing this manual step for every collection.
+
+2. Open **Info Base** in client-web. Enter a distinctive phrase from the collected item and search.
+3. Select a result, inspect its details, use **View content** where supported, and follow its graph
+ relationships. Some content needs a compatible browser Extension for rich rendering; an Agent can
+ also retrieve Core-resolved text through the CLI.
+
+**Checkpoint:** you found a real item and its source relationships. Collection and indexing are
+separate; an AI key is not required for lexical search. Source-derived relationships do not mean
+that InKCre has automatically summarized or reorganized everything with AI.
+
+Next: [Schedule Collection and Indexing](/guide/schedules).
+
+
+
+
+You need a connected CLI and at least one completed collection. If you have not collected anything
+yet, start with [your first source](/guide/first-source).
+
+Collection and indexing are separate. Create a lexical-index maintenance Job after collection to
+enable search by remembered words, without an AI provider:
+
+```sh
+inkcre-cli job create --type core.feature_retrieval.lexical.maintain.v1 --input-json '{"parameters":{}}'
+```
+
+Use its returned Job ID with `inkcre-cli job wait JOB_ID --for 30s`, replacing `JOB_ID` with the
+number. Check its status and `state` for indexed records and diagnostics. Maintenance processes a
+bounded batch; repeat if you imported more than one batch can index.
+
+Copy a distinctive phrase from an item your source collected and search:
+
+```sh
+inkcre-cli recall 'A phrase from your saved item' --mode lexical
+```
+
+Read a returned Block's content and relationships, replacing `123` with that Block's ID:
+
+```sh
+inkcre-cli resolver invoke block:123 --method get_text
+inkcre-cli graph neighborhood block:123
+```
+
+**Checkpoint:** you can retrieve a real item you recognize and see its source relationships. You
+have completed the first loop: source → saved information → information you can use.
+
+This first form of organization comes from the source: feed items belong to feeds, GitHub
+repositories can belong to Lists, and mail has sender and mailbox relationships. It does not mean
+InKCre has already summarized, tagged, or reorganized everything with AI.
+
+Next: [Keep collecting and searching](/guide/schedules).
+
+
+
diff --git a/website/content/en/guide/sinks/chatgpt.md b/website/content/en/guide/sinks/chatgpt.md
new file mode 100644
index 0000000..902c1c3
--- /dev/null
+++ b/website/content/en/guide/sinks/chatgpt.md
@@ -0,0 +1,196 @@
+---
+title: Connect ChatGPT through MCP
+description: Use the InKCre MCP Sink and Secure MCP Tunnel to retrieve your information in ChatGPT.
+---
+
+# Connect ChatGPT through MCP
+
+A **Sink** makes InKCre capabilities available to another tool. This guide connects the MCP Sink to
+ChatGPT through OpenAI's Secure MCP Tunnel, so you can ask ChatGPT to find and read information from
+your instance. It is an on-demand connection, not a scheduled digest or notification service.
+
+The connection follows this path:
+
+```text
+ChatGPT → OpenAI Secure MCP Tunnel → your running tunnel-client → InKCre MCP Sink
+```
+
+The tunnel client must be able to reach Core. It can run on your computer or another machine you
+operate; it does not have to run beside Core. Keep it running whenever ChatGPT needs the connection.
+
+## Before you start
+
+- Have a working instance and complete [a known-item search](/guide/search) first. Retain its Core
+ URL and private JWT secret. If someone else operates the instance, ask them to perform the Sink
+ setup rather than requesting their administrator credentials.
+- Install the CLI environment from [Connect the CLI](/guide/connect-cli). The setup example below
+ uses its installed Python dependencies because the CLI does not yet have a `sink` command.
+- Confirm that your ChatGPT account/workspace permits developer-mode MCP connections and that you
+ can access [Platform Tunnels](https://platform.openai.com/settings/organization/tunnels).
+ Availability and UI labels depend on OpenAI's rollout and workspace policy; check the current
+ [developer-mode requirements](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt)
+ if an option is missing.
+
+Connecting authorizes ChatGPT to use the Sink's exposed capabilities and receive returned content.
+Only connect an instance whose information you intend to share with that ChatGPT account/workspace.
+The Sink PAT is not a per-user data-isolation boundary. Keep the tunnel machine and its credentials
+under your control; someone who obtains the PAT can invoke the Sink if they can reach its endpoint.
+
+Keep these credentials distinct:
+
+| Value | Purpose | Where it belongs |
+| ----------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------- |
+| InKCre `JWT_SECRET` | Signs administrator requests to create and enable a Sink. | Local setup only; never enter it into ChatGPT or tunnel settings. |
+| MCP Sink PAT | Admits calls to this MCP endpoint. | Sink configuration and the tunnel client's downstream Authorization header. |
+| OpenAI tunnel runtime API key | Lets the client use the provisioned tunnel. | Tunnel client; not the Sink config. |
+
+## 1. Create and enable the MCP Sink
+
+Create a new random PAT in your password manager and save it. Do not reuse `JWT_SECRET` or an OpenAI
+key. If you already have an enabled MCP Sink and its PAT, reuse them and skip this step.
+
+In a private local folder, save this as `create-mcp-sink.py`. Activate the CLI's Python environment
+and run `python create-mcp-sink.py`. The prompts hide both secrets; nothing is stored in the script.
+
+```python
+import getpass
+import time
+
+import httpx
+import jwt
+
+core_url = input("Core HTTPS URL: ").strip().rstrip("/")
+if not core_url.startswith("https://"):
+ raise ValueError("Use your trusted Core HTTPS URL, not the PostgREST URL")
+secret = getpass.getpass("InKCre JWT_SECRET: ")
+pat = getpass.getpass("New MCP Sink PAT from your password manager: ")
+if not secret or not pat:
+ raise ValueError("Both credentials are required")
+issued = int(time.time()) - 5
+token = jwt.encode(
+ {"role": "authenticated", "iss": "inkcre-peer", "aud": "inkcre-api",
+ "iat": issued, "exp": issued + 900},
+ secret, algorithm="HS256",
+)
+with httpx.Client(timeout=30, headers={"Authorization": f"Bearer {token}"}) as client:
+ created = client.post(f"{core_url}/sinks", json={
+ "type": "core.mcp.v1", "nickname": "ChatGPT", "config": {"pat": pat},
+ })
+ created.raise_for_status()
+ sink_id = created.json()["id"]
+ print(f"Created Sink ID: {sink_id}; retain this ID before continuing.")
+ enabled = client.post(f"{core_url}/sinks/{sink_id}/enable")
+ enabled.raise_for_status()
+ print(f"MCP endpoint: {core_url}/sinks/{sink_id}/mcp")
+```
+
+Save the printed Sink ID and MCP endpoint. Enabling applies to the Core Peer you contacted. Do not
+append this path to the PostgREST URL or to `/readyz`.
+
+If a request times out or enabling fails, inspect the existing Sink before repeating creation: the
+first write may already have succeeded. The operator can use authenticated `GET /sinks` and
+`POST /sinks/{id}/enable` with the same short-lived Bearer JWT pattern. Do not publish a Sink
+management response; it can contain configuration credentials. The authoritative API and lifecycle
+details are in
+[MCP Sink Operations](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/mcp-sink.md).
+
+## 2. Provision a tunnel and install its client
+
+1. Open [Platform Tunnels](https://platform.openai.com/settings/organization/tunnels) in the
+ organization associated with your ChatGPT account/workspace. Create a tunnel and retain its
+ `tunnel_id`. If tunnel creation is unavailable, ask the organization administrator for access.
+2. Obtain a runtime API key whose principal can **Read** and **Use** that tunnel. Creating tunnels
+ needs **Manage** permission; do not use an admin API key for the long-running client.
+3. Download the supported `tunnel-client` for your operating system from the Tunnels page, extract
+ it, and place the executable on your `PATH`. Verify `tunnel-client --version` in a terminal.
+
+The
+[official tunnel onboarding guide](https://github.com/openai/tunnel-client/blob/master/docs/onboarding.md)
+owns account setup, downloads, and permissions. Keep the tunnel in the organization/workspace that
+your ChatGPT session can access; an otherwise healthy tunnel in another account will not appear.
+
+## 3. Start the tunnel client
+
+The following commands use **Bash** on macOS/Linux or in WSL on Windows. Run `bash` first if your
+terminal uses another shell. Enter secrets at the hidden prompts instead of pasting them into
+commands saved in shell history:
+
+```bash
+read -r -s -p 'MCP Sink PAT: ' INKCRE_MCP_PAT; printf '\n'
+export INKCRE_MCP_AUTHORIZATION="Bearer $INKCRE_MCP_PAT"
+unset INKCRE_MCP_PAT
+read -r -s -p 'OpenAI tunnel runtime API key: ' CONTROL_PLANE_API_KEY; printf '\n'
+export CONTROL_PLANE_API_KEY
+
+tunnel-client run \
+ --control-plane.tunnel-id='YOUR-TUNNEL-ID' \
+ --control-plane.api-key='env:CONTROL_PLANE_API_KEY' \
+ --mcp.server-url='https://YOUR-CORE-HOST/sinks/YOUR-SINK-ID/mcp' \
+ --mcp.extra-headers='Authorization: env:INKCRE_MCP_AUTHORIZATION' \
+ --mcp.discovery-extra-headers='Authorization: env:INKCRE_MCP_AUTHORIZATION' \
+ --health.listen-addr='127.0.0.1:8080'
+```
+
+Replace the tunnel ID and server URL before running. The environment value contains the complete
+`Bearer ` header value: the `env:` reference does not add the Bearer prefix. Both header flags
+are needed because discovery/probes and regular calls reach the protected Sink separately. See the
+[tunnel configuration reference](https://github.com/openai/tunnel-client/blob/master/docs/configuration.md).
+
+In another terminal, run `curl --fail http://127.0.0.1:8080/readyz`; continue when it returns
+HTTP 200. The local diagnostics UI is at `http://127.0.0.1:8080/ui`. If port 8080 is occupied,
+choose another loopback port in the command and both URLs. Keep this listener local; do not expose
+its UI as the MCP endpoint. Core must remain reachable too, including when hosted on a sleeping
+plan.
+
+## 4. Add the connection in ChatGPT
+
+1. In ChatGPT, enable **Developer mode** in **Settings → Security and login**. Some workspace
+ interfaces expose it through Apps settings instead; follow the linked developer-mode help for
+ your account if the labels differ.
+2. Open [ChatGPT Plugins](https://chatgpt.com/plugins), select the add button, and name the
+ connection `InKCre`. Under **Connection**, choose **Tunnel** and select your tunnel or enter its
+ ID.
+3. Create the connection and review its discovered tools. If your interface offers **Scan Tools**,
+ run it. Expect InKCre tools such as `inkcre_recall` and `inkcre_read_blocks`, not the tunnel
+ client's embedded demo tools. Do not paste the InKCre JWT secret into ChatGPT.
+4. Start a new chat using **Try in chat**, or select InKCre in the message's tools menu.
+
+These steps follow OpenAI's
+[connection guide](https://developers.openai.com/plugins/deploy/connect-chatgpt). This is a private
+developer-mode connection, not publication in a public plugin directory.
+
+## 5. Verify a real retrieval
+
+Choose an item that you already found with the CLI and ask:
+
+> Use InKCre to search for “[a distinctive phrase from my saved item]” using lexical retrieval. Read
+> the most relevant result, summarize it, and cite its original source. Do not change my data.
+
+Confirm that ChatGPT actually calls InKCre tools, reads the expected item, and grounds its answer in
+that content. A plausible answer without a tool call is not a connection test. Select InKCre again
+for a later message that needs new retrieval; do not assume selection persists across messages.
+
+The previous InKCre acceptance exercised real ChatGPT tool calls through this tunnel path. It did
+**not** establish automatic Skill import or proactive use: the server's `use-inkcre` Skill was
+readable by an MCP client, but the tested ChatGPT scan did not import it. Treat discovered tools and
+a successful real task as the checkpoint, not the presence of a Skill. Semantic retrieval separately
+requires an embedding configuration; an unavailable semantic mode does not invalidate lexical
+results.
+
+## Troubleshooting and stopping access
+
+| Symptom | What to check |
+| ------------------------------------------ | ----------------------------------------------------------------------------------------- |
+| Tunnel is not selectable | Match the Platform organization and ChatGPT account/workspace; verify tunnel permissions. |
+| Sink responds `401` | Check the PAT and complete Bearer prefix in both runtime and discovery headers. |
+| Sink responds `404` | Check Sink ID, Core origin, and whether it is enabled on that Peer. |
+| Local tunnel readiness fails | Inspect its local UI/logs, runtime-key permissions, and reachability of Core. |
+| Tools appear but ChatGPT does not use them | Attach InKCre to that message and request a known-item retrieval explicitly. |
+| Search finds nothing | Repeat the CLI search and index maintenance from [Find What You Saved](/guide/search). |
+
+Refresh the ChatGPT connection after changing server tool metadata, then test in a new chat.
+Stopping `tunnel-client` interrupts this tunnel path; closing its terminal or sleeping its host does
+the same. Remove the connection from ChatGPT when no longer needed. To revoke the endpoint itself,
+the operator can call authenticated `POST /sinks/{id}/disable`; disable before deleting a Sink. For
+a leaked PAT, rotate the Sink config and update the tunnel environment before restarting it. Never
+disable PAT authentication just to make discovery pass.
diff --git a/website/content/en/guide/sources.md b/website/content/en/guide/sources.md
new file mode 100644
index 0000000..0c39c3f
--- /dev/null
+++ b/website/content/en/guide/sources.md
@@ -0,0 +1,47 @@
+---
+title: Connect More Sources
+description: Choose an independent setup guide for each information source.
+---
+
+# Connect More Sources
+
+Use the guide for the information you want to bring into InKCre. Each page starts from a working
+instance and a [connected interface](/guide/connect); you do not need to complete the RSS tutorial
+first.
+
+Choose **Web app** for your own setup or **CLI / Agent** for terminal instructions. The choice
+follows you between guide pages. Shared prerequisites and source limitations apply to both. The
+[Extension guide](/guide/extensions) distinguishes browser setup from Core installation and calls
+out operations that still need an operator or Agent.
+
+| What you want to collect | Setup guide | What you need |
+| -------------------------------------------- | ------------------------------------------------ | ---------------------------------------- |
+| Articles from a publication | [RSS and Atom](/guide/sources/rss) | A feed URL |
+| Saved repositories and Lists | [GitHub Stars and Lists](/guide/sources/github) | A GitHub personal access token |
+| New or historical mail | [Email over IMAP](/guide/sources/mail) | IMAP access and supported credentials |
+| Text and messages you forward | [Telegram inbox](/guide/sources/telegram) | A dedicated bot and your numeric user ID |
+| Your bookmarked posts | [Twitter / X bookmarks](/guide/sources/twitter) | An X OAuth app and API access |
+| Notes you write in a Memos-compatible client | [Memos-compatible capture](/guide/sources/memos) | A supported client and a dedicated PAT |
+
+Memos is a write-in capture interface, not a collector that imports an existing Memos server. An
+arbitrary webpage, social account, or service is not automatically a supported Source.
+
+## Add one source at a time
+
+Install and enable its Core Extension, complete any account authorization, create the Source, then
+[collect once](/guide/collect). [Index and search](/guide/search) for an item you recognize before
+[scheduling](/guide/schedules). More successful Jobs do not by themselves prove that more
+information was imported.
+
+The guides use compatible Extension versions for Core Host SDK `0.3.x`. Inspect existing
+installations before changing versions and consult the linked Registry release listing for
+compatibility. Keep local configuration files, tokens, and management-command output private.
+
+## Build a missing collector
+
+An Extension can add a Source without becoming part of the Core repository. If you know Python and
+want to connect another service, follow
+[Build a Source Extension](/developer/ecosystem/source-extension). That is the ecosystem developer
+path; changing InKCre itself has a separate [contributor guide](/developer/contributing).
+
+Next: [Use Your Information](/guide/daily-use).
diff --git a/website/content/en/guide/sources/github.md b/website/content/en/guide/sources/github.md
new file mode 100644
index 0000000..f7e96ef
--- /dev/null
+++ b/website/content/en/guide/sources/github.md
@@ -0,0 +1,79 @@
+---
+title: GitHub Stars and Lists
+outline: false
+description: Connect your GitHub saves to InKCre.
+---
+
+# GitHub Stars and Lists
+
+Start with a [connected interface](/guide/connect). Choose Web for your own setup, or CLI / Agent
+for terminal instructions.
+
+
+
+
+## Set up in the Web app
+
+1. Create a personal access token for the account whose Stars and Lists you want to collect, using
+ [GitHub's token instructions](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).
+ Its account and permissions determine what the collector can see.
+2. [Prepare the Extension](/guide/extensions): `inkcre/github` version `0.3.1` on Core Host SDK
+ `0.3.x`. Select the online Core client to install and enable the collector.
+3. Open **Sources** and the create-source form. Enter **Nickname** `My GitHub saves` and choose
+ **Type** `extensions.github.stars.Source`.
+4. Enter this object in **Config**, replace the token, and save/create:
+
+```json
+{ "github_token": "YOUR-GITHUB-TOKEN" }
+```
+
+5. Open the saved Source and [collect once](/guide/collect). [Index and search](/guide/search) for a
+ repository you starred before [adding a schedule](/guide/schedules).
+
+Keep your token and configuration private. Each run synchronizes current Stars and Lists; this is
+not a code backup or a full GitHub activity archive.
+
+
+
+
+Start with a working instance and a [connected CLI](/guide/connect-cli). The version below targets
+Core Host SDK `0.3.x`; check the linked release listing for other Host versions. If already
+installed, inspect `inkcre-cli extension get inkcre/github` before changing it. Keep credential
+files and command output private.
+
+1. Create a personal access token for the account whose Stars and Lists you want to collect,
+ following
+ [GitHub's token instructions](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).
+ Its account and permissions determine which data is visible.
+2. Enable the Extension:
+
+ ```sh
+ inkcre-cli extension install inkcre/github --version 0.3.1
+ inkcre-cli extension enable inkcre/github
+ ```
+
+3. Save `github.json` with your token:
+
+ ```json
+ {
+ "nickname": "My GitHub saves",
+ "config": { "github_token": "YOUR-GITHUB-TOKEN" }
+ }
+ ```
+
+ ```sh
+ inkcre-cli source create --type extensions.github.stars.Source --input github.json
+ ```
+
+4. Collect the returned Source ID using [Run a Collection](/guide/collect). After indexing, search
+ for a repository you starred. Each run synchronizes current Stars and Lists; it is not a code
+ backup or a full GitHub activity archive.
+
+See the [GitHub collector](https://github.com/InKCre/core-py/blob/main/extensions/github/README.md)
+and [published releases](https://registry.inkcre.dev/v1/extensions/inkcre/github).
+
+Next: [index and search for a known item](/guide/search), then
+[schedule collection](/guide/schedules).
+
+
+
diff --git a/website/content/en/guide/sources/mail.md b/website/content/en/guide/sources/mail.md
new file mode 100644
index 0000000..a185e1e
--- /dev/null
+++ b/website/content/en/guide/sources/mail.md
@@ -0,0 +1,118 @@
+---
+title: Email over IMAP
+outline: false
+description: Connect your IMAP mailbox to InKCre.
+---
+
+# Email over IMAP
+
+Start with a [connected interface](/guide/connect). Choose Web for your own setup, or CLI / Agent
+for terminal instructions.
+
+
+
+
+## Set up in the Web app
+
+1. Find your provider's IMAP hostname and enable IMAP if required. Obtain an app-specific password
+ where supported. An ordinary password cannot replace an unsupported authentication method.
+2. [Prepare the Extension](/guide/extensions): `inkcre/mail` version `0.3.1` on Core Host SDK
+ `0.3.x`. Select the online Core client to install and enable the collector.
+3. Open **Sources** and the create-source form. Use **Nickname** `My mail` and **Type**
+ `extensions.mail.source.Source`.
+4. Paste this into **Config**, replace the host and credentials, and save/create:
+
+```json
+{
+ "protocol": "imap",
+ "parameters": {
+ "host": "YOUR-IMAP-HOST",
+ "port": 993,
+ "security": "tls",
+ "username": "YOUR-MAIL-LOGIN",
+ "password": "YOUR-APP-PASSWORD"
+ },
+ "ordinary_mark_as_seen": false,
+ "synchronize_deletions": false
+}
+```
+
+5. Send yourself a test message **after creating the Source**, then [collect once](/guide/collect).
+ Ordinary collection begins with new mail; an empty result does not prove authentication failed.
+ Inspect the Job's mailbox diagnostics. These settings preserve unread state.
+6. To collect older mail, open the Source's new-collection Job dialog, select **Historical
+ backfill**, and enter a small date range as its run Config:
+
+```json
+{ "since": "2026-09-01", "before": "2026-09-08" }
+```
+
+Choose dates appropriate to your mailbox. The start is included and the end excluded. Observe that
+Job, then [index and search](/guide/search) for a known email. This collector does not send mail or
+generate email digests. Keep credentials and diagnostics private.
+
+
+
+
+Start with a working instance and a [connected CLI](/guide/connect-cli). The version below targets
+Core Host SDK `0.3.x`; check the linked release listing for other Host versions. If already
+installed, inspect `inkcre-cli extension get inkcre/mail` before changing it. Keep credential files
+and command output private.
+
+1. Find your provider's IMAP hostname and enable IMAP if required. Obtain an app-specific password
+ where supported. Accounts requiring an unsupported authentication method cannot be connected by
+ substituting an ordinary login password.
+2. Install and enable the Core Extension:
+
+ ```sh
+ inkcre-cli extension install inkcre/mail --version 0.3.1
+ inkcre-cli extension enable inkcre/mail
+ ```
+
+3. Save `mail.json`, replacing the host and credentials:
+
+ ```json
+ {
+ "nickname": "My mail",
+ "config": {
+ "protocol": "imap",
+ "parameters": {
+ "host": "YOUR-IMAP-HOST",
+ "port": 993,
+ "security": "tls",
+ "username": "YOUR-MAIL-LOGIN",
+ "password": "YOUR-APP-PASSWORD"
+ },
+ "ordinary_mark_as_seen": false,
+ "synchronize_deletions": false
+ }
+ }
+ ```
+
+ ```sh
+ inkcre-cli source create --type extensions.mail.source.Source --input mail.json
+ ```
+
+4. Send yourself a test email **after creating the Source**, then [collect its ID](/guide/collect).
+ Ordinary collection starts with new mail; an empty first run does not imply login failed. Inspect
+ mailbox diagnostics even if the Job finishes. This example preserves unread state; the
+ collector's default would mark ordinary collected mail as seen.
+5. For older mail, request a small explicit backfill. Replace `42` with the Mail Source ID and
+ choose dates for your mailbox:
+
+ ```sh
+ inkcre-cli source backfill 42 --input-json '{"since":"2026-09-01","before":"2026-09-08"}'
+ ```
+
+ The start date is included and the end date excluded. Observe the Job and index afterward. This
+ collector does not send mail or create email digests.
+
+See the
+[Mail configuration](https://github.com/InKCre/core-py/blob/main/docs/30-unit-tdd/mail-extension.md)
+and [published releases](https://registry.inkcre.dev/v1/extensions/inkcre/mail).
+
+Next: [index and search for a known item](/guide/search), then
+[schedule collection](/guide/schedules).
+
+
+
diff --git a/website/content/en/guide/sources/memos.md b/website/content/en/guide/sources/memos.md
new file mode 100644
index 0000000..588b600
--- /dev/null
+++ b/website/content/en/guide/sources/memos.md
@@ -0,0 +1,108 @@
+---
+title: Memos-Compatible Capture
+outline: false
+description: Write notes into InKCre from a supported Memos client.
+---
+
+# Memos-Compatible Capture
+
+Start with a [connected interface](/guide/connect). Choose Web for your own setup, or CLI / Agent
+for terminal instructions.
+
+
+
+
+## Configure in the Web app
+
+This is a write-in capture endpoint, **not** an importer for an existing Memos server. It implements
+a bounded Memos `0.29.1` API subset; the previously accepted client is MoeMemos Android `2.0.4`.
+
+1. In **Extensions**, select your online Core under **Control Extension on Client** and install
+ `inkcre/memos` version `0.3.0` for Core Host SDK `0.3.x`. For an existing installation, follow
+ the version-change instructions in [Prepare an Extension](/guide/extensions).
+2. Select **This browser**, enable Memos there, and open **Setup**. The same release supplies the
+ browser setup and Core service; do not install a different version for each client.
+3. Choose your Core if more than one is available, then select **Prepare connection**. Setup
+ generates or reuses a dedicated PAT and enables Memos on that Core when necessary.
+4. Copy **Server URL** and **Personal Access Token** into the supported Memos client's account
+ setup. Use the complete URL shown, not PostgREST, and do not append `/api/v1`.
+5. Write a distinctive note, refresh the client, and reopen it. Then
+ [index and search](/guide/search) for the same text in InKCre.
+
+If setup reports a missing public address, set your Core's **Public HTTP Base URL** in **Clients →
+Config**, then refresh setup. The address must be reachable from your Memos client. The **Memos
+connection help** link opens the installed version's step-by-step guide; the Extension card also
+links its available global, Core, and Web documentation.
+
+There is no Source to create or collection schedule: the client writes notes directly. Index
+maintenance remains separate. Other client versions may use unsupported endpoints.
+
+To disconnect, remove the account from the client and rotate or clear the Extension's token.
+Disabling the Core Extension withdraws its routes; neither action erases saved notes.
+
+
+
+
+This is a capture endpoint for notes you write, **not** an importer for an existing Memos server. It
+implements a bounded Memos `0.29.1` API subset; the previously accepted client is MoeMemos Android
+`2.0.4`. Other clients or versions may use unsupported endpoints.
+
+## 1. Configure the Extension
+
+Start with a [connected CLI](/guide/connect-cli) and a Core HTTPS URL. Install the Core Host SDK
+`0.3.x` release, or inspect `inkcre-cli extension get inkcre/memos` if already installed:
+
+```sh
+inkcre-cli extension install inkcre/memos --version 0.3.0
+```
+
+Generate a dedicated PAT and save it in your password manager. Its required format is `memos_pat_`
+followed by exactly 32 ASCII letters or digits. For example, run locally:
+
+```sh
+python -c 'import secrets, string; print("memos_pat_" + "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(32)))'
+```
+
+In a private folder, save `memos.json`, replacing the placeholder with that PAT:
+
+```json
+{
+ "personal_access_token": "YOUR-GENERATED-MEMOS-PAT"
+}
+```
+
+```sh
+inkcre-cli extension config update inkcre/memos --input memos.json
+inkcre-cli extension enable inkcre/memos
+```
+
+The update response can contain the PAT; keep both it and the file private. This token authorizes
+the Memos interface, not general Core administration. Never substitute `JWT_SECRET`.
+
+## 2. Connect the client and write a note
+
+1. In the supported client's server/account setup, use `https://YOUR-CORE-HOST/memos` as the server
+ address and the PAT as its access token. Do not use your PostgREST URL or append `/api/v1`.
+2. Write a short note with distinctive text, such as “Memos capture verification orchid”.
+3. Refresh the client's list and reopen the note to confirm it was saved.
+4. [Maintain the index and search](/guide/search) for the same text from InKCre.
+
+There is no `source create` or collection Cron for this path: saving in the client sends the note
+directly to InKCre. Schedule index maintenance if you want new notes searchable without a manual
+run. The supported subset includes notes, attachments, and comments, not full Memos administration,
+social features, or browsing the entire InKCre info-base.
+
+## Disconnect or troubleshoot
+
+If login fails, verify the `/memos` server prefix, PAT format, and running Core Extension. An
+unsupported client endpoint can fail even with valid credentials; use the pinned client/API pairing
+before diagnosing the instance itself.
+
+Remove the account from the client and rotate or clear `personal_access_token` to revoke the old
+credential. Disable the Extension to withdraw its routes. These actions do not erase stored notes.
+
+See the [release listing](https://registry.inkcre.dev/v1/extensions/inkcre/memos) and the
+[Memos runtime contract](https://github.com/InKCre/core-py/blob/main/docs/30-unit-tdd/memos-extension.md).
+
+
+
diff --git a/website/content/en/guide/sources/rss.md b/website/content/en/guide/sources/rss.md
new file mode 100644
index 0000000..977be25
--- /dev/null
+++ b/website/content/en/guide/sources/rss.md
@@ -0,0 +1,104 @@
+---
+title: RSS and Atom
+outline: false
+description: Install the RSS collector and collect your first feed.
+---
+
+# RSS and Atom
+
+Start with a [connected interface](/guide/connect). Choose Web for your own setup, or CLI / Agent
+for terminal instructions.
+
+
+
+
+## Set up in the Web app
+
+Choose a publication's **RSS or Atom feed URL**, not its normal homepage.
+
+1. [Prepare the Extension](/guide/extensions): `inkcre/rss` version `0.2.1` for Core Host SDK
+ `0.3.x`. Select your online Core client to install it, then enable it there. No CLI or browser
+ Extension is needed for this collector.
+2. Open **Sources** and the create-source form. Set **Nickname** to `My first feed`.
+3. Choose **Type** `extensions.rss.rss.Source`, or `extensions.rss.atom.Source` for Atom.
+4. Paste this object into **Config**, replacing the feed URL:
+
+```json
+{
+ "feed_url": "https://YOUR-PUBLICATION/FEED",
+ "fetch_full_text": false,
+ "download_enclosures": false
+}
+```
+
+5. Save/create the Source, then open it from the list. [Run a Collection](/guide/collect) and
+ inspect its Job before [indexing and searching](/guide/search).
+
+The Config editor takes only the object above, not a CLI wrapper containing `nickname` and `config`.
+These options avoid extra article fetches and attachment downloads. A feed exposes only what its
+publisher currently provides; a successful Job is not an import of the full archive. After finding a
+known item, [schedule collection](/guide/schedules).
+
+
+
+
+Before you start, [connect the CLI](/guide/connect-cli) to your instance.
+
+Choose a publication you already read and copy its **RSS or Atom feed URL**. Its normal homepage URL
+is not usually a feed URL. Look for an RSS/Subscribe link on the publication.
+
+## Enable the collector
+
+Install the RSS Extension on Core, then enable it:
+
+```sh
+inkcre-cli extension install inkcre/rss --version 0.2.1
+inkcre-cli extension enable inkcre/rss
+inkcre-cli source types
+```
+
+Version `0.2.1` supports Core Host SDK `0.3.x`. For a different Host version, check the
+[RSS release listing](https://registry.inkcre.dev/v1/extensions/inkcre/rss) for a published release
+with a compatible `python.host_sdk_version`. Do not guess a version or use `latest`. If already
+installed, inspect `inkcre-cli extension get inkcre/rss` before changing it.
+
+The type list should contain `extensions.rss.rss.Source` and `extensions.rss.atom.Source`. In the
+Web app, select Core under **Control Extension on Client** to control the collector, not **This
+browser**.
+
+## Add and run the source
+
+1. Create `feed.json` in your local working folder:
+
+ ```json
+ {
+ "nickname": "My first feed",
+ "config": {
+ "feed_url": "https://YOUR-PUBLICATION/FEED",
+ "fetch_full_text": false,
+ "download_enclosures": false
+ }
+ }
+ ```
+
+ Replace the URL. These first-run settings collect feed content without extra article fetches or
+ attachment downloads.
+
+2. Create the Source, choosing the matching feed format:
+
+ ```sh
+ inkcre-cli source create --type extensions.rss.rss.Source --input feed.json
+ ```
+
+ For Atom, use `extensions.rss.atom.Source`. Record the returned Source `id`.
+
+3. [Run a Collection](/guide/collect) using that Source ID. The guide shows the collection command,
+ how to wait for its separate Job ID, and what to inspect if it fails.
+
+**Checkpoint:** the collection Job finished. A feed exposes only what its publisher currently
+provides; success does not mean its entire historical archive was imported.
+
+Next: [Find what you saved](/guide/search), then [schedule collection](/guide/schedules).
+
+
+
diff --git a/website/content/en/guide/sources/telegram.md b/website/content/en/guide/sources/telegram.md
new file mode 100644
index 0000000..726815e
--- /dev/null
+++ b/website/content/en/guide/sources/telegram.md
@@ -0,0 +1,101 @@
+---
+title: Send or forward messages from Telegram
+outline: false
+description: Connect a personal Telegram capture inbox to InKCre.
+---
+
+# Send or forward messages from Telegram
+
+Start with a [connected interface](/guide/connect). Choose Web for your own setup, or CLI / Agent
+for terminal instructions.
+
+
+
+
+## Set up in the Web app
+
+1. Create a dedicated bot with [@BotFather](https://t.me/botfather): send `/newbot`, follow the
+ prompts, and retain its token.
+2. Obtain your own numeric user ID using
+ [Telegram Desktop's data export](https://telegram.org/blog/export-and-more). Open **Settings →
+ Advanced → Export Telegram data**, include personal information, and choose JSON. Use
+ `personal_information.user_id`, not your `@username` or the bot's ID. Telegram may require
+ another-device confirmation or a waiting period.
+3. [Prepare the Extension](/guide/extensions): `inkcre/telegram` version `0.3.1` on Core Host SDK
+ `0.3.x`. Select the online Core client to install and enable it.
+4. Open **Sources** and the create-source form. Enter **Nickname** `My Telegram inbox` and choose
+ **Type** `extensions.telegram.source.Source`. Paste this into **Config**, replace both values,
+ and save/create:
+
+```json
+{
+ "bot_token": "YOUR-BOT-TOKEN",
+ "bound_user_id": 123456789,
+ "download_attachments": false
+}
+```
+
+5. Send a distinctive private message to the bot, then [collect once](/guide/collect). Saved
+ messages receive a 👍 reaction. [Index and search](/guide/search) for the text.
+6. [Schedule collection](/guide/schedules); `*/5 * * * *` polls every five minutes while Core runs.
+
+Use one bot per Source. This is an inbox, not group/channel history import or a notification
+destination. Attachments remain metadata-only unless `download_attachments` is enabled. Telegram
+retains updates for a limited time; keep another copy if your instance sleeps.
+
+
+
+
+Start with a working instance and a [connected CLI](/guide/connect-cli). The version below targets
+Core Host SDK `0.3.x`; check the linked release listing for other Host versions. If already
+installed, inspect `inkcre-cli extension get inkcre/telegram` before changing it. Keep credential
+files and command output private.
+
+1. Create a dedicated bot with [@BotFather](https://t.me/botfather): send `/newbot`, follow its
+ naming prompts, and retain the token. Obtain your own numeric user ID using
+ [Telegram Desktop's data export](https://telegram.org/blog/export-and-more): open **Settings →
+ Advanced → Export Telegram data**, include personal information, and choose JSON format. In the
+ exported JSON, use `personal_information.user_id`, not your `@username` or the bot's ID. Telegram
+ may require confirmation from another device or a waiting period.
+2. Install and enable the Core Extension:
+
+ ```sh
+ inkcre-cli extension install inkcre/telegram --version 0.3.1
+ inkcre-cli extension enable inkcre/telegram
+ ```
+
+3. Save `telegram.json`, replacing the token and example user ID:
+
+ ```json
+ {
+ "nickname": "My Telegram inbox",
+ "config": {
+ "bot_token": "YOUR-BOT-TOKEN",
+ "bound_user_id": 123456789,
+ "download_attachments": false
+ }
+ }
+ ```
+
+ ```sh
+ inkcre-cli source create --type extensions.telegram.source.Source --input telegram.json
+ ```
+
+4. Send a distinctive text message in a private chat with the bot, then
+ [collect the Source ID](/guide/collect). Successfully saved messages receive a 👍 reaction. After
+ indexing, search for its text.
+5. Add a schedule to forward messages without running the CLI each time. `*/5 * * * *` polls every
+ five minutes while Core is awake. Telegram retains updates for a limited time, so a long-sleeping
+ instance should not be your only copy.
+
+Use one bot for one Source. This is a capture inbox, not group/channel history import or a
+notification destination. Attachments are metadata-only in this example; enable
+`download_attachments` when you want their bytes saved too. See the
+[Telegram collector](https://github.com/InKCre/core-py/blob/main/extensions/telegram/README.md) and
+[published releases](https://registry.inkcre.dev/v1/extensions/inkcre/telegram).
+
+Next: [index and search for a known item](/guide/search), then
+[schedule collection](/guide/schedules).
+
+
+
diff --git a/website/content/en/guide/sources/twitter.md b/website/content/en/guide/sources/twitter.md
new file mode 100644
index 0000000..c9f26c3
--- /dev/null
+++ b/website/content/en/guide/sources/twitter.md
@@ -0,0 +1,221 @@
+---
+title: Twitter / X Bookmarks
+outline: false
+description: Authorize an X account and collect its bookmarked posts.
+---
+
+# Twitter / X Bookmarks
+
+This guide uses the official X API to collect your own bookmarks. Start with a
+[connected interface](/guide/connect), a Core Host SDK `0.3.x` instance with a public HTTPS URL, and
+an X account containing a recent bookmark whose text you recognize.
+
+You also need an X developer app with OAuth 2.0 user authorization and access to the required API
+endpoints. API access can incur charges; check your app's access and billing before starting. The
+[X Bookmarks documentation](https://docs.x.com/x-api/posts/bookmarks/introduction) owns provider
+requirements. An app-only bearer token is not a substitute for authorizing your account.
+
+
+
+
+## Use the Twitter setup wizard
+
+The browser-side Twitter Extension provides a visual **Setup** wizard. You do not need the OAuth
+script when that wizard is available with a compatible Core Extension.
+
+Use `inkcre/twitter` version `0.4.1` with Core Host SDK `0.3.x` and a compatible Web Host. Both
+Hosts use the same installed version: Core runs the Python collector, while the browser loads the
+setup wizard from that release's Module Federation distribution. If browser enablement reports a
+missing distribution, have your operator check the release publication and Registry configuration.
+Do not downgrade to `0.3.0`: its Python package targets Core Host `0.1.x`.
+
+### 1. Open Setup
+
+1. [Prepare the Extension](/guide/extensions) on Core with name `inkcre/twitter` and version
+ `0.4.1`, then enable it on Core.
+2. In **Extensions**, choose **This browser** under **Control Extension on Client**, enable
+ `inkcre/twitter`, and click its **Setup** button.
+3. Choose your online Core Peer/client in the wizard. Enable Twitter there if prompted. Core handles
+ the callback and collection; browser enablement alone is insufficient.
+
+### 2. Authorize your X account
+
+1. Copy the callback URL displayed by the wizard. In your X developer app's authentication settings,
+ configure OAuth 2.0 for a confidential web-app client, and register that URL exactly.
+2. Enter the OAuth **Client ID** and **Client Secret** in the wizard—not the API Key/Secret. Save
+ the application (**Save OAuth App**, or **Save application** in newer layouts).
+3. Choose **Create authorization link**, then **Open X authorization**. Approve the intended X
+ account in the new tab and return to the wizard. It observes the authorization result.
+4. Verify the connected handle before continuing. Complete authorization within ten minutes.
+
+Authorization is Extension-wide: all Twitter bookmark Sources in the deployment use this account.
+Replacing the OAuth application can disconnect it; read the reset confirmation before proceeding.
+Never paste your InKCre JWT secret into X. Keep provider credentials and authorization links
+private.
+
+### 3. Set up collection
+
+1. Choose an existing **Bookmark Source**, or enter a nickname and choose **Create Bookmark
+ Source**. Do not create another Source if the one you want already exists.
+2. Choose **Collect bookmarks daily at**. The time is interpreted in Core's timezone, not
+ necessarily your browser's timezone. Choose **Continue**.
+3. Review the handle, Source and schedule, then explicitly choose **Start collecting bookmarks**.
+
+The final action enables the schedule **and submits an immediate collection Job**. It does not mean
+that Job has finished. Open the Source's Jobs in client-web, [inspect the run](/guide/collect), then
+[index and search](/guide/search) for a recent bookmark. Do not add a second collection schedule
+after using the wizard.
+
+To disconnect later, stop its collection schedule first, then use **Disconnect** in the wizard's
+account step. You can separately revoke the app in X. Neither action removes collected information.
+
+
+
+
+## 1. Enable the Core Extension
+
+```sh
+inkcre-cli extension install inkcre/twitter --version 0.4.1
+inkcre-cli extension enable inkcre/twitter
+inkcre-cli source types
+```
+
+Look for `extensions.twitter.bookmark.Source`. If already installed, inspect
+`inkcre-cli extension get inkcre/twitter` before changing it. Check the
+[release listing](https://registry.inkcre.dev/v1/extensions/inkcre/twitter) for other Host versions.
+This CLI / Agent procedure does not require a browser-side Twitter Extension.
+
+## 2. Connect your X account
+
+Authorization belongs to the **Extension**, not an individual Source. All Twitter bookmark Sources
+in this deployment use the connected account. Do not switch accounts to create a second user's
+Source: that changes the account used by existing Sources too.
+
+Prefer the visual wizard when a compatible browser release is available. For an Agent/operator using
+this path, the CLI does not expose OAuth setup commands. The following local script calls its
+authenticated setup API. Use the Python environment from [Connect the CLI](/guide/connect-cli),
+which already supplies `httpx` and `PyJWT`. If someone else operates Core, ask them to perform
+setup; do not ask them to share their signing secret.
+
+Save this as `connect-twitter.py`, then run `python connect-twitter.py`. It first prints the exact
+callback URL. In your X app's user authentication settings, enable OAuth 2.0, choose a confidential
+web-app client that provides a Client ID and Client Secret, and register that callback URL exactly.
+Keep the script open while doing this. Use the OAuth Client ID/Secret, not API Key/Secret. See
+[X's OAuth app settings](https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code)
+if you cannot find those fields.
+
+```python
+import getpass
+import time
+
+import httpx
+import jwt
+
+core_url = input("Core HTTPS URL: ").strip().rstrip("/")
+if not core_url.startswith("https://"):
+ raise ValueError("Use your trusted Core HTTPS URL, not PostgREST")
+secret = getpass.getpass("InKCre JWT_SECRET: ")
+if not secret:
+ raise ValueError("JWT_SECRET is required")
+
+with httpx.Client(timeout=30) as client:
+ def request(method, path, **kwargs):
+ issued = int(time.time()) - 5
+ token = jwt.encode(
+ {"role": "authenticated", "iss": "inkcre-peer", "aud": "inkcre-api",
+ "iat": issued, "exp": issued + 900},
+ secret, algorithm="HS256",
+ )
+ response = client.request(
+ method, core_url + path,
+ headers={"Authorization": f"Bearer {token}"}, **kwargs,
+ )
+ response.raise_for_status()
+ return response.json()
+
+ status = request("GET", "/twitter/setup")
+ print("Register this callback URL in X:", status["callback_url"])
+ if not status["connected"]:
+ client_id = getpass.getpass("X OAuth Client ID: ").strip()
+ client_secret = getpass.getpass("X OAuth Client Secret: ")
+ if not client_id or not client_secret:
+ raise ValueError("Both OAuth app credentials are required")
+ request("PUT", "/twitter/setup/oauth-app", json={
+ "client_id": client_id, "client_secret": client_secret,
+ })
+ transaction = request("POST", "/twitter/setup/oauth-transactions")
+ print("Open this private authorization URL:", transaction["authorize_url"])
+ input("Authorize in X; after the callback says connected, press Enter: ")
+ result = request("POST", "/twitter/setup/oauth-transaction", json={
+ "transaction_id": transaction["id"],
+ })
+ if result["status"] != "succeeded":
+ raise RuntimeError(f"Authorization did not complete: {result['status']}")
+ status = request("GET", "/twitter/setup")
+ if not status["connected"]:
+ raise RuntimeError("Account is not connected; inspect setup status")
+ print("Connected account:", status["handle"])
+```
+
+Approve the account you intend to collect. The Extension requests `tweet.read`, `users.read`,
+`bookmark.read`, and `offline.access` so it can refresh authorization. Complete the browser flow
+within ten minutes. Keep credentials and the authorization URL private; never enter `JWT_SECRET`
+into X. Account tokens stay in the deployment, whose operator and admitted Peers are trusted.
+
+If the callback URL is missing or wrong, fix the Core Peer's `http_public_base_url` before
+proceeding; it must identify Core's public HTTPS origin, yielding `/twitter/auth/callback`. Starting
+a new authorization supersedes the previous pending attempt. A request to replace an already
+configured OAuth app may return HTTP 409: do not blindly enable `confirm_account_reset`, because
+replacement disconnects the existing account.
+
+## 3. Create and collect a Source
+
+Save `twitter.json`:
+
+```json
+{
+ "nickname": "My X bookmarks",
+ "config": {}
+}
+```
+
+```sh
+inkcre-cli source create --type extensions.twitter.bookmark.Source --input twitter.json
+```
+
+Record the returned Source ID. Replace `42` with it and collect:
+
+```sh
+inkcre-cli source collect 42 --input-json '{"result_limit":40}'
+```
+
+Use the returned Job ID with [Run a Collection](/guide/collect), then
+[index and search](/guide/search) for text from a recent bookmark. Once that works, add a
+[schedule](/guide/schedules).
+
+
+
+
+**Scope:** an ordinary run reads one page, with `result_limit` from 5 to 100 (default 40), stopping
+at the previously seen bookmark when present. This is not a guaranteed archive of all bookmarks,
+folders, replies, or unbookmarks. The current `full` option also performs a single page fetch per
+Job and does not automatically drain the entire history; do not treat it as a complete backfill.
+Collect often enough for your usage and retain another copy of information you cannot afford to
+lose.
+
+## If setup or collection fails
+
+- **404 on setup:** check the Core URL and whether this Extension is running on that Core Peer.
+- **401:** check your InKCre connection for setup failures; reconnect X if the collection's provider
+ authorization has expired or been revoked.
+- **402/403 from X:** inspect app access, credits, permissions, and the authorized account.
+- **429:** respect the provider's rate limit instead of repeatedly submitting Jobs.
+- **Finished but no new items:** check the connected handle and bookmark a new recognizable post.
+
+To stop collection, disable its Cron first. An operator can disconnect the stored account using
+authenticated `DELETE /twitter/setup/account` using the CLI / Agent request pattern, and separately
+revoke the app in X's account settings. Disconnecting does not delete previously collected data. The
+alternate `twikit` backend exists, but its account-login mechanics are not this OAuth walkthrough.
+
+Implementation reference:
+[Twitter Extension](https://github.com/InKCre/core-py/tree/main/extensions/twitter).
diff --git a/website/content/en/guide/troubleshooting.md b/website/content/en/guide/troubleshooting.md
new file mode 100644
index 0000000..d51f5bd
--- /dev/null
+++ b/website/content/en/guide/troubleshooting.md
@@ -0,0 +1,25 @@
+---
+title: Troubleshooting
+description: Check deployment, connection, collection, and retrieval problems.
+---
+
+# Troubleshooting
+
+Use the symptom below to find the next check. Keep credentials out of logs or screenshots you share.
+
+| What you observe | What to check next |
+| ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
+| Deployment workflow missing | Enable Actions in your fork and check that it is up to date with upstream `main`. |
+| Core slow or offline | Open Core `/readyz`; inspect host logs if it stays unready. PostgREST may sleep separately. |
+| Client returns `401` | Check endpoint and secret. Anonymous PostgREST `401` is expected; authenticated `401` is not. |
+| Source type absent | Install a compatible Extension and enable it on Core, not only in the Web app. |
+| Job stays pending | Confirm Core is awake and its collector enabled. Job creation only confirms acceptance. |
+| Collection finished but search empty | Confirm the source exposed the item; run lexical maintenance and inspect diagnostics. |
+| CLI works but Web cannot find a provider | Check Core readiness and that browser and Core Peer IDs differ. |
+| Old mail missing | Ordinary collection starts with new mail. Request a historical backfill. |
+| Truncated content or no attachment bytes | First-run examples avoid extra downloads. Check the collector's settings. |
+| Request outcome uncertain | Read the Job or resulting data before repeating a write; a lost response does not prove nothing happened. |
+
+Keep hosting and connection credentials in your password manager. Review your database's
+backup/restore options and hosting usage before collecting irreplaceable information. A fork, a
+client configuration export, and a source account are not backups of your info-base.
diff --git a/website/content/en/index.md b/website/content/en/index.md
index f5e0fe1..3613760 100644
--- a/website/content/en/index.md
+++ b/website/content/en/index.md
@@ -13,6 +13,9 @@ hero:
indexing, and downstream use.
actions:
- theme: brand
+ text: Getting Started
+ link: /getting-started
+ - theme: alt
text: Developer Guide
link: /developer/
- theme: alt
@@ -36,9 +39,10 @@ features:
InKCre is evolving rapidly. Product behavior, interfaces, and developer contracts may change as the
project tests and solidifies its foundations.
-This site currently focuses on a concise [Developer Guide](/developer/) and the project's
-[identity and direction](/about/). It does not yet present a stable User Manual or a complete
-third-party development platform.
+Start with [Getting Started](/getting-started) to learn what InKCre does, why you might use it, and
+how to begin. Running your own instance is one path, covered by [Self-Hosted](/self-hosted/). The
+[Developer Guide](/developer/) is for contributors; [About InKCre](/about/) explains the project's
+identity and direction.
## Start with the current foundations
diff --git a/website/content/en/self-hosted/advanced.md b/website/content/en/self-hosted/advanced.md
new file mode 100644
index 0000000..cec8c35
--- /dev/null
+++ b/website/content/en/self-hosted/advanced.md
@@ -0,0 +1,80 @@
+---
+title: Advanced Self-Hosting
+description:
+ Deploy InKCre on your own infrastructure and plan its operation, upgrades, and recovery.
+---
+
+# Advanced Self-Hosting
+
+This guide is for people who want to manage the runtime directly or move beyond the fork-based quick
+deployments. If you want the shortest route to your first collected information, start with
+[Self-Hosted Getting Started](/self-hosted/getting-started).
+
+## Manual deployment
+
+You can use your own machine, VPS, containers, or other hosting. No GitHub fork, Neon account,
+Render account, or Heroku account is inherently required. These providers automate a portable
+runtime consisting of:
+
+| Component | Responsibility |
+| ----------------------------------------------------------- | --------------------------------------------------------------------------------------- |
+| PostgreSQL with the required extensions, including pgvector | Persists the info-base and shared configuration. |
+| Database initializer | Creates/migrates the schema, provisions runtime roles, and reconciles built-in records. |
+| PostgREST | Exposes the admitted database API used by the Web client. |
+| Core Python program | Runs collection, Jobs, Extensions, and the Core HTTP API. |
+
+The initializer is a deployment step, not an additional always-running service. Core and PostgREST
+connect to the same initialized database with different runtime roles. The CLI connects to Core; the
+Web client uses PostgREST and discovers Core capabilities. The Web app itself can remain at
+`app.inkcre.dev` or be hosted separately.
+
+For a manual installation, work through these steps using the runtime documentation for the Core
+revision you selected:
+
+1. Provision PostgreSQL with the required extensions. Retain an owner connection for initialization
+ and migrations; do not use that privileged connection as the application's runtime connection.
+2. Run Core's ordered database initializer with the runtime profile. It provisions schema and roles
+ as well as migrations: starting an empty database and only launching Python is not enough. The
+ [database lifecycle guide](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/database-contract.md)
+ owns the initializer commands, credentials, and readiness checks.
+3. Configure PostgREST with the `authenticator` connection, the admitted `inkcre` schema, and the
+ deployment's JWT settings. Configure Core with the `inkcre_core` connection and matching JWT
+ settings. Keep the migration-owner credentials out of both running services.
+4. Start the Core Python program with its pinned dependencies, or use its container image, and start
+ PostgREST. The [Core README](https://github.com/InKCre/core-py#readme) and
+ [container/runtime guide](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/docker.md)
+ own the supported entry points. The Compose stack is a useful topology reference, but its
+ development credentials and defaults are not a production configuration.
+5. Configure HTTPS and reachable service URLs, advertise Core's public address in its Peer
+ configuration, and arrange process restarts, backups, and upgrades. Verify Core `/readyz` and
+ authenticated database access before connecting clients. The
+ [runtime orchestration guide](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/runtime-orchestration.md)
+ explains readiness and capability availability.
+
+## Operate your instance
+
+A successful first deployment is the beginning of operating the instance. Before relying on it for
+important information:
+
+- Choose hosting that keeps Core available for scheduled collection; sleeping plans cannot promise
+ continuous collection.
+- Keep database backups and verify how you will restore them. Your source accounts and repository
+ fork are not backups of the info-base.
+- Retain deployment credentials securely. Coordinate changes to database passwords and JWT settings
+ across the services and clients that use them.
+- Plan upgrades against the selected Core revision's migration and deployment instructions. Inspect
+ readiness and collection Jobs after changes; do not treat a running process as proof that
+ collection succeeded.
+
+Provider-specific procedures remain in the
+[Render deployment guide](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/render-neon-self-host.md)
+and
+[Heroku deployment guide](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/heroku-neon-self-host.md).
+The Core runtime documentation linked above owns exact commands and recovery procedures.
+
+## Continue with collection and use
+
+Once Core and PostgREST are ready, retain both URLs, the private JWT secret, and the Core Peer
+identity. Continue with [Connect the CLI](/guide/connect-cli),
+[your first source](/guide/first-source), [search](/guide/search), and
+[everyday tools](/guide/daily-use). These shared guides apply to manual and quick deployments alike.
diff --git a/website/content/en/self-hosted/getting-started.md b/website/content/en/self-hosted/getting-started.md
new file mode 100644
index 0000000..f6d3d3c
--- /dev/null
+++ b/website/content/en/self-hosted/getting-started.md
@@ -0,0 +1,51 @@
+---
+title: Getting Started
+description: Follow the self-hosted path from deployment to collecting and using your information.
+---
+
+# Self-Hosted Getting Started
+
+This is the [Self-Hosted](/self-hosted/) path through InKCre: you will operate your own instance.
+For the application's What, Why, and How, begin with [Getting Started](/getting-started).
+
+You will use the Web app for interactive setup. A trusted Agent or operator can handle the remaining
+terminal-only operations, such as installing Core collectors and scheduling indexing. No InKCre
+development setup or AI model is needed for the first collection-and-search journey. Each guide
+below is self-contained and can also be used with an existing compatible instance.
+
+## 1. Deploy an instance
+
+Choose **one** deployment guide:
+
+- [Render and Neon](/self-hosted/render-neon): fork-based quick deployment on Render.
+- [Heroku and Neon](/self-hosted/heroku-neon): the equivalent quick deployment on Heroku.
+- [Advanced Self-Hosting](/self-hosted/advanced): initialize PostgreSQL, configure PostgREST, and
+ run Core on infrastructure you choose.
+
+Neon and the compute providers are convenience options, not requirements. At the end, retain a ready
+Core URL, a PostgREST URL, your private JWT secret, and the Core Peer ID.
+
+## 2. Complete the first collection-and-search loop
+
+Follow these shared user guides in order. If you already have an instance, start here.
+
+1. [Connect to your instance](/guide/connect), using the Web app or the CLI for your Agent.
+2. [Collect your first source](/guide/first-source); a public RSS or Atom feed is a simple starting
+ point.
+3. [Find what you saved](/guide/search): maintain the lexical index, search, and inspect a result.
+
+**Checkpoint:** you can retrieve a real item from your source. Collection and indexing are separate;
+neither requires you to set up AI organization first.
+
+## 3. Make it useful day to day
+
+- [Schedule collection and indexing](/guide/schedules) after the manual steps work.
+- [Connect more sources](/guide/sources), such as GitHub saves, email, and Telegram messages.
+- [Use your information](/guide/daily-use) from the Web app, a terminal, or a trusted assistant.
+ This also explains optional AI organization and MCP access.
+
+These paths make information available on demand. They do not configure automatic daily digests or
+Telegram/email push notifications. Hosting that sleeps cannot guarantee continuous collection.
+
+If a step fails, use [Troubleshooting](/guide/troubleshooting). For backups, upgrades, and ongoing
+hosting responsibilities, return to [Advanced Self-Hosting](/self-hosted/advanced).
diff --git a/website/content/en/self-hosted/heroku-neon.md b/website/content/en/self-hosted/heroku-neon.md
new file mode 100644
index 0000000..a3a8a72
--- /dev/null
+++ b/website/content/en/self-hosted/heroku-neon.md
@@ -0,0 +1,56 @@
+---
+title: Deploy with Heroku and Neon
+description: Deploy your own InKCre instance using a GitHub fork, Neon, and Heroku.
+---
+
+# Deploy with Heroku and Neon
+
+Use this fork-based quick deployment if you do not have an instance yet. You need GitHub, Neon, and
+a compute-provider account. These providers are convenience options, not InKCre requirements;
+[Advanced Self-Hosting](/self-hosted/advanced) covers your own infrastructure.
+
+Keep credentials in a password manager. API keys let the workflow act on your hosting accounts; they
+are different from sign-in passwords.
+
+Choose Heroku instead of Render; you do not need both providers. The database still lives in Neon,
+while Heroku runs two apps: Core and PostgREST.
+
+1. Open [InKCre/core-py](https://github.com/InKCre/core-py), choose **Fork**, and enable workflows
+ in your fork's **Actions** tab. Create a dedicated project in [Neon](https://console.neon.tech),
+ keeping the default `neondb` database and `neondb_owner` role. Save its project ID and create a
+ Neon API key with access to it.
+2. Create a [Heroku account](https://dashboard.heroku.com/) with billing enabled and obtain an API
+ key. The workflow creates the two apps; you do not need to provision a Heroku database add-on.
+3. In your fork's **Settings → Secrets and variables → Actions**, add the following settings.
+ Generate independent random values for the three credential secrets and retain them in your
+ password manager.
+
+| Kind | Name | Value |
+| -------- | ----------------------------- | ---------------------------------------------------------------------------------- |
+| Secret | `NEON_API_KEY` | Your Neon API key. |
+| Secret | `HEROKU_API_KEY` | Your Heroku API key. |
+| Secret | `JWT_SECRET` | A new random secret of at least 32 ASCII characters. |
+| Secret | `CORE_DATABASE_PASSWORD` | A separate random password of at least 32 ASCII characters. |
+| Secret | `POSTGREST_DATABASE_PASSWORD` | Another random password of at least 32 ASCII characters. |
+| Variable | `NEON_PROJECT_ID` | Your Neon project ID. |
+| Variable | `HEROKU_APP_PREFIX` | A unique lowercase app prefix, such as `alex-inkcre`, between 3 and 18 characters. |
+
+4. Open **Actions → Deploy self-hosted InKCre to Heroku → Run workflow**, select your fork's `main`
+ branch, and wait for the run to succeed.
+5. Save the Core URL, PostgREST URL, and Core Peer ID from its summary. Open Core `/readyz` and wait
+ for HTTP `200`, then continue to [Connect the CLI](/guide/connect-cli).
+
+The workflow runs one Eco web dyno per app. Heroku charges and
+[Eco sleep behavior](https://devcenter.heroku.com/articles/eco-dyno-hours) apply. Keep the same
+database passwords on reruns; replacing them is a coordinated credential rotation, not a routine
+redeploy. As with Render Free, sleeping Core cannot provide continuous collection. The maintained
+deployment procedure and recovery details live in the
+[Core Heroku self-hosting guide](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/heroku-neon-self-host.md).
+
+**Keep your instance credentials private.** `JWT_SECRET` grants control of the instance, not a
+limited personal login. Do not paste it into an online JWT generator, an issue, a chat, or your
+public fork. This deployment does not isolate different users from one another. Start with your own
+information and trusted devices.
+
+Next: [Connect the CLI](/guide/connect-cli), or return to the
+[self-hosted walkthrough](/self-hosted/getting-started).
diff --git a/website/content/en/self-hosted/index.md b/website/content/en/self-hosted/index.md
new file mode 100644
index 0000000..a53bfc0
--- /dev/null
+++ b/website/content/en/self-hosted/index.md
@@ -0,0 +1,36 @@
+---
+title: Self-Hosted
+description: Choose how to run your own InKCre instance and connect it to your information sources.
+---
+
+# Self-Hosted
+
+Self-hosting means operating your own InKCre instance: you choose where information is stored, which
+sources it collects, and which clients can access it. You also manage credentials, hosting
+availability, backups, and updates. This section is for users choosing that responsibility.
+
+For the application-level introduction and access choices, start with
+[Getting Started](/getting-started). Here, choose between the step-by-step
+[self-hosted walkthrough](/self-hosted/getting-started) and
+[Advanced Self-Hosting](/self-hosted/advanced) for manual deployment and operations.
+
+## Choose a deployment path
+
+| Path | What you manage | Start here |
+| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
+| Fork quick deployment: Neon + Render | Provider accounts and GitHub settings; the workflow initializes the database and deploys Core and PostgREST. | [Render walkthrough](/self-hosted/render-neon) |
+| Fork quick deployment: Neon + Heroku | The same topology on Heroku, with its billing and runtime settings. | [Heroku walkthrough](/self-hosted/heroku-neon) |
+| Manual deployment | PostgreSQL initialization, PostgREST, the Core Python process or container, and hosting operations on infrastructure you choose. | [Advanced](/self-hosted/advanced) |
+
+Neon, Render, and Heroku are convenient deployment options, not product dependencies. Forking
+provides a ready-made deployment workflow; it is not required to run InKCre. Both quick deployments
+produce your own instance rather than access to a shared hosted account. Their sleeping compute
+plans need particular care if you want continuous collection.
+
+## From an empty instance to useful information
+
+Follow [Getting Started](/self-hosted/getting-started) to deploy an instance, collect one RSS feed,
+find a saved item, and add the sources you use. The guide then connects the instance to the Web app
+and everyday tools. It assumes basic technical familiarity but no InKCre development setup.
+
+If you already operate a compatible instance, begin at [connecting the CLI](/guide/connect-cli).
diff --git a/website/content/en/self-hosted/render-neon.md b/website/content/en/self-hosted/render-neon.md
new file mode 100644
index 0000000..b3d1edc
--- /dev/null
+++ b/website/content/en/self-hosted/render-neon.md
@@ -0,0 +1,58 @@
+---
+title: Deploy with Render and Neon
+description: Deploy your own InKCre instance using a GitHub fork, Neon, and Render.
+---
+
+# Deploy with Render and Neon
+
+Use this fork-based quick deployment if you do not have an instance yet. You need GitHub, Neon, and
+a compute-provider account. These providers are convenience options, not InKCre requirements;
+[Advanced Self-Hosting](/self-hosted/advanced) covers your own infrastructure.
+
+Keep credentials in a password manager. API keys let the workflow act on your hosting accounts; they
+are different from sign-in passwords.
+
+1. Open [InKCre/core-py](https://github.com/InKCre/core-py), choose **Fork**, and create your
+ repository copy. Open its **Actions** tab and enable workflows if GitHub asks.
+2. Create a project in [Neon](https://console.neon.tech). Keep the default `neondb` database and
+ `neondb_owner` role. Save the project ID and create a Neon API key with access to it.
+3. Create a workspace in [Render](https://dashboard.render.com). Save its workspace ID from Settings
+ and create a Render API key. Your fork must be public, or Render must already have permission to
+ read it.
+4. In your fork, open **Settings → Secrets and variables → Actions**. Use **New repository secret**
+ under **Secrets** and **New repository variable** under **Variables** to add:
+
+| Kind | Name | Value |
+| -------- | ----------------------- | ------------------------------------------------------------------------------------ |
+| Secret | `NEON_API_KEY` | Your Neon API key. |
+| Secret | `RENDER_API_KEY` | Your Render API key. |
+| Secret | `JWT_SECRET` | A new random secret of at least 32 ASCII characters, saved in your password manager. |
+| Variable | `NEON_PROJECT_ID` | Your Neon project ID. |
+| Variable | `RENDER_OWNER_ID` | Your Render workspace ID. |
+| Variable | `RENDER_SERVICE_PREFIX` | A unique lowercase prefix such as `alex-inkcre`, between 3 and 40 characters. |
+
+5. Open **Actions → Deploy self-hosted InKCre → Run workflow**, select your fork's `main` branch,
+ and run it. Wait for completion. If it fails, open the failed step, correct the reported problem,
+ and rerun using the same credentials.
+6. Open the completed run's summary. Save the **Core URL**, **PostgREST URL**, and **Core Peer ID**.
+ The workflow deliberately does not print your secret; retain the original value.
+7. Open the Core URL with `/readyz` appended. Continue when it returns HTTP `200`. A sleeping
+ service may take time to start.
+
+**Checkpoint:** deployment succeeded, and you have both service URLs and your `JWT_SECRET`. Use the
+**Core URL** for the CLI and the **PostgREST URL** for the Web app later.
+
+The workflow selects Render Free services. They sleep when idle, share account usage limits, and
+cannot guarantee continuous collection. Check
+[Render's current limits](https://render.com/docs/free) and your Neon plan before relying on them.
+Continuous scheduled collection needs hosting that keeps Core running. The maintained deployment
+procedure and recovery details live in the
+[Core self-hosting guide](https://github.com/InKCre/core-py/blob/main/docs/40-deployment/render-neon-self-host.md).
+
+**Keep your instance credentials private.** `JWT_SECRET` grants control of the instance, not a
+limited personal login. Do not paste it into an online JWT generator, an issue, a chat, or your
+public fork. This deployment does not isolate different users from one another. Start with your own
+information and trusted devices.
+
+Next: [Connect the CLI](/guide/connect-cli), or return to the
+[self-hosted walkthrough](/self-hosted/getting-started).
diff --git a/website/extension-docs/README.md b/website/extension-docs/README.md
new file mode 100644
index 0000000..16ae5b7
--- /dev/null
+++ b/website/extension-docs/README.md
@@ -0,0 +1,37 @@
+# First-party Extension documentation
+
+First-party producers use this VitePress preset and the website's theme through their pinned
+`docs/_shared` checkout. The Hub owns the template; the producer owns the pages. Ecosystem authors
+can build an entirely different static site because Registry receives only its output.
+
+Each set has ordinary Markdown pages, `.vitepress/config.mts`, and `.vitepress/theme/index.ts`.
+Import `extensionDocs` from this directory in the config, with the Extension name, scope, title,
+description, and its own sidebar. The theme entry re-exports `website/.vitepress/theme/index.ts`.
+Relative import paths are resolved from each producer's document location. Do not copy the theme.
+
+Install the locked toolchain once, then build a set from the producer repository root:
+
+```sh
+pnpm --dir docs/_shared/website --ignore-workspace install --frozen-lockfile
+INKCRE_DOCS_VERSION=0.2.0 pnpm --dir docs/_shared/website --ignore-workspace exec vitepress build \
+ "$PWD/extensions/memos/docs/global"
+```
+
+The output is `/.vitepress/dist/`. `INKCRE_DOCS_VERSION` is required and must match the exact
+Release targeted by publication. Set `INKCRE_DOCS_REGISTRY_URL` when publishing to another Registry;
+it defaults to the public first-party Registry. The build requires no publisher credential or final
+snapshot hostname. Root-relative links work at the snapshot's root origin.
+
+`--ignore-workspace` is required when the producer itself uses pnpm workspaces: the shared website
+has its own lockfile and toolchain and must not be resolved as another producer workspace package.
+
+Use `/` for the overview and stable leaf routes for complete procedures. Local search, narrow-screen
+navigation, version/scope labels, and the existing `InterfaceGuide` Web/CLI selector are shared.
+Without JavaScript both interface sections remain readable. Set `outline: false` on pages using
+`InterfaceGuide`, so the outline does not link to hidden headings. This template adds no Service
+Worker or offline cache.
+
+Validate each set with VitePress's dead-link checks and inspect its built HTML before uploading.
+Publication uses the Developer Toolkit's static-bundle protocol; it does not publish another Python
+or MF artifact. Upload the complete output and retain the candidate for uncertain-response recovery.
+Never publish a newly built candidate over an unreviewed newer documentation entity tag.
diff --git a/website/extension-docs/preset.mts b/website/extension-docs/preset.mts
new file mode 100644
index 0000000..b5988e2
--- /dev/null
+++ b/website/extension-docs/preset.mts
@@ -0,0 +1,56 @@
+import { createRequire } from 'node:module'
+import { dirname } from 'node:path'
+import { defineConfig, type DefaultTheme } from 'vitepress'
+
+const toolchain = createRequire(createRequire(import.meta.url).resolve('vitepress/package.json'))
+
+type Scope = 'global' | 'python' | 'module-federation'
+const scopeNames: Record = {
+ global: 'General',
+ python: 'Python',
+ 'module-federation': 'Web',
+}
+
+/** First-party presentation only; Registry accepts any author's static output. */
+export function extensionDocs(options: {
+ name: string
+ title: string
+ scope: Scope
+ description: string
+ sidebar: DefaultTheme.SidebarItem[]
+}) {
+ const version = process.env.INKCRE_DOCS_VERSION
+ if (!version) throw new Error('Set INKCRE_DOCS_VERSION to the exact Extension Release version.')
+ const registry = new URL(process.env.INKCRE_DOCS_REGISTRY_URL ?? 'https://registry.inkcre.dev')
+ const edition = `${options.name} · ${version} · ${scopeNames[options.scope]}`
+ return defineConfig({
+ base: '/',
+ cleanUrls: true,
+ lang: 'en',
+ title: options.title,
+ description: options.description,
+ lastUpdated: true,
+ // Pages live outside this toolchain's package root. Use its Vue for both
+ // client and server rendering instead of requiring a second installation.
+ vite: {
+ resolve: {
+ alias: { vue: dirname(toolchain.resolve('vue/package.json')) },
+ },
+ },
+ head: [['meta', { name: 'inkcre-documentation', content: edition }]],
+ themeConfig: {
+ nav: [
+ { text: `${version} · ${scopeNames[options.scope]}`, link: '/' },
+ { text: 'InKCre', link: 'https://inkcre.dev/getting-started' },
+ {
+ text: 'Registry',
+ link: new URL(`/explore/${options.name}?version=${encodeURIComponent(version)}`, registry)
+ .href,
+ },
+ ],
+ sidebar: options.sidebar,
+ search: { provider: 'local' },
+ footer: { message: edition },
+ },
+ })
+}