docs: rebuild documentation site with VitePress (#518)

* docs: rebuild documentation site with VitePress

* docs: address VitePress review feedback

* docs: complete locale redirect validation
This commit is contained in:
jinliyl 2026-09-04 14:50:50 +08:00 • committed by GitHub
parent 3f2eb6235f
commit 88ed21165b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 4495 additions and 1309 deletions

View file

@ -28,6 +28,7 @@ jobs:
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Node

View file

@ -11,11 +11,15 @@ on:
- 'README_ZH.md'
- 'docs/**'
- 'github-pages/**'
- 'reme/config/default.yaml'
- 'integrations/claude_code/README.md'
- 'integrations/hermes_agent/README.md'
- 'reme_studio/README*.md'
- 'reme_studio/public/og.jpg'
- 'typescript/README*.md'
- 'plugins/*/README*.md'
- 'benchmark/*/README*.md'
- 'benchmark/toolmemory/gitcha.png'
pull_request:
branches: [main, master, dev, develop]
paths:
@ -26,11 +30,15 @@ on:
- 'README_ZH.md'
- 'docs/**'
- 'github-pages/**'
- 'reme/config/default.yaml'
- 'integrations/claude_code/README.md'
- 'integrations/hermes_agent/README.md'
- 'reme_studio/README*.md'
- 'reme_studio/public/og.jpg'
- 'typescript/README*.md'
- 'plugins/*/README*.md'
- 'benchmark/*/README*.md'
- 'benchmark/toolmemory/gitcha.png'
workflow_dispatch:
concurrency:

View file

@ -6,6 +6,9 @@ on:
paths:
- "github-pages/**"
- "docs/**"
- "reme/config/default.yaml"
- "integrations/claude_code/README.md"
- "integrations/hermes_agent/README.md"
- "README.md"
- "README_ZH.md"
- "reme_studio/README*.md"
@ -13,6 +16,7 @@ on:
- "typescript/README*.md"
- "plugins/*/README*.md"
- "benchmark/*/README*.md"
- "benchmark/toolmemory/gitcha.png"
- "AGENTS.md"
- ".github/workflows/deploy-docs.yml"
- ".github/workflows/_build-docs.yml"

View file

@ -62,6 +62,8 @@ and concise documentation together.
conventions.
- `benchmark/` and `cookbook/`: runnable evaluations and example workflows.
- `docs/`: README-linked supporting pages and figures.
- `github-pages/`: VitePress build shell, generated-content assembly, documentation checks, and GitHub Pages output. The
canonical theme and guides remain under `docs/`; `.generated/` and `dist/` are disposable.
## Development Setup
@ -195,6 +197,9 @@ Integration tests may contact real model providers, services, or agent subproces
run credentialed or externally mutating tests automatically; run them only when the task requires them and the necessary
environment has been supplied or authorized. Mock network, model, and subprocess boundaries in unit tests.
If documentation or the documentation theme changes, run `npm test` and `npm run build` from `github-pages/`. The Job
reference is generated from `reme/config/default.yaml`; do not edit generated pages directly.
## Change Guardrails
- Preserve unrelated user changes in a dirty working tree.

View file

@ -51,12 +51,12 @@ users retain control of the durable files.
- [2026.08] - Published [`@agentscope-ai/reme`](https://www.npmjs.com/package/@agentscope-ai/reme), providing native
ReMe memory integrations for DeepSeek Harness and OpenClaw plus a shared TypeScript HTTP client.
- [2026.08] - Published the [ReMe blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog), an end-to-end introduction to its local-first memory
- [2026.08] - Published the [ReMe blog](https://reme.agentscope.io/en/reme-blog), an end-to-end introduction to its local-first memory
architecture, self-evolving workflows, hybrid search, proactive discovery, and benchmark results.
- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/?doc=toolmemory-en) of agent tool-use execution built
- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/en/benchmarks/toolmemory) of agent tool-use execution built
on ReMe is available on [arXiv:2608.03403](https://arxiv.org/abs/2608.03403).
- [2026.07] - Introduced optional plugins: [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) for paper discovery and
analysis, and [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-en) for researching the latest 24 hours of topic-related CLS news
- [2026.07] - Introduced optional plugins: [Daily Paper](https://reme.agentscope.io/en/plugins/daily-paper) for paper discovery and
analysis, and [Auto Fin](https://reme.agentscope.io/en/plugins/auto-fin) for researching the latest 24 hours of topic-related CLS news
with local-memory search and validated historical wikilinks.
- [2026.07] - Our
paper [Remember Me, Refine Me: A Dynamic Procedural Memory Framework for Experience-Driven Agent Evolution](https://aclanthology.org/2026.findings-acl.829/)
@ -171,7 +171,7 @@ Related: [[digest/wiki/memory-as-file.md]]
The `core` installation includes Studio. After starting ReMe, open <http://127.0.0.1:2333/> to browse, edit, and search
the workspace. To add Studio to a base installation, use `pip install "reme-ai[web]"`. See the
[ReMe Studio guide](https://reme.agentscope.io/?doc=studio-en) for source builds, configuration, and development.
[ReMe Studio guide](https://reme.agentscope.io/en/workspace/studio) for source builds, configuration, and development.
## 🤝 Use ReMe with Your Agent
@ -308,12 +308,12 @@ benchmark.
| Benchmark | Setting | Sample size | Agentic score | Focus |
| --------------------------------------------------------------------------- | ------------ | -----------------------: | ------------: | ------------------------------------------------------------------ |
| **[LongMemEval cleaned-s](https://reme.agentscope.io/?doc=longmemeval-en)** | **Overall** | **500 questions** | **89.4%** | Cross-session retrieval, knowledge updates, and temporal reasoning |
| [BEAM](https://reme.agentscope.io/?doc=beam-en) | 100K context | 20 cases / 400 questions | 66.1% | Ten types of long-context memory tasks |
| [BEAM](https://reme.agentscope.io/?doc=beam-en) | 1M context | 35 cases / 700 questions | 65.0% | Ultra-long conversation settings |
| **[LongMemEval cleaned-s](https://reme.agentscope.io/en/benchmarks/longmemeval)** | **Overall** | **500 questions** | **89.4%** | Cross-session retrieval, knowledge updates, and temporal reasoning |
| [BEAM](https://reme.agentscope.io/en/benchmarks/beam) | 100K context | 20 cases / 400 questions | 66.1% | Ten types of long-context memory tasks |
| [BEAM](https://reme.agentscope.io/en/benchmarks/beam) | 1M context | 35 cases / 700 questions | 65.0% | Ultra-long conversation settings |
ReMe also achieved a **0.580 PROC score across five user personas** in the repository's
[π-Bench evaluation](https://reme.agentscope.io/?doc=pibench-en), 2.4% above NanoBot under the same test-model configuration. PROC
[π-Bench evaluation](https://reme.agentscope.io/en/benchmarks/pibench), 2.4% above NanoBot under the same test-model configuration. PROC
measures proactive handling of hidden intent, clarification, cross-session preferences and conventions, task
dependencies, and underspecified requests.
@ -326,8 +326,8 @@ plugins; see the source distributions and their documentation for [Daily Paper](
| Plugin | Capability |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) | Discover and rank papers, analyze PDFs with an agent, and generate file-native notes and a five-minute brief. |
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-en) | Fetch topic-related CLS news, search ReMe history, and generate wikilink-backed Markdown reports. |
| [Daily Paper](https://reme.agentscope.io/en/plugins/daily-paper) | Discover and rank papers, analyze PDFs with an agent, and generate file-native notes and a five-minute brief. |
| [Auto Fin](https://reme.agentscope.io/en/plugins/auto-fin) | Fetch topic-related CLS news, search ReMe history, and generate wikilink-backed Markdown reports. |
See [Plugin Management](docs/en/plugin_management.md) to install, inspect, validate, enable, and uninstall ReMe plugins.
@ -338,6 +338,8 @@ These guides cover the main user workflows and the runtime contracts implemented
| Guide | What you will learn |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| [Quick Start](docs/en/quick_start.md) | Install ReMe, start the service, and run the first file and memory operations. |
| [Configuration](docs/en/configuration.md) | Configure the workspace, models, Service, Jobs, Components, plugins, and CLI overrides. |
| [Services and Deployment](docs/en/services.md) | Use HTTP, SSE, MCP, and Studio while respecting the default security boundary. |
| [Memory as File](docs/en/memory_as_file.md) | Understand workspace layers, frontmatter, wikilinks, chunks, and the file-as-source-of-truth model. |
| [Auto Memory](docs/en/auto_memory.md) | Preserve source conversations and distill reusable daily memory cards. |
| [Auto Resource](docs/en/auto_resource.md) | Import supported text resources and turn them into source-linked daily cards. |
@ -347,7 +349,9 @@ These guides cover the main user workflows and the runtime contracts implemented
| [Application Scenarios](docs/en/reme_scene.md) | Follow concrete financial research, coding-memory, and personal knowledge-base examples. |
| [Framework](docs/en/framework.md) | Understand Application, Job, Step, Component, service, configuration, and lifecycle boundaries. |
| [TypeScript integrations](typescript/README.md) | Configure the shared client and native DeepSeek Harness and OpenClaw adapters. |
| [ReMe Blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog) | Read the product story, design rationale, examples, and benchmark summary. |
| [CLI and Job API](docs/en/reference/cli.md) | Learn command syntax and use the generated default Job parameter reference. |
| [Operations and Recovery](docs/en/operations.md) | Diagnose services, maintain indexes, and back up, migrate, or recover a workspace. |
| [ReMe Blog](https://reme.agentscope.io/en/reme-blog) | Read the product story, design rationale, examples, and benchmark summary. |
## 🛠️ Common Commands

View file

@ -49,13 +49,13 @@
- [2026.08] - 发布 [`@agentscope-ai/reme`](https://www.npmjs.com/package/@agentscope-ai/reme),提供统一 TypeScript HTTP
client,以及 DeepSeek Harness 和 OpenClaw 的原生 ReMe 记忆集成。
- [2026.08] - 发布 [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
- [2026.08] - 发布 [ReMe 博客](https://reme.agentscope.io/zh/reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
主动发现与评测结果。
- [2026.08] - 基于 ReMe 的智能体工具使用
[经验驱动增强方法](https://reme.agentscope.io/?doc=toolmemory-zh)已发布,见
[经验驱动增强方法](https://reme.agentscope.io/zh/benchmarks/toolmemory)已发布,见
[arXiv:2608.03403](https://arxiv.org/abs/2608.03403)。
- [2026.07] - 新增可选插件:[每日论文](https://reme.agentscope.io/?doc=daily-paper-zh)用于论文发现与解析,
[Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh)用于研究最近 24 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建
- [2026.07] - 新增可选插件:[每日论文](https://reme.agentscope.io/zh/plugins/daily-paper)用于论文发现与解析,
[Auto Fin](https://reme.agentscope.io/zh/plugins/auto-fin)用于研究最近 24 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建
wikilink。
- [2026.07] -
我们的论文 [Remember Me, Refine Me: A Dynamic Procedural Memory Framework for Experience-Driven Agent Evolution](https://aclanthology.org/2026.findings-acl.829/)
@ -170,7 +170,7 @@ ReMe 会把 Agent 记忆保存为可读的 Markdown。
上面的 `core` 安装已包含 Studio。启动 ReMe 后,打开 <http://127.0.0.1:2333/> 即可浏览、编辑和搜索 workspace。
如需为基础安装单独添加 Studio,可使用 `pip install "reme-ai[web]"`。源码构建、配置和开发说明见
[ReMe Studio 指南](https://reme.agentscope.io/?doc=studio-zh)。
[ReMe Studio 指南](https://reme.agentscope.io/zh/workspace/studio)。
## 🤝 将 ReMe 接入你的 Agent
@ -301,11 +301,11 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
| 基准 | 设置 | 样本量 | Agentic 得分 | 主要检验内容 |
| --------------------------------------------------------------------------- | ----------- | ----------------: | -----------: | ------------------------------ |
| **[LongMemEval cleaned-s](https://reme.agentscope.io/?doc=longmemeval-zh)** | **整体** | **500 题** | **89.4%** | 跨会话检索、知识更新与时间推理 |
| [BEAM](https://reme.agentscope.io/?doc=beam-zh) | 100K 上下文 | 20 cases / 400 题 | 66.1% | 十类长上下文记忆任务 |
| [BEAM](https://reme.agentscope.io/?doc=beam-zh) | 1M 上下文 | 35 cases / 700 题 | 65.0% | 超长对话设置 |
| **[LongMemEval cleaned-s](https://reme.agentscope.io/zh/benchmarks/longmemeval)** | **整体** | **500 题** | **89.4%** | 跨会话检索、知识更新与时间推理 |
| [BEAM](https://reme.agentscope.io/zh/benchmarks/beam) | 100K 上下文 | 20 cases / 400 题 | 66.1% | 十类长上下文记忆任务 |
| [BEAM](https://reme.agentscope.io/zh/benchmarks/beam) | 1M 上下文 | 35 cases / 700 题 | 65.0% | 超长对话设置 |
在仓库的 [π-Bench 评测](https://reme.agentscope.io/?doc=pibench-zh)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580**
在仓库的 [π-Bench 评测](https://reme.agentscope.io/zh/benchmarks/pibench)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580**
,比相同测试模型配置的 NanoBot 高 2.4%。PROC 用于评估隐藏意图完成、针对性澄清、跨会话偏好和规范复用、跨任务依赖推断以及欠规格请求推进等主动性能力。
## 🧩 扩展与插件
@ -316,8 +316,8 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
| 插件 | 能力 |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------ |
| [每日论文](https://reme.agentscope.io/?doc=daily-paper-zh) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 |
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |
| [每日论文](https://reme.agentscope.io/zh/plugins/daily-paper) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 |
| [Auto Fin](https://reme.agentscope.io/zh/plugins/auto-fin) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |
安装、查看、校验、启用和卸载 ReMe 插件的方法见[插件管理](docs/zh/plugin_management.md)。
@ -328,6 +328,8 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
| 文档 | 主要内容 |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务,并执行首次文件和记忆操作。 |
| [基础配置](docs/zh/configuration.md) | 配置 workspace、模型、Service、Job、Component、插件和命令行覆盖。 |
| [服务与部署](docs/zh/services.md) | 使用 HTTP、SSE、MCP 和 Studio,并理解默认安全边界。 |
| [Memory as File](docs/zh/memory_as_file.md) | 理解 workspace 分层、frontmatter、wikilink、chunk 和文件事实来源模型。 |
| [Auto Memory](docs/zh/auto_memory.md) | 保留过滤后的对话来源记录,并提炼可复用的 daily 记忆卡片。 |
| [Auto Resource](docs/zh/auto_resource.md) | 导入支持的文本资料,转换为可追溯来源的 daily 卡片。 |
@ -337,7 +339,9 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
| [应用场景](docs/zh/reme_scene.md) | 查看金融研究、研发记忆和个人知识库的完整使用示例。 |
| [框架说明](docs/zh/framework.md) | 理解 Application、Job、Step、Component、service、配置和生命周期边界。 |
| [TypeScript 集成](typescript/README_ZH.md) | 配置统一 client,以及 DeepSeek Harness 和 OpenClaw 原生适配器。 |
| [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
| [CLI 与 Job API](docs/zh/reference/cli.md) | 查询命令语法,以及由默认配置自动生成的 Job 参数参考。 |
| [运维与恢复](docs/zh/operations.md) | 诊断服务、维护索引,并备份、迁移和恢复 workspace。 |
| [ReMe 博客](https://reme.agentscope.io/zh/reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
## 🛠️ 常用命令

View file

@ -6,7 +6,7 @@
> Code: [https://github.com/WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)
<p align="center">
<img src="gitcha.png" alt="ExpG challenges and overview" width="85%">
<img src="./gitcha.png" alt="ExpG challenges and overview" width="85%">
</p>
### Overview

View file

@ -6,7 +6,7 @@
> 代码:[https://github.com/WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)
<p align="center">
<img src="gitcha.png" alt="ExpG 挑战与概览" width="85%">
<img src="./gitcha.png" alt="ExpG 挑战与概览" width="85%">
</p>
### 简介

342
docs/.vitepress/config.mts Normal file
View file

@ -0,0 +1,342 @@
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { defineConfig, type DefaultTheme } from "vitepress";
import { legacyRoutes } from "./legacy-routes.mjs";
const sourceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const repositoryRoot = path.resolve(sourceRoot, "../../..");
const repository = "https://github.com/agentscope-ai/ReMe";
const base = process.env.DOCS_BASE || "/";
function readSourceMap(): Record<string, string> {
try {
return JSON.parse(fs.readFileSync(path.join(sourceRoot, ".source-map.json"), "utf8"));
} catch {
return {};
}
}
const sourceMap = readSourceMap();
function collectMarkdown(directory: string, root = directory): string[] {
const files: string[] = [];
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
if (entry.name.startsWith(".") || entry.name === "public" || entry.name === "figure") continue;
const absolute = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...collectMarkdown(absolute, root));
else if (entry.name.endsWith(".md")) files.push(path.relative(root, absolute).replaceAll(path.sep, "/"));
}
return files.sort();
}
function buildLlmsFiles(outDir: string) {
const pages = collectMarkdown(sourceRoot);
const index = [
"# ReMe Documentation",
"",
"> Local-first, file-native memory for agents.",
"",
...pages.map((relativePath) => {
const source = fs.readFileSync(path.join(sourceRoot, relativePath), "utf8");
const title = source.match(/^#\s+(.+)$/m)?.[1]
|| source.match(/^title:\s*(.+)$/m)?.[1]
|| path.basename(relativePath, ".md");
const route = relativePath.replace(/(?:^|\/)index\.md$/, "").replace(/\.md$/, "");
return `- [${title}](https://reme.agentscope.io/${route})`;
}),
"",
];
fs.writeFileSync(path.join(outDir, "llms.txt"), index.join("\n"), "utf8");
const full = ["# ReMe Documentation", ""];
for (const relativePath of pages) {
const source = fs.readFileSync(path.join(sourceRoot, relativePath), "utf8");
full.push(`<!-- source: ${sourcePathFor(relativePath)} -->`, "", source, "", "---", "");
const pageDir = path.join(outDir, relativePath.replace(/\.md$/, ""));
fs.mkdirSync(pageDir, { recursive: true });
fs.writeFileSync(path.join(pageDir, "llms.txt"), source, "utf8");
}
fs.writeFileSync(path.join(outDir, "llms-full.txt"), full.join("\n"), "utf8");
}
function sourcePathFor(relativePath: string) {
return sourceMap[relativePath] || `docs/${relativePath}`;
}
function sourceLastUpdated(relativePath: string): number | undefined {
const sourcePath = sourcePathFor(relativePath);
try {
const timestamp = execFileSync("git", ["log", "-1", "--format=%ct", "--", sourcePath], {
cwd: repositoryRoot,
encoding: "utf8",
}).trim();
if (timestamp) return Number(timestamp) * 1000;
} catch {
// Fall back to the canonical file timestamp outside a Git checkout.
}
try {
return fs.statSync(path.join(repositoryRoot, sourcePath)).mtimeMs;
} catch {
return undefined;
}
}
const legacyRedirectScript = `(() => {
const routes = ${JSON.stringify(legacyRoutes)};
const id = new URLSearchParams(window.location.search).get("doc");
const target = id && routes[id];
const base = ${JSON.stringify(base)};
if (target) {
const destination = /^https?:/.test(target)
? target
: base.replace(/\\/$/, "") + target;
window.location.replace(destination + window.location.hash);
return;
}
const root = base.endsWith("/") ? base : base + "/";
if (window.location.pathname === root) {
window.location.replace(root + "zh/" + window.location.hash);
}
})();`;
function nav(language: "zh" | "en"): DefaultTheme.NavItem[] {
const zh = language === "zh";
return [
{ text: zh ? "开始使用" : "Get Started", link: `/${language}/quick_start` },
{ text: zh ? "核心概念" : "Concepts", link: `/${language}/memory_as_file` },
{ text: zh ? "指南" : "Guides", link: `/${language}/auto_memory` },
{ text: zh ? "集成" : "Integrations", link: `/${language}/integrations` },
{ text: zh ? "API 参考" : "API Reference", link: `/${language}/reference/cli` },
{ text: zh ? "常见问题" : "FAQ", link: `/${language}/faq` },
];
}
function sidebar(language: "zh" | "en"): DefaultTheme.SidebarItem[] {
const zh = language === "zh";
return [
{
text: zh ? "开始使用" : "Get Started",
collapsed: false,
items: [
{ text: zh ? "项目介绍" : "Introduction", link: `/${language}/` },
{ text: zh ? "快速开始" : "Quick Start", link: `/${language}/quick_start` },
{ text: zh ? "基础配置" : "Configuration", link: `/${language}/configuration` },
{ text: zh ? "服务与部署" : "Services and Deployment", link: `/${language}/services` },
],
},
{
text: zh ? "核心概念" : "Core Concepts",
collapsed: false,
items: [
{ text: zh ? "文件即记忆" : "Memory as File", link: `/${language}/memory_as_file` },
{ text: zh ? "记忆检索" : "Memory Search", link: `/${language}/memory_search` },
{ text: zh ? "自动关联" : "Auto Link", link: `/${language}/auto_link` },
{ text: zh ? "应用场景" : "Application Scenarios", link: `/${language}/reme_scene` },
],
},
{
text: zh ? "记忆工作流" : "Memory Workflows",
collapsed: false,
items: [
{ text: "Auto Memory", link: `/${language}/auto_memory` },
{ text: "Auto Resource", link: `/${language}/auto_resource` },
{ text: "Auto Dream", link: `/${language}/auto_dream` },
{ text: "Proactive", link: `/${language}/proactive` },
],
},
{
text: zh ? "Agent 集成" : "Agent Integrations",
collapsed: true,
items: [
{ text: zh ? "集成总览" : "Overview", link: `/${language}/integrations` },
{ text: "Claude Code", link: `/${language}/integrations/claude-code` },
{ text: "Hermes Agent", link: `/${language}/integrations/hermes` },
{ text: "TypeScript / OpenClaw / DSH", link: `/${language}/integrations/typescript` },
],
},
{
text: zh ? "工作区与插件" : "Workspace and Plugins",
collapsed: true,
items: [
{ text: "ReMe Studio", link: `/${language}/workspace/studio` },
{ text: zh ? "插件管理" : "Plugin Management", link: `/${language}/plugin_management` },
{ text: zh ? "插件开发" : "Plugin Development", link: `/${language}/plugin_development` },
{ text: zh ? "每日论文" : "Daily Paper", link: `/${language}/plugins/daily-paper` },
{ text: "Auto Fin", link: `/${language}/plugins/auto-fin` },
{ text: "LME", link: `/${language}/plugins/lme` },
{ text: "BEAM", link: `/${language}/plugins/beam` },
],
},
{
text: zh ? "API 参考" : "API Reference",
collapsed: true,
items: [
{ text: "CLI", link: `/${language}/reference/cli` },
{ text: zh ? "Job API" : "Job API", link: `/${language}/reference/jobs` },
{ text: "HTTP / MCP", link: `/${language}/services#http-api` },
],
},
{
text: zh ? "运维" : "Operations",
collapsed: true,
items: [
{ text: zh ? "诊断、备份与恢复" : "Diagnostics, Backup, and Recovery", link: `/${language}/operations` },
{ text: zh ? "常见问题" : "FAQ", link: `/${language}/faq` },
],
},
{
text: zh ? "开发者" : "Development",
collapsed: true,
items: [
{ text: zh ? "代码框架" : "Framework", link: `/${language}/framework` },
{ text: zh ? "开源与贡献" : "Contributing", link: `/${language}/contributing` },
],
},
{
text: zh ? "评测" : "Benchmarks",
collapsed: true,
items: [
{ text: "BEAM", link: `/${language}/benchmarks/beam` },
{ text: "LongMemEval", link: `/${language}/benchmarks/longmemeval` },
{ text: "π-Bench", link: `/${language}/benchmarks/pibench` },
{ text: "Tool Memory / ExpG", link: `/${language}/benchmarks/toolmemory` },
],
},
];
}
function configureRepositoryLinks(md: any) {
for (const ruleName of ["link_open", "image"] as const) {
const original = md.renderer.rules[ruleName];
md.renderer.rules[ruleName] = (tokens: any[], index: number, options: any, env: any, self: any) => {
const attribute = ruleName === "image" ? "src" : "href";
const token = tokens[index];
const attributeIndex = token.attrIndex(attribute);
const target = attributeIndex >= 0 ? token.attrs[attributeIndex][1] : "";
if (target && !/^(?:[a-z]+:|#|\/)/i.test(target)) {
const cleanTarget = target.split("#")[0].split("?")[0];
const generatedTarget = path.resolve(sourceRoot, path.dirname(env.relativePath), cleanTarget);
if (!fs.existsSync(generatedTarget)) {
const originalPage = sourcePathFor(env.relativePath);
const originalTarget = path.posix.normalize(path.posix.join(path.posix.dirname(originalPage), cleanTarget));
const suffix = target.slice(cleanTarget.length);
const url = ruleName === "image"
? `https://raw.githubusercontent.com/agentscope-ai/ReMe/main/${originalTarget}${suffix}`
: `${repository}/blob/main/${originalTarget}${suffix}`;
token.attrs[attributeIndex][1] = url;
}
}
return original ? original(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
};
}
}
export default defineConfig({
lang: "zh-CN",
title: "ReMe",
description: "Local-first, file-native memory for agents",
base,
cleanUrls: true,
lastUpdated: true,
ignoreDeadLinks: [/^http:\/\/localhost(?::\d+)?(?:\/|$)/],
sitemap: {
hostname: "https://reme.agentscope.io",
transformItems(items) {
const isRoot = (url: string) => url.replace(/^\/+|\/+$/g, "") === "";
return items.filter((item) => !isRoot(item.url)).map((item) => {
const route = item.url.replace(/^\/+/, "");
const relativePath = !route || route.endsWith("/") ? `${route}index.md` : `${route}.md`;
const links = item.links?.filter((link) => !isRoot(link.url));
return { ...item, links, lastmod: sourceLastUpdated(relativePath) };
});
},
},
head: [
["link", { rel: "icon", type: "image/svg+xml", href: `${base}favicon.svg` }],
["meta", { name: "theme-color", content: "#087f6a" }],
["script", {}, legacyRedirectScript],
],
markdown: {
config: configureRepositoryLinks,
},
transformPageData(pageData, { siteConfig }) {
const sourcePath = path.join(siteConfig.srcDir, pageData.relativePath);
pageData.frontmatter._sourcePath = sourcePathFor(pageData.relativePath);
pageData.lastUpdated = sourceLastUpdated(pageData.relativePath);
try {
pageData.frontmatter._rawMarkdown = fs.readFileSync(sourcePath, "utf8");
} catch {
pageData.frontmatter._rawMarkdown = "";
}
},
buildEnd(siteConfig) {
buildLlmsFiles(siteConfig.outDir);
},
themeConfig: {
logo: "/favicon.svg",
siteTitle: "ReMe",
nav: [
...nav("zh"),
{
text: "语言",
items: [
{ text: "简体中文", link: "/zh/" },
{ text: "English", link: "/en/" },
],
},
],
outline: { label: "页面导航", level: [2, 3] },
search: {
provider: "local",
options: {
locales: {
zh: {
translations: {
button: { buttonText: "搜索文档", buttonAriaLabel: "搜索文档" },
modal: {
noResultsText: "没有找到相关内容",
resetButtonTitle: "清除查询",
footer: { selectText: "选择", navigateText: "切换", closeText: "关闭" },
},
},
},
},
},
},
socialLinks: [{ icon: "github", link: repository }],
footer: {
message: "Released under the Apache-2.0 License.",
copyright: "Copyright ReMe contributors",
},
},
locales: {
zh: {
label: "简体中文",
lang: "zh-CN",
link: "/zh/",
themeConfig: {
nav: nav("zh"),
sidebar: { "/zh/": sidebar("zh") },
outline: { label: "页面导航", level: [2, 3] },
docFooter: { prev: "上一页", next: "下一页" },
darkModeSwitchLabel: "外观",
sidebarMenuLabel: "菜单",
returnToTopLabel: "返回顶部",
langMenuLabel: "切换语言",
},
},
en: {
label: "English",
lang: "en-US",
link: "/en/",
themeConfig: {
nav: nav("en"),
sidebar: { "/en/": sidebar("en") },
outline: { label: "On this page", level: [2, 3] },
docFooter: { prev: "Previous page", next: "Next page" },
},
},
},
});

View file

@ -0,0 +1,47 @@
export const legacyRoutes = {
"readme-zh": "/zh/",
"readme-en": "/en/",
"zh-quick_start": "/zh/quick_start",
"en-quick_start": "/en/quick_start",
"zh-plugin_management": "/zh/plugin_management",
"en-plugin_management": "/en/plugin_management",
"zh-memory_as_file": "/zh/memory_as_file",
"en-memory_as_file": "/en/memory_as_file",
"zh-memory_search": "/zh/memory_search",
"en-memory_search": "/en/memory_search",
"zh-auto_memory": "/zh/auto_memory",
"en-auto_memory": "/en/auto_memory",
"zh-auto_resource": "/zh/auto_resource",
"en-auto_resource": "/en/auto_resource",
"zh-auto_link": "/zh/auto_link",
"en-auto_link": "/en/auto_link",
"zh-auto_dream": "/zh/auto_dream",
"en-auto_dream": "/en/auto_dream",
"zh-proactive": "/zh/proactive",
"en-proactive": "/en/proactive",
"zh-reme_scene": "/zh/reme_scene",
"en-reme_scene": "/en/reme_scene",
"zh-framework": "/zh/framework",
"en-framework": "/en/framework",
"zh-reme-blog": "/zh/reme-blog",
"en-reme-blog": "/en/reme-blog",
"zh-contributing": "/zh/contributing",
"en-contributing": "/en/contributing",
"typescript-zh": "/zh/integrations/typescript",
"typescript-en": "/en/integrations/typescript",
"studio-zh": "/zh/workspace/studio",
"studio-en": "/en/workspace/studio",
"daily-paper-zh": "/zh/plugins/daily-paper",
"daily-paper-en": "/en/plugins/daily-paper",
"auto-fin-zh": "/zh/plugins/auto-fin",
"auto-fin-en": "/en/plugins/auto-fin",
"beam-zh": "/zh/benchmarks/beam",
"beam-en": "/en/benchmarks/beam",
"longmemeval-zh": "/zh/benchmarks/longmemeval",
"longmemeval-en": "/en/benchmarks/longmemeval",
"pibench-zh": "/zh/benchmarks/pibench",
"pibench-en": "/en/benchmarks/pibench",
"toolmemory-zh": "/zh/benchmarks/toolmemory",
"toolmemory-en": "/en/benchmarks/toolmemory",
"agents-guide": "https://github.com/agentscope-ai/ReMe/blob/main/AGENTS.md",
};

View file

@ -0,0 +1,32 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { useData } from "vitepress";
const { frontmatter, lang } = useData();
const copied = ref(false);
const label = computed(() => {
if (copied.value) return lang.value.startsWith("zh") ? "已复制" : "Copied";
return lang.value.startsWith("zh") ? "复制 Markdown" : "Copy Markdown";
});
async function copyMarkdown() {
const markdown = String(frontmatter.value._rawMarkdown || "");
if (!markdown) return;
await navigator.clipboard.writeText(markdown);
copied.value = true;
window.setTimeout(() => { copied.value = false; }, 1800);
}
</script>
<template>
<div class="copy-markdown-wrap">
<button class="copy-markdown" type="button" :class="{ copied }" @click="copyMarkdown">
<svg v-if="!copied" viewBox="0 0 24 24" aria-hidden="true">
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
<svg v-else viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg>
{{ label }}
</button>
</div>
</template>

View file

@ -0,0 +1,15 @@
<script setup lang="ts">
import { computed } from "vue";
import { useData } from "vitepress";
const { frontmatter, lang } = useData();
const sourcePath = computed(() => String(frontmatter.value._sourcePath || ""));
const label = computed(() => lang.value.startsWith("zh") ? "在 GitHub 查看源文件" : "View source on GitHub");
const href = computed(() => `https://github.com/agentscope-ai/ReMe/blob/main/${sourcePath.value}`);
</script>
<template>
<div v-if="sourcePath" class="source-link-wrap">
<a :href="href" target="_blank" rel="noreferrer">{{ label }} ↗</a>
</div>
</template>

View file

@ -0,0 +1,224 @@
:root {
--vp-layout-max-width: 1560px;
--vp-c-brand-1: #087f6a;
--vp-c-brand-2: #086554;
--vp-c-brand-3: #19a98f;
--vp-c-brand-soft: rgba(8, 127, 106, 0.14);
--vp-c-bg: #ffffff;
--vp-c-bg-alt: #f4f7f5;
--vp-c-bg-elv: #ffffff;
--vp-c-bg-soft: #f1f6f3;
--vp-c-text-1: #17221d;
--vp-c-text-2: #526159;
--vp-c-text-3: #718078;
--vp-c-divider: #dce5e0;
--vp-font-family-base: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--vp-font-family-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
--reme-blue: #3156d9;
--reme-green: #087f6a;
}
.dark {
--vp-c-brand-1: #57dfc3;
--vp-c-brand-2: #35c6a9;
--vp-c-brand-3: #087f6a;
--vp-c-brand-soft: rgba(87, 223, 195, 0.14);
--vp-c-bg: #0d1512;
--vp-c-bg-alt: #09100d;
--vp-c-bg-elv: #14201b;
--vp-c-bg-soft: #17251f;
--vp-c-text-1: #edf7f3;
--vp-c-text-2: #bacbc4;
--vp-c-text-3: #91a49c;
--vp-c-divider: #283a33;
}
body {
background:
radial-gradient(circle at 8% 8%, rgba(25, 201, 176, 0.055), transparent 28rem),
var(--vp-c-bg);
}
.VPNav {
border-bottom: 1px solid color-mix(in srgb, var(--vp-c-divider) 84%, transparent);
background: color-mix(in srgb, var(--vp-c-bg) 88%, transparent);
backdrop-filter: blur(18px) saturate(140%);
}
.VPNavBarTitle .logo {
width: 30px;
height: 30px;
}
.VPNavBarTitle .title {
font-weight: 780;
letter-spacing: -0.02em;
}
.VPNavBarSearch .DocSearch-Button,
.VPNavBarSearch button {
min-width: 190px;
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
background: var(--vp-c-bg-alt);
}
.VPSidebar {
border-right: 1px solid var(--vp-c-divider);
background: color-mix(in srgb, var(--vp-c-bg-alt) 82%, transparent);
}
.VPSidebarItem .text {
font-size: 14px;
}
.VPSidebarItem.level-0 > .item > .text {
color: var(--vp-c-text-1);
font-weight: 750;
}
.VPSidebarItem.is-active > .item .link > .text {
color: var(--vp-c-brand-1);
}
.VPDocAsideOutline {
border-left-color: var(--vp-c-divider);
}
.VPDoc .container > .content {
min-width: 0;
}
.VPDoc .content-container {
max-width: 900px !important;
}
.vp-doc {
color: var(--vp-c-text-1);
font-size: 16px;
line-height: 1.78;
}
.vp-doc h1 {
margin-bottom: 28px;
font-size: clamp(36px, 5vw, 52px);
line-height: 1.08;
letter-spacing: -0.045em;
}
.vp-doc h2 {
margin-top: 52px;
border-top-color: var(--vp-c-divider);
font-size: 27px;
letter-spacing: -0.025em;
}
.vp-doc h3 {
margin-top: 34px;
font-size: 20px;
}
.vp-doc :not(pre) > code {
border-radius: 5px;
color: color-mix(in srgb, var(--vp-c-brand-1) 82%, var(--vp-c-text-1));
}
.vp-doc div[class*="language-"] {
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--vp-c-brand-1) 62%, transparent);
}
.copy-markdown-wrap {
display: flex;
justify-content: flex-end;
margin-bottom: 18px;
}
.copy-markdown {
display: inline-flex;
align-items: center;
gap: 7px;
min-height: 34px;
padding: 6px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 9px;
color: var(--vp-c-text-2);
background: var(--vp-c-bg-soft);
cursor: pointer;
font-size: 13px;
font-weight: 650;
}
.copy-markdown:hover,
.copy-markdown.copied {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
}
.copy-markdown svg {
width: 15px;
height: 15px;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
}
.source-link-wrap {
margin-top: 48px;
padding-top: 20px;
border-top: 1px solid var(--vp-c-divider);
font-size: 13px;
}
.source-link-wrap a {
color: var(--vp-c-text-3);
text-decoration: none;
}
.source-link-wrap a:hover {
color: var(--vp-c-brand-1);
}
.VPHome {
background:
radial-gradient(circle at 14% 14%, rgba(25, 201, 176, 0.14), transparent 30rem),
radial-gradient(circle at 86% 10%, rgba(49, 86, 217, 0.11), transparent 28rem);
}
.VPHero .name {
background: linear-gradient(120deg, var(--reme-green), var(--reme-blue));
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.VPHero .name,
.VPHero .text {
letter-spacing: -0.05em;
}
.VPHero .image-bg {
background: linear-gradient(135deg, rgba(25, 201, 176, 0.28), rgba(49, 86, 217, 0.2));
filter: blur(46px);
}
.VPFeature {
border-color: color-mix(in srgb, var(--vp-c-brand-1) 18%, var(--vp-c-divider));
border-radius: 16px;
background: linear-gradient(150deg, var(--vp-c-bg-elv), var(--vp-c-bg-soft));
transition: transform 160ms ease, box-shadow 160ms ease;
}
.VPFeature:hover {
transform: translateY(-3px);
box-shadow: 0 16px 34px rgba(21, 58, 43, 0.1);
}
@media (max-width: 768px) {
.vp-doc h1 { font-size: 34px; }
.vp-doc h2 { margin-top: 44px; font-size: 24px; }
.copy-markdown-wrap { margin-top: -8px; }
}

View file

@ -0,0 +1,15 @@
import { h } from "vue";
import DefaultTheme from "vitepress/theme";
import CopyMarkdownButton from "./CopyMarkdownButton.vue";
import SourceLink from "./SourceLink.vue";
import "./custom.css";
export default {
extends: DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
"doc-before": () => h(CopyMarkdownButton),
"doc-after": () => h(SourceLink),
});
},
};

157
docs/en/configuration.md Normal file
View file

@ -0,0 +1,157 @@
---
title: Configuration
description: ReMe configuration files, environment expansion, command-line overrides, and core components.
---
# Configuration
ReMe uses YAML or JSON to describe its Service, Jobs, and Components. The built-in default is `reme/config/default.yaml`. Select another configuration at startup and apply command-line overrides when needed.
## Precedence
Configuration is merged in this order, with later values winning:
1. `application_defaults` from enabled plugins.
2. The selected file; `default` is used when none is specified.
3. CLI dot-notation overrides.
```bash
reme start
reme start config=demo
reme start config=/absolute/path/to/app.yaml
reme start service.port=8181 workspace_dir=/data/reme
```
`config` accepts a built-in name or a `.yaml`, `.yml`, or `.json` file. Overrides are deep-merged, so changing `service.port` preserves sibling service settings.
## CLI values
Arguments use `key=value`; leading `-` or `--` is accepted:
```bash
reme start --service.port=8181 --service.web_enabled=false
```
Values support null, booleans, numbers, JSON arrays and objects, quoted JSON strings, and plain strings. Numeric-looking values with leading zeroes, such as `007`, remain strings. Quote values such as `"true"` in JSON when they must remain strings.
## Environment variables
Configuration recursively expands:
```yaml
api_key: ${LLM_API_KEY}
base_url: ${LLM_BASE_URL:-https://example.com/v1}
```
`${VAR}` fails when undefined; `${VAR:-default}` uses its fallback. ReMe also searches for `.env` from the command's working directory through at most five parents.
Keep secrets in `.env` or the process environment, never in committed configuration.
## Application fields
| Field | Default | Purpose |
|---|---|---|
| `app_name` | `ReMe` | Display name |
| `workspace_dir` | `.reme` | User-owned workspace root, normalized to an absolute path |
| `metadata_dir` | `metadata` | Rebuildable indexes, graphs, and catalogs |
| `session_dir` | `session` | Agent sessions; standard transcripts use `session/dialog` |
| `mem_session_dir` | `mem_session` | Agent-wrapper sessions and configuration |
| `resource_dir` | `resource` | External resources |
| `daily_dir` | `daily` | Daily memory |
| `digest_dir` | `digest` | Consolidated long-term memory |
| `timezone` | `Asia/Shanghai` | IANA timezone used for dates and cron jobs |
| `language` | empty | Default language for LLM interactions |
| `plugins` | `[]` | Installed plugins enabled for this Application |
| `service` | HTTP | Service configuration |
| `jobs` | default Jobs | Job configurations by name |
| `components` | defaults | Components grouped by type and name |
`session_dir` must remain workspace-relative.
## LLM
The default LLM uses an OpenAI-compatible interface:
```yaml
components:
as_llm:
default:
backend: openai
model: qwen3.7-plus
context_size: 200000
credential:
api_key: ${LLM_API_KEY:-}
base_url: ${LLM_BASE_URL:-}
```
Built-in registrations include `openai`, `anthropic`, `dashscope`, `deepseek`, `gemini`, `moonshot`, `ollama`, and `xai`. Their detailed model fields follow the corresponding AgentScope wrappers.
File operations, BM25 search, and wikilink traversal do not require an LLM. Evolution workflows such as `auto_memory`, `auto_resource`, and `auto_dream` do.
## Embeddings
Vector retrieval is disabled by default. Credentials alone do not enable it: configure `as_embedding`, `embedding_store`, and connect the store to `file_store`.
```yaml
components:
as_embedding:
default:
backend: openai
model: text-embedding-v4
dimensions: 1024
credential:
api_key: ${EMBEDDING_API_KEY}
base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
embedding_store:
default:
backend: local
as_embedding: default
file_store:
default:
backend: local
embedding_store: default
keyword_index: default
file_graph: default
```
Rebuild the embedding index after changing the model or dimensions.
## Service and Jobs
Minimal HTTP configuration:
```yaml
service:
backend: http
host: 127.0.0.1
port: 2333
web_enabled: true
mcp_enabled: true
mcp_path: /mcp
```
A Job declares a backend, parameter schema, and ordered Steps:
```yaml
jobs:
example:
backend: base
description: Example job
parameters:
type: object
properties:
text: { type: string }
required: [text]
steps:
- backend: example_step
```
Set `enable_serve: false` to keep a Job internal. Background and cron Jobs are never service-exposed.
## Inspect the effective configuration
```bash
reme app_config
```
The result is the merged, validated configuration with secrets redacted. Use it when diagnosing plugin or override precedence. The authoritative contracts remain `reme/schema/application_config.py` and `reme/config/default.yaml`.

View file

@ -209,6 +209,13 @@ Documentation lives under:
docs/
```
User guides should have matching `docs/zh/` and `docs/en/` versions and appear in the corresponding navigation in
`docs/.vitepress/config.mts`. The ReMe Studio, TypeScript, plugin, and benchmark READMEs remain canonical in their own
directories; `github-pages/scripts/generate-content.mjs` mirrors them during builds. Never edit `.generated/` or `dist/`.
The Job API reference is generated from `reme/config/default.yaml`. Update that YAML and its tests when a default Job
contract changes rather than editing generated pages.
Documentation should:
- Use clear titles that directly identify a capability or flow.
@ -216,6 +223,15 @@ Documentation should:
- Use real repository paths such as `reme/config/default.yaml`, `reme/steps/`, and `tests/unit/`.
- Describe default behavior according to the current code, `pyproject.toml`, and default configuration.
Validate the documentation site with:
```bash
cd github-pages
npm ci
npm test
npm run build
```
---
## Getting Help

71
docs/en/faq.md Normal file
View file

@ -0,0 +1,71 @@
---
title: Frequently Asked Questions
description: Quick answers for ReMe installation, services, models, retrieval, files, and plugins.
---
# Frequently Asked Questions
## Do basic file operations require a model API key?
No. `write`, `read`, `list`, `stat`, BM25 search, and wikilink traversal work without model credentials. `auto_memory`, `auto_resource`, and `auto_dream` require an LLM.
## Why is search still BM25-only after setting an embedding key?
Embeddings are disabled by default. Configure `as_embedding` and `embedding_store`, then connect `file_store.default.embedding_store` to that component. See [Configuration](./configuration.md#embeddings).
## Why did `reme reindex` not discover a new file?
`reindex` rebuilds indexes from current `file_chunks`; it does not scan the workspace. Check `index_update_loop`, the watched directory and extension, and `health_check`.
## How do I use another workspace?
```bash
reme start workspace_dir=/absolute/path/to/memory
```
Ordinary CLI calls discover the running service, so they do not need the workspace argument again.
## What if port 2333 is occupied?
Do not stop an unknown listener. Select another port:
```bash
reme start service.port=8181
```
Then confirm it with `reme find_reme`.
## Why is an installed plugin missing its Jobs?
Installation only makes the distribution discoverable in the active Python environment. Enable it for the Application:
```bash
reme start plugins='["auto-fin"]'
```
Restart a running service after changing package or enablement state.
## May I edit workspace Markdown directly?
Yes. Files are the source of truth and watchers ingest changes. Keep frontmatter valid, use complete workspace-relative wikilinks, and avoid unconditional concurrent saves.
## May I expose ReMe publicly?
Not with the default configuration alone. Jobs can write and delete, HTTP CORS is permissive, and there is no general authentication layer. Use a controlled network or authenticated TLS reverse proxy and restrict `service.jobs`.
## How should I back up and migrate memory?
Stop writes and back up the complete workspace. `session/`, `resource/`, `daily/`, and `digest/` are the key sources; `metadata/` can be backed up or rebuilt. See [Diagnostics, Backup, and Recovery](./operations.md).
## Why is Studio unavailable?
The base `reme-ai` package has no frontend assets. Install `reme-ai[web]` or `reme-ai[core]`, or set `service.web_static_dir`. Missing Studio assets do not disable the Job API.
## Which capabilities does the running service expose?
```bash
reme help
reme app_config
```
Static documentation describes defaults; plugins and custom configuration may change the active service.

36
docs/en/index.md Normal file
View file

@ -0,0 +1,36 @@
---
layout: home
title: ReMe Documentation
titleTemplate: false
hero:
name: ReMe
text: Memory that works for agents
tagline: Files remain yours. ReMe turns conversations and resources into readable, editable, searchable, interconnected local files.
image:
src: /favicon.svg
alt: ReMe
actions:
- theme: brand
text: Quick Start
link: /en/quick_start
- theme: alt
text: Configuration
link: /en/configuration
features:
- icon: 📁
title: Memory as files
details: Workspace files are the durable source of truth; indexes, graphs, and caches remain rebuildable.
link: /en/memory_as_file
- icon: 🧠
title: Memory workflows
details: Turn conversations and resources into daily notes, then consolidate them into connected long-term digests.
link: /en/auto_memory
- icon: 🔎
title: Search and graph
details: Combine keyword and optional vector retrieval with wikilink graph expansion.
link: /en/memory_search
- icon: 🔌
title: Agent integrations
details: Connect existing agents through the CLI, HTTP, MCP, and host adapters.
link: /en/integrations
---

63
docs/en/integrations.md Normal file
View file

@ -0,0 +1,63 @@
---
title: Agent Integrations
description: Connect ReMe to agents through the CLI, HTTP, MCP, Skills, and host adapters.
---
# Agent Integrations
ReMe keeps memory in an independent service and a user-owned workspace. Multiple agents can call the same memory system without binding storage to one model or host.
## Choose an interface
| Scenario | Recommended interface |
|---|---|
| Local script or hook | ReMe CLI |
| Application backend | HTTP Client |
| Tool-protocol host | MCP |
| TypeScript agent | `@agentscope-ai/reme` |
| Claude Code | MCP + Skill + Stop Hook |
| Hermes Agent | Memory provider adapter |
| Codex or another coding agent | `reme_memory` Skill or MCP |
## General memory loop
1. Before answering, call `search` for relevant memory.
2. Use `read` on high-value results and `traverse` when relationships matter.
3. Retain workspace-relative source paths in the answer.
4. At session end, pass source messages to `auto_memory`.
5. Let background or scheduled workflows consolidate daily notes into digest memory.
An empty search result must remain empty; do not present model inference as recalled history.
## MCP
The default HTTP service exposes streamable HTTP MCP at `http://127.0.0.1:2333/mcp`. Common tools include `search`, `read`, `traverse`, `list`, `auto_memory`, and `proactive`.
Use `service.jobs` to expose a read-only subset or keep write tools in a separate configuration.
## CLI and Skill
`skills/reme_memory/SKILL.md` defines a general workflow for agents that can run local commands: installation checks, service discovery, retrieval, reading, and persistence boundaries.
It deliberately avoids silently modifying Python environments, stopping unknown processes on port conflicts, writing recalled tool output back as conversation source, or persisting credentials.
## TypeScript, OpenClaw, and DeepSeek Harness
See [TypeScript Agent Integrations](./integrations/typescript.md) for the shared HTTP client, DeepSeek Harness adapter, OpenClaw adapter, and package validation.
## Claude Code
`integrations/claude_code/` provides streamable HTTP MCP configuration, a `reme-memory` Skill, and a Stop hook that calls `auto_memory_cc`. Follow that directory's README for installation.
## Hermes Agent
`integrations/hermes_agent/` provides a memory provider that recalls context before model calls and asynchronously invokes `auto_memory` after each turn.
## Production guidance
- choose a stable absolute `workspace_dir`;
- reuse a service discovered by `reme find_reme`;
- treat `reme help` as the active Job contract;
- apply timeouts and failure logging to writes;
- do not block the host's core response path when memory is temporarily unavailable;
- use authentication, TLS, and a minimal Job allowlist for remote access.

102
docs/en/operations.md Normal file
View file

@ -0,0 +1,102 @@
---
title: Diagnostics, Backup, and Recovery
description: ReMe health checks, logs, index maintenance, workspace backup, migration, and recovery.
---
# Diagnostics, Backup, and Recovery
ReMe recovery protects user-owned workspace files and rebuilds catalogs, indexes, and graphs from those sources. Never delete or rewrite user memory merely to repair derived state.
## Quick diagnosis
Run these in order:
```bash
reme find_reme
reme version
reme health_check
reme status
reme app_config
```
- `find_reme` confirms the actual host, port, and PID;
- `version` verifies that the CLI reaches the service;
- `health_check` reports component health;
- `status` estimates stateful component memory and process RSS;
- `app_config` returns the effective configuration with secrets redacted.
## Logs and common symptoms
`log_to_console` and `log_to_file` control logging. For startup failures, inspect the first exception rather than later client connection errors.
| Symptom | Check first |
|---|---|
| CLI cannot find ReMe | `reme find_reme`, process state, startup directory, and port |
| Automatic memory fails | LLM backend, model, API key, and base URL |
| Search is BM25-only | Whether an embedding store is connected to `file_store` |
| New files are absent | Directory, extension, watcher, and `health_check` |
| Studio fails but API works | Installed web extra, static path, and browser console |
| Installed plugin is unavailable | Python interpreter, `plugins` configuration, and service restart |
## Index maintenance
```bash
reme reindex scope=all
reme reindex scope=bm25
reme reindex scope=embedding
```
`reindex` rebuilds BM25 and/or embedding indexes from the current `file_chunks`. It does not scan the workspace, rechunk files, or rebuild the wikilink graph. Diagnose the watcher first when ingestion is the problem.
Rebuild a daily index page separately:
```bash
reme daily_reindex date=2026-09-04
```
## Backup
Stop writes or stop the service, then back up the complete workspace. The most important sources are:
- `session/` for conversation sources;
- `resource/` for external resources;
- `daily/` for daily memory;
- `digest/` for consolidated memory.
`metadata/` contains indexes, graphs, and catalogs. Backing it up accelerates restoration, but it is not the sole source of truth.
Use an explicit, stable absolute `workspace_dir` for durable deployments rather than relying on an incidental `.reme/` under the current directory.
## Migrate a workspace
1. Stop the old service to prevent writes during the copy.
2. Copy the complete workspace while preserving timestamps.
3. Start with the new absolute path:
```bash
reme start workspace_dir=/new/location/reme-memory
```
4. Run `health_check`, `status`, and a representative `search`.
5. Rebuild embeddings if their model or dimensions changed.
Do not push a workspace containing private conversations to a public repository.
## Recover derived state
Do not remove anything until a backup exists. Then:
1. preserve `session/`, `resource/`, `daily/`, and `digest/`;
2. record the effective configuration and component backends;
3. verify that the failure is limited to `metadata/`;
4. move suspect derived state to an isolated backup location;
5. restart with the same configuration and let watchers rebuild;
6. validate search, graph traversal, and daily indexes.
The internal layout of metadata files is not a public automation contract.
## Concurrent editing
When Studio or an editor saves a complete file, pass the mtime from `stat` as `save.expected_mtime`. A save then fails if another actor changed the file after it was opened, avoiding silent overwrites.
File Jobs enforce workspace containment and per-path locking. Do not bypass them to write arbitrary absolute paths.

View file

@ -0,0 +1,101 @@
---
title: Plugin Development
description: Create, register, configure, test, and publish a ReMe plugin.
---
# Plugin Development
A ReMe plugin is a regular Python distribution exposed through the `reme.plugins` entry-point group. Its package-level `plugin.yaml` can register Step and Component backends and provide default Application configuration.
## Minimal structure
```text
my-plugin/
├── pyproject.toml
└── src/my_plugin/
├── __init__.py
├── plugin.yaml
└── steps.py
```
`pyproject.toml`:
```toml
[project.entry-points."reme.plugins"]
my-plugin = "my_plugin"
```
`plugin.yaml`:
```yaml
name: my-plugin
backends:
my_step: my_plugin.steps:MyStep
application_defaults:
jobs:
my_action:
backend: base
description: Run my plugin action
parameters:
type: object
properties:
text: { type: string }
required: [text]
steps:
- backend: my_step
```
## Implement a Step
```python
from reme.components.component_registry import R
from reme.steps.base_step import BaseStep
@R.register("my_step")
class MyStep(BaseStep):
async def execute(self):
self.context.response.answer = self.context.data["text"]
```
Step instances belong to one Job invocation. Put shared in-memory state under a narrow `app_context.metadata` key. Promote state that needs lifecycle, locking, or persistence to a Component or workspace file.
## Configuration merge
`application_defaults` is a partial `ApplicationConfig`:
```text
plugin defaults < selected/default config < CLI overrides
```
Plugins must not rewrite user configuration. Their backends enter an Application-local registry only when the plugin appears in that Application's `plugins` list.
## Local validation
```bash
reme plugins validate ./path/to/my-plugin
reme plugins install ./path/to/my-plugin --editable
reme plugins list
reme plugins show my-plugin
reme start plugins='["my-plugin"]'
reme my_action text=hello
```
Validation imports plugin code, so run it only for trusted sources.
## Test boundaries
- create workspaces with `tmp_path`;
- mock network, model, and subprocess boundaries;
- verify disabled plugins do not mutate the built-in registry;
- verify plugin defaults and explicit configuration precedence;
- keep tasks, clients, and executors under Component lifecycle;
- never delete or rewrite user source files to repair derived state.
The repository's Daily Paper, Auto Fin, LME, and BEAM plugins are complete examples.
## Compatibility
Legacy Python Plugin descriptors and the `reme.configs` entry point remain supported during migration, but new plugins should use `plugin.yaml`. Enablement always belongs to an Application rather than a process-global switch.
See [Plugin Management](./plugin_management.md) for installation, upgrades, and removal.

View file

@ -1,5 +1,12 @@
---
title: Quick Start
description: Install and start ReMe, then complete a first file, retrieval, and automatic-memory workflow.
---
# Quick Start
This page gets one working loop running. See [Configuration](./configuration.md) for the full configuration contract and [Services and Deployment](./services.md) for HTTP or MCP integration.
## Installation
ReMe requires Python 3.11+.
@ -237,3 +244,5 @@ You can also specify a YAML or JSON configuration file:
```bash
reme start config=/path/to/custom.yaml
```
Continue with the [CLI Reference](./reference/cli.md), [Job API Reference](./reference/jobs.md), or [Diagnostics, Backup, and Recovery](./operations.md).

87
docs/en/reference/cli.md Normal file
View file

@ -0,0 +1,87 @@
---
title: CLI Reference
description: ReMe command syntax, service invocation, configuration overrides, and plugin commands.
---
# CLI Reference
The basic syntax is:
```text
reme ACTION key=value ...
```
## Start an Application
```bash
reme start
reme start config=demo
reme start workspace_dir=/data/reme service.port=8181
reme start job=search query="keywords" limit=5
```
`start job=<name>` runs one Job through a one-shot service; plain `start` runs the configured Service.
## Call Jobs
Once a service is running, each action name is a Job name:
```bash
reme help
reme health_check
reme search query="project decision" limit=10
reme read path=digest/wiki/project.md start_line=1 end_line=80
```
Use JSON for structured values:
```bash
reme auto_memory \
session_id=example \
messages='[{"role":"user","content":"Remember this preference"}]'
```
Client-selection arguments—`backend`, `transport`, `host`, `port`, `timeout`, `command`, `args`, and `show_metadata`—configure the client and never leak into the Job payload.
## Configuration overrides
```bash
reme start \
config=/path/to/custom.yaml \
service.port=8181 \
service.web_enabled=false \
plugins='["auto-fin"]'
```
Leading `-` or `--` is optional. Use dots for nested keys and JSON for arrays and objects.
## Service discovery
```bash
reme find_reme
```
This reports a discovered service but never starts or replaces a process.
## Plugin commands
Package management runs locally rather than through HTTP or MCP:
```bash
reme plugins list
reme plugins show auto-fin
reme plugins validate auto-fin
reme plugins install reme-auto-fin
reme plugins uninstall auto-fin
```
See [Plugin Management](../plugin_management.md) for the complete workflow.
## Discover active capabilities
The [Job API Reference](./jobs.md) describes the default configuration. Plugins and custom YAML may change the running service, so automation should prefer:
```bash
reme help
reme app_config
```

115
docs/en/services.md Normal file
View file

@ -0,0 +1,115 @@
---
title: Services and Deployment
description: Run ReMe through HTTP, SSE, MCP, the CLI, and ReMe Studio while respecting its local security boundary.
---
# Services and Deployment
ReMe can run as a local HTTP service, a standalone MCP server, or a one-shot CLI Job. The default starts HTTP on `127.0.0.1:2333` and serves JSON, SSE, streamable HTTP MCP, and optional ReMe Studio from one process.
## HTTP API
```bash
reme start
reme start service.host=127.0.0.1 service.port=8181
```
Regular Jobs become `POST /<job-name>`:
```bash
curl -s http://127.0.0.1:2333/search \
-H 'Content-Type: application/json' \
-d '{"query":"user preferences","limit":5}'
```
Job arguments live at the request body's top level. A regular response follows the `Response` schema:
```json
{"success": true, "answer": "...", "metadata": {}}
```
Unhandled Step failures become unsuccessful responses.
## Streaming and SSE
Jobs with `backend: stream` also use `POST /<job-name>`, returning `text/event-stream`. Error paths emit an error chunk and always terminate the stream. The default `chat` Job is streaming:
```bash
curl -N http://127.0.0.1:2333/chat \
-H 'Content-Type: application/json' \
-d '{"query":"Summarize my long-term preferences"}'
```
MCP does not expose Stream Jobs.
## MCP
The default HTTP service mounts streamable HTTP MCP at `/mcp`:
```yaml
service:
backend: http
mcp_enabled: true
mcp_path: /mcp
mcp_stateless_http: false
```
For a standalone MCP service:
```bash
reme start service.backend=mcp service.transport=stdio
reme start service.backend=mcp service.transport=sse service.port=2333
reme start service.backend=mcp service.transport=streamable-http service.port=2333
```
MCP tools come from non-stream Jobs with `enable_serve: true`. Use `service.jobs` as an allowlist. `injected_job_kwargs` adds server-managed values that callers cannot override.
```yaml
service:
backend: http
jobs: [search, read, traverse, auto_memory]
injected_job_kwargs:
tenant_id: local-user
tool_error_on_failure: true
```
## ReMe Studio
After installing `reme-ai[web]` or `reme-ai[core]`, the default HTTP origin also serves Studio:
```text
http://127.0.0.1:2333/
```
Disable it with `service.web_enabled=false` or select a build with `service.web_static_dir`. Missing static assets do not prevent the Job API from starting.
## One-shot Jobs
```bash
reme start job=search query="user preferences" limit=5
```
This selects the one-shot CLI Service while retaining the normal Application, Component, and Job lifecycle.
## Service discovery
```bash
reme find_reme
```
Ordinary `reme <action>` commands prefer the running service's actual backend, host, port, and transport. They fall back to local configuration only when no service is discovered.
## Security boundary
ReMe is local-first:
- the default binds to `127.0.0.1`;
- HTTP CORS allows any origin;
- Jobs may write, move, or delete files;
- the service layer has no general-purpose user authentication.
Do not expose the default service directly to the public internet. For remote access, place it on a controlled network or behind an authenticated TLS reverse proxy, apply access controls and request-size limits, and expose only necessary Jobs.
## OpenAPI
FastAPI exposes the active endpoints through `/docs`, `/redoc`, and `/openapi.json`. The Studio SPA fallback preserves these reserved paths.

43
docs/index.md Normal file
View file

@ -0,0 +1,43 @@
---
layout: home
title: ReMe Documentation
titleTemplate: false
head:
- - meta
- http-equiv: refresh
content: "0; url=/zh/"
- - link
- rel: canonical
href: https://reme.agentscope.io/zh/
hero:
name: ReMe
text: 让 Agent 真正记住
tagline: 文件属于你,记忆服务于 Agent。ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的本地文件。
image:
src: /favicon.svg
alt: ReMe
actions:
- theme: brand
text: 快速开始
link: /zh/quick_start
- theme: alt
text: 核心概念
link: /zh/memory_as_file
features:
- icon: 📁
title: Local-first
details: Workspace 文件是持久事实源;索引、图谱和缓存都可以重新构建。
link: /zh/memory_as_file
- icon: 🧠
title: Memory workflows
details: 将对话和资料写入 daily,自动整理为互相关联的长期 digest。
link: /zh/auto_memory
- icon: 🔎
title: Search and graph
details: 结合关键词、可选向量检索与 wikilink 图谱,渐进式展开上下文。
link: /zh/memory_search
- icon: 🔌
title: Agent integrations
details: 通过 CLI、HTTP、MCP 和宿主适配器接入已有 Agent。
link: /zh/integrations
---

5
docs/public/favicon.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs><linearGradient id="g" x1="8" y1="7" x2="57" y2="58" gradientUnits="userSpaceOnUse"><stop stop-color="#19c9b0"/><stop offset="1" stop-color="#3156d9"/></linearGradient></defs>
<rect x="4" y="4" width="56" height="56" rx="18" fill="url(#g)"/>
<path d="M20 46V18h13.2c7.2 0 11.8 3.8 11.8 10 0 4.3-2.2 7.5-6 9l7 9h-8l-5.8-7.8H27V46h-7Zm7-13.6h5.6c3.5 0 5.4-1.4 5.4-4.2 0-2.8-1.9-4.1-5.4-4.1H27v8.3Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 492 B

168
docs/zh/configuration.md Normal file
View file

@ -0,0 +1,168 @@
---
title: 基础配置
description: ReMe 配置文件、环境变量、命令行覆盖和核心组件配置。
---
# 基础配置
ReMe 使用 YAML 或 JSON 描述 Service、Job 和 Component。默认配置位于 `reme/config/default.yaml`;启动时可以选择其他配置,再用命令行覆盖其中的字段。
## 配置优先级
配置按下面的顺序合并,靠后的值优先:
1. 已启用插件提供的 `application_defaults`。
2. 选中的配置文件;未指定时使用内置 `default`。
3. 命令行 dot notation 覆盖。
```bash
reme start
reme start config=demo
reme start config=/absolute/path/to/app.yaml
reme start service.port=8181 workspace_dir=/data/reme
```
`config` 支持内置配置名以及 `.yaml`、`.yml`、`.json` 文件。覆盖采用深度合并,不会因为修改 `service.port` 而丢失 `service` 下的其他字段。
## 值的解析
CLI 参数使用 `key=value`,前导 `-` 或 `--` 也会被接受:
```bash
reme start --service.port=8181 --service.web_enabled=false
```
值支持:
- `null`、布尔值、整数和浮点数;
- JSON 数组和对象;
- JSON 引号字符串;
- 普通字符串。
类似 `007` 的前导零字符串不会被转换成数字。需要保留 `true`、`false` 等字面字符串时,使用 JSON 引号:`value='"true"'`。
## 环境变量
配置文件会递归展开两种表达式:
```yaml
api_key: ${LLM_API_KEY}
base_url: ${LLM_BASE_URL:-https://example.com/v1}
```
`${VAR}` 在变量未定义时会报错;`${VAR:-default}` 使用默认值。ReMe 还会从命令启动目录向上查找 `.env`,最多检查五级父目录。
不要把密钥提交到配置文件或 Git。推荐把密钥放在 `.env` 或进程环境中。
## Application 字段
| 字段 | 默认值 | 作用 |
|---|---|---|
| `app_name` | `ReMe` | 应用显示名称 |
| `workspace_dir` | `.reme` | 用户拥有的 workspace 根目录,会规范化为绝对路径 |
| `metadata_dir` | `metadata` | 索引、图谱和 catalog 等派生状态 |
| `session_dir` | `session` | Agent 对话记录;标准 transcript 位于 `session/dialog` |
| `mem_session_dir` | `mem_session` | Agent wrapper 的会话和配置 |
| `resource_dir` | `resource` | 外部资料 |
| `daily_dir` | `daily` | Daily memory |
| `digest_dir` | `digest` | 长期整理后的记忆 |
| `timezone` | `Asia/Shanghai` | Cron、日期和梦境流程使用的 IANA 时区 |
| `language` | 空 | LLM 交互默认语言 |
| `plugins` | `[]` | 为当前 Application 启用的已安装插件 |
| `service` | HTTP | 服务端配置 |
| `jobs` | 默认 Job | Job 名到 Job 配置的映射 |
| `components` | 默认组件 | 按类型和名称组织的组件配置 |
`session_dir` 必须保持 workspace-relative。其他 workspace 子目录也应使用清晰、稳定的相对名称。
## LLM 配置
默认 LLM 使用 OpenAI-compatible 接口:
```yaml
components:
as_llm:
default:
backend: openai
model: qwen3.7-plus
context_size: 200000
credential:
api_key: ${LLM_API_KEY:-}
base_url: ${LLM_BASE_URL:-}
```
可注册的内置 backend 包括 `openai`、`anthropic`、`dashscope`、`deepseek`、`gemini`、`moonshot`、`ollama` 和 `xai`。实际字段由对应 AgentScope model wrapper 决定。
基础文件操作、BM25 检索、wikilink 遍历不需要 LLM。`auto_memory`、`auto_resource`、`auto_dream` 等演化流程需要可用 LLM。
## Embedding 配置
向量检索默认关闭。只设置 `EMBEDDING_API_KEY` 不会自动启用它;还需要同时启用 `as_embedding`、`embedding_store`,并把它连接到 `file_store`:
```yaml
components:
as_embedding:
default:
backend: openai
model: text-embedding-v4
dimensions: 1024
credential:
api_key: ${EMBEDDING_API_KEY}
base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
embedding_store:
default:
backend: local
as_embedding: default
file_store:
default:
backend: local
embedding_store: default
keyword_index: default
file_graph: default
```
修改 embedding 模型或维度后,应重新构建 embedding 索引。
## Service 和 Job
最小 HTTP 配置:
```yaml
service:
backend: http
host: 127.0.0.1
port: 2333
web_enabled: true
mcp_enabled: true
mcp_path: /mcp
```
Job 由 backend、参数 schema 和顺序执行的 Step 组成:
```yaml
jobs:
example:
backend: base
description: Example job
parameters:
type: object
properties:
text: { type: string }
required: [text]
steps:
- backend: example_step
```
设置 `enable_serve: false` 可以保留内部 Job、禁止 Service 暴露。后台和 Cron Job始终不会作为请求端点暴露。
## 查看生效配置
服务启动后运行:
```bash
reme app_config
```
返回的是已合并、已校验并隐藏密钥后的配置。排查覆盖顺序或插件配置时,应以它为准,而不是只查看某一个 YAML 文件。
完整字段定义以 `reme/schema/application_config.py` 和 `reme/config/default.yaml` 为准。

View file

@ -190,6 +190,12 @@ pytest tests/unit/test_reme_cli.py
docs/
```
用户指南应同时提供 `docs/zh/` 与 `docs/en/` 版本,并在 `docs/.vitepress/config.mts` 的对应导航中注册。ReMe Studio、
TypeScript、插件和评测的 README 是各自目录中的规范源文件;`github-pages/scripts/generate-content.mjs` 会在构建时镜像它们,
不要编辑 `.generated/` 或 `dist/`。
`Job API 参考`由 `reme/config/default.yaml` 自动生成。修改默认 Job 参数时更新 YAML 和测试,不要手工维护生成页。
建议文档保持:
- 标题明确,直接说明能力或流程。
@ -197,6 +203,15 @@ docs/
- 涉及路径时使用仓库内真实路径,例如 `reme/config/default.yaml`、`reme/steps/`、`tests/unit/`。
- 涉及默认行为时,以当前代码和 `pyproject.toml`、默认配置为准。
文档站检查:
```bash
cd github-pages
npm ci
npm test
npm run build
```
---
## 获取帮助

75
docs/zh/faq.md Normal file
View file

@ -0,0 +1,75 @@
---
title: 常见问题
description: ReMe 安装、服务、模型、检索、文件和插件问题的快速答案。
---
# 常见问题
## 基础文件操作需要模型 API Key 吗?
不需要。`write`、`read`、`list`、`stat`、BM25 搜索和 wikilink 遍历可以在没有模型凭据时运行。`auto_memory`、`auto_resource` 和 `auto_dream` 需要 LLM。
## 为什么配置了 Embedding Key 仍然只有 BM25?
Embedding 默认未启用。除了凭据,还必须配置 `as_embedding`、`embedding_store`,并让 `file_store.default.embedding_store` 指向该组件。参见[基础配置](./configuration.md#embedding-配置)。
## 为什么 `reme reindex` 没有发现新文件?
`reindex` 只从当前 `file_chunks` 重建 BM25 或 embedding 索引,不扫描 workspace。检查 `index_update_loop` watcher、文件目录、后缀和 `health_check`。
## 怎样使用另一个 workspace?
启动时传入稳定的绝对路径:
```bash
reme start workspace_dir=/absolute/path/to/memory
```
普通 CLI 调用会发现运行服务,不必重复 workspace 参数。
## 端口 2333 被占用怎么办?
不要停止未知监听者。选择另一个端口:
```bash
reme start service.port=8181
```
然后用 `reme find_reme` 确认发现结果。
## 为什么插件安装后仍然没有对应 Job?
安装只让 distribution 在当前 Python 环境中可见。还需要在应用配置中启用:
```bash
reme start plugins='["auto-fin"]'
```
修改安装或启用状态后需要重启已运行的服务。
## 可以直接编辑 workspace 中的 Markdown 吗?
可以。文件是事实源,watcher 会摄取修改。应保留有效 frontmatter、使用完整 workspace-relative wikilink,并避免同时由多个编辑器无条件覆盖同一文件。
## 可以把服务暴露到公网吗?
默认配置不适合直接公网暴露。服务包含写入和删除 Job,HTTP CORS 宽松,且没有通用认证层。请使用受控网络或带 TLS、认证、访问控制的反向代理,并限制 `service.jobs`。
## 怎样备份和迁移?
停止写入后备份整个 workspace。`session/`、`resource/`、`daily/` 和 `digest/` 是最重要的用户数据;`metadata/` 可以随同备份,也可以从源文件重建。详见[诊断、备份与恢复](./operations.md)。
## Studio 找不到怎么办?
基础 `reme-ai` 包不包含前端资源。安装 `reme-ai[web]` 或 `reme-ai[core]`,或通过 `service.web_static_dir` 指向构建产物。Studio 缺失不会影响 Job API。
## 当前服务到底开放了哪些能力?
运行:
```bash
reme help
reme app_config
```
静态文档描述默认配置;运行服务可能由自定义配置和插件改变。

36
docs/zh/index.md Normal file
View file

@ -0,0 +1,36 @@
---
layout: home
title: ReMe 文档
titleTemplate: false
hero:
name: ReMe
text: 让 Agent 真正记住
tagline: 文件属于你,记忆服务于 Agent。ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的本地文件。
image:
src: /favicon.svg
alt: ReMe
actions:
- theme: brand
text: 快速开始
link: /zh/quick_start
- theme: alt
text: 查看配置
link: /zh/configuration
features:
- icon: 📁
title: 文件即记忆
details: Workspace 文件是持久事实源;索引、图谱和缓存都可以重新构建。
link: /zh/memory_as_file
- icon: 🧠
title: 自动记忆
details: 将对话和资料写入 daily,自动整理为互相关联的长期 digest。
link: /zh/auto_memory
- icon: 🔎
title: 检索与图谱
details: 结合关键词、可选向量检索与 wikilink 图谱,渐进式展开上下文。
link: /zh/memory_search
- icon: 🔌
title: Agent 集成
details: 通过 CLI、HTTP、MCP 和宿主适配器接入已有 Agent。
link: /zh/integrations
---

88
docs/zh/integrations.md Normal file
View file

@ -0,0 +1,88 @@
---
title: Agent 集成
description: 通过 CLI、HTTP、MCP、Skill 和宿主适配器把 ReMe 接入 Agent。
---
# Agent 集成
ReMe 把记忆能力放在独立服务和用户拥有的 workspace 中。Agent 可以通过标准接口调用同一套记忆,而不必把存储逻辑绑定到某一个模型或宿主。
## 选择接入方式
| 场景 | 推荐方式 |
|---|---|
| 本机脚本或 Hook | ReMe CLI |
| 应用后端 | HTTP Client |
| 支持工具协议的 Agent | MCP |
| TypeScript Agent | `@agentscope-ai/reme` |
| Claude Code | MCP + Skill + Stop Hook |
| Hermes Agent | Memory provider adapter |
| Codex 或其他 coding agent | `reme_memory` Skill 或 MCP |
## 通用接入循环
一个完整但可控的 Agent 记忆循环通常包含:
1. 会话开始或回答前,用 `search` 找到相关记忆;
2. 对高价值结果使用 `read`,必要时用 `traverse` 展开关系;
3. 在回答中保留 workspace-relative 来源路径;
4. 会话结束后,把原始消息交给 `auto_memory`;
5. 由后台或定时任务把 daily 内容整理到 digest。
搜索不到内容时应明确返回空结果,不应把模型推测当成历史记忆。
## MCP
默认 HTTP 服务在 `http://127.0.0.1:2333/mcp` 提供 streamable HTTP MCP。常用工具包括:
- `search`
- `read`
- `traverse`
- `list`
- `auto_memory`
- `proactive`
根据宿主风险模型,可以用 `service.jobs` 只暴露只读工具,或将写入工具放在单独配置中。
## CLI 和 Skill
仓库中的 `skills/reme_memory/SKILL.md` 描述了一个通用 Agent 工作流,包括安装检查、服务发现、检索、读取和写入边界。它适合能够执行本地命令的 Agent。
Skill 不应:
- 未经允许安装或升级 Python 环境;
- 发现端口冲突后停止未知进程;
- 把召回的工具结果再次写入对话来源;
- 将密钥或敏感信息写入记忆。
## TypeScript、OpenClaw 与 DeepSeek Harness
统一 TypeScript 客户端和宿主适配器见 [TypeScript Agent 集成](./integrations/typescript.md)。它包含:
- HTTP Client;
- DeepSeek Harness adapter;
- OpenClaw adapter;
- 构建与发布检查。
## Claude Code
仓库的 `integrations/claude_code/` 提供:
- streamable HTTP MCP 配置;
- `reme-memory` Skill;
- 会话停止时调用 `auto_memory_cc` 的 Hook。
完整安装步骤以仓库中的 `integrations/claude_code/README.md` 为准。
## Hermes Agent
`integrations/hermes_agent/` 提供 memory provider:模型调用前检索相关记忆,每轮结束后异步调用 `auto_memory`。完整配置见该目录 README。
## 生产接入建议
- 明确选择稳定、绝对的 `workspace_dir`;
- 启动前复用 `reme find_reme` 发现的服务;
- 以 `reme help` 为当前 Job 契约;
- 为写入动作设置超时和失败日志;
- 不因记忆服务暂时不可用而阻塞宿主的核心回答流程;
- 对远程访问使用认证、TLS 和最小 Job allowlist。

View file

@ -0,0 +1,67 @@
---
title: Claude Code 集成
description: 通过 MCP、reme-memory Skill 和 Stop Hook 将 Claude Code 连接到 ReMe。
---
# Claude Code 集成
ReMe 的 Claude Code 插件提供长期记忆召回,并在每次会话结束后异步记录对话。Daily 到 digest 的整理仍由共享的 ReMe 服务负责。
## 能力
- 通过 MCP 使用 `search`、`traverse`、`daily_list`、`frontmatter_read`、`read`、`auto_memory_cc` 等工具;
- `reme-memory` Skill 在回答前召回长期记忆并保留来源路径;
- Stop Hook 只把 Claude Code `session_id` 传给服务端,服务端从本地 transcript 解析会话;
- 记录在脱离 Claude Code 的后台进程中进行,不延迟退出;服务不可用时记录日志并结束。
## 部署模型
插件连接到用户预先启动的共享 HTTP MCP 服务,不为每个 Claude Code 窗口创建 ReMe。这样所有窗口共享一个 workspace、一组 watcher 和一次 dream cron。
## 准备 ReMe
```bash
pip install "reme-ai[core]"
```
在稳定目录配置 LLM 环境,然后启动:
```bash
reme start service.backend=http
```
默认 JSON Job API 和 MCP 地址分别位于同一个 `127.0.0.1:2333` 服务,MCP 路径是 `/mcp`。使用其他端口时,必须同步修改插件的 `.mcp.json`。
默认搜索使用 BM25;只有启用向量检索时才需要 Embedding 配置。
## 安装插件
在 Claude Code 中运行:
```text
/plugin marketplace add ./integrations/claude_code
/plugin install reme@reme-marketplace
```
重启 Claude Code,再运行 `/mcp`,确认 `reme` server 和工具已经连接。
## Hook 与路径
- MCP 配置:`integrations/claude_code/reme/.mcp.json`;
- 自动记忆 Hook:`integrations/claude_code/reme/hooks/auto_memory.py`;
- Hook 日志:`integrations/claude_code/reme/logs/auto_memory_hook.log`;
- 默认 transcript 根目录:`~/.claude/projects`;
- 可通过 `CLAUDE_CONFIG_DIR` 修改 transcript 根目录;
- 可通过 `REME_HOST`、`REME_PORT` 覆盖 Hook 使用的服务地址。
Hook 需要 `python3` 位于 `PATH`。MCP 工具名前缀可能随 Claude Code 版本包含 server segment;Skill 使用 `mcp__reme__*` 匹配这一差异。
## 验证
1. `reme health_check` 返回健康;
2. Claude Code `/mcp` 显示 ReMe;
3. `reme-memory` 能召回一条已存在记忆;
4. 完成测试会话后,Hook 日志没有错误;
5. 对应内容出现在当天 daily note 中。
英文原始部署说明位于 `integrations/claude_code/README.md`。

View file

@ -0,0 +1,59 @@
---
title: Hermes Agent 集成
description: 使用 ReMe memory provider 在 Hermes 调用模型前召回、每轮结束后异步记录。
---
# Hermes Agent 集成
Hermes memory provider 连接到一个已经运行的 ReMe HTTP 服务,在每次模型调用前召回相关记忆,并在用户/助手回合完成后异步调用 `auto_memory`。
## Workspace 隔离
ReMe 的搜索范围是一个完整 workspace。多个 Hermes profile 指向同一个 workspace 时会共享召回结果;需要隔离时,为每个 profile 使用独立 workspace 和端点。
```bash
reme start \
workspace_dir="/absolute/path/to/reme-hermes-default" \
service.backend=http \
service.host=127.0.0.1 \
service.port=2333
```
自动记忆需要 LLM;默认 BM25 搜索不需要 Embedding。
## 安装与配置
```bash
hermes plugins install agentscope-ai/ReMe/integrations/hermes_agent
hermes memory setup
```
选择 `reme`,接受默认的 `http://127.0.0.1:2333`,或输入上一步使用的端点。Setup 会先调用 `health_check`,只有新端点健康时才替换现有 provider 配置。
配置存放在 `$HERMES_HOME/reme.json`:
```json
{
"endpoint": "http://127.0.0.1:2333",
"request_timeout": 600.0,
"recall_timeout": 5.0,
"health_timeout": 2.0,
"health_retry_seconds": 30.0,
"shutdown_timeout": 30.0,
"recall_limit": 5
}
```
运行 `hermes memory status` 检查安装和配置。新的 Hermes 会话还会重新检查端点健康状态。
## 生命周期和失败行为
- `prefetch` 调用 ReMe `search`,Hermes 将结果放入受保护的 memory context;
- `sync_turn` 把完成的回合加入串行后台写队列,再调用 `auto_memory`;
- cron、flush 和 subagent context 不写入对话记忆;
- 健康检查失败后,在 cooldown 结束前暂停召回和记录;
- 召回与写入有独立 cooldown,单项失败不会关闭另一项;
- 召回使用较短超时,避免慢搜索长期阻塞模型调用;
- shutdown 会在有限时间内排空写队列,ReMe 服务仍由用户独立管理。
英文权威安装说明位于 `integrations/hermes_agent/README.md`。

106
docs/zh/operations.md Normal file
View file

@ -0,0 +1,106 @@
---
title: 诊断、备份与恢复
description: ReMe 服务健康检查、日志、索引维护、备份迁移和常见恢复流程。
---
# 诊断、备份与恢复
ReMe 的恢复原则是:保护 workspace 中的用户文件,通过源文件重建索引、图谱和 catalog。不要为了修复索引而删除或改写用户记忆。
## 快速诊断
依次执行:
```bash
reme find_reme
reme version
reme health_check
reme status
reme app_config
```
- `find_reme`:服务是否存在,以及实际 host、port、PID;
- `version`:CLI 能否成功访问服务;
- `health_check`:组件健康状态;
- `status`:状态组件的内存估算和进程 RSS;
- `app_config`:隐藏密钥后的实际生效配置。
## 日志
`log_to_console` 和 `log_to_file` 控制日志目标。排查启动失败时先查看第一条异常,而不是后续客户端连接错误。
常见类别:
| 现象 | 优先检查 |
|---|---|
| CLI 找不到服务 | `reme find_reme`、启动目录、端口和进程状态 |
| 自动记忆失败 | LLM backend、model、API key、base URL |
| 只有 BM25 结果 | embedding 组件是否真正接入 `file_store` |
| 新文件没有进入搜索 | 文件所在目录、后缀、watcher 和 `health_check` |
| Studio 空白但 API 正常 | web extra、静态构建路径和浏览器控制台 |
| 插件安装后不可用 | 当前 Python 解释器、`plugins` 配置、服务重启 |
## 索引维护
```bash
reme reindex scope=all
reme reindex scope=bm25
reme reindex scope=embedding
```
`reindex` 从当前 `file_chunks` 重建 BM25 和/或 embedding 派生索引。它不会扫描 workspace、重新分块或重建 wikilink 图谱。
如果问题发生在文件摄取阶段,应先确认后台 watcher 正常运行;不能把 `reindex` 当成通用“重新扫描”命令。
Daily 索引页可单独重建:
```bash
reme daily_reindex date=2026-09-04
```
## 备份
停止写入或停止服务后,优先备份整个 workspace。最重要的目录是:
- `session/`:原始对话来源;
- `resource/`:外部资料;
- `daily/`:每日记忆;
- `digest/`:长期记忆。
`metadata/` 包含索引、图谱和 file catalog。一起备份可以加速恢复,但它不是唯一事实源。
不要只备份进程目录下偶然生成的 `.reme/`;生产使用应明确设置稳定的绝对 `workspace_dir`。
## 迁移 workspace
1. 停止旧服务,避免迁移期间继续写入。
2. 复制完整 workspace,并保留文件时间信息。
3. 使用新的绝对路径启动:
```bash
reme start workspace_dir=/new/location/reme-memory
```
4. 运行 `health_check`、`status` 和一次代表性 `search`。
5. 如果 embedding 模型或维度发生变化,再重建 embedding 索引。
普通 Markdown 和资源文件可以使用版本控制或同步工具;包含敏感对话的 workspace 不应推送到公开仓库。
## 从派生状态故障恢复
在确认备份可用前,不要删除任何内容。恢复顺序应为:
1. 保留 `session/`、`resource/`、`daily/` 和 `digest/`;
2. 记录当前配置和组件 backend;
3. 确认故障只发生在 `metadata/`;
4. 将有问题的派生状态移到隔离备份位置;
5. 用同一配置启动 ReMe,让 watcher 从源文件重建;
6. 验证搜索、图谱和 Daily 索引。
具体 metadata 文件属于实现细节,不应在自动化脚本里依赖其内部格式。
## 并发编辑
Studio 或其他编辑器保存完整文件时,应使用 `stat` 返回的 mtime 作为 `save.expected_mtime`。如果文件在打开后被外部修改,保存会失败,从而避免静默覆盖。
文件 Job 会校验 workspace containment,并对同一路径加锁。不要绕过这些 Job 直接向不受控制的绝对路径写入。

View file

@ -0,0 +1,101 @@
---
title: 插件开发
description: 创建、注册、配置、测试和发布 ReMe 插件。
---
# 插件开发
ReMe 插件是一个普通 Python distribution,通过 `reme.plugins` entry-point group 暴露 package-level `plugin.yaml`。插件可以注册新的 Step、Component backend,并提供默认 Application 配置。
## 最小结构
```text
my-plugin/
├── pyproject.toml
└── src/my_plugin/
├── __init__.py
├── plugin.yaml
└── steps.py
```
`pyproject.toml`:
```toml
[project.entry-points."reme.plugins"]
my-plugin = "my_plugin"
```
`plugin.yaml`:
```yaml
name: my-plugin
backends:
my_step: my_plugin.steps:MyStep
application_defaults:
jobs:
my_action:
backend: base
description: Run my plugin action
parameters:
type: object
properties:
text: { type: string }
required: [text]
steps:
- backend: my_step
```
## 实现 Step
```python
from reme.components.component_registry import R
from reme.steps.base_step import BaseStep
@R.register("my_step")
class MyStep(BaseStep):
async def execute(self):
self.context.response.answer = self.context.data["text"]
```
Step 实例属于单次 Job 调用。跨调用的内存状态应放在带命名空间的 `app_context.metadata`;需要生命周期、锁或持久化时,应升级为 Component 或 workspace 文件。
## 配置合并
`application_defaults` 是不完整的 `ApplicationConfig`。合并顺序为:
```text
插件默认值 < 选中/default 配置 < CLI 覆盖
```
插件不应自动修改用户的配置文件。只有在 Application 的 `plugins` 列表中显式启用后,插件 backend 才会加入该 Application 的局部 registry。
## 本地验证
```bash
reme plugins validate ./path/to/my-plugin
reme plugins install ./path/to/my-plugin --editable
reme plugins list
reme plugins show my-plugin
reme start plugins='["my-plugin"]'
reme my_action text=hello
```
校验会导入插件代码,因此只应对可信源码执行。
## 测试边界
- 使用 `tmp_path` 创建 workspace;
- Mock 网络、模型和子进程;
- 验证插件未启用时不会污染 built-in registry;
- 验证默认配置与显式配置的合并优先级;
- 验证后台任务、客户端和 executor 都由 Component 生命周期关闭;
- 验证失败不会删除或重写用户源文件。
仓库中的 `plugins/daily_paper`、`plugins/auto-fin`、`plugins/lme` 和 `plugins/beam` 是完整参考实现。
## 兼容性
迁移期间仍兼容旧的 Python Plugin descriptor 和 `reme.configs` entry point,但新插件应使用 `plugin.yaml`。不要依赖进程级全局 enable/disable 状态;插件启用始终属于具体 Application 配置。
包管理命令、升级与卸载行为见[插件管理](./plugin_management.md)。

View file

@ -1,5 +1,12 @@
---
title: 快速开始
description: 安装并启动 ReMe,完成文件写入、检索和自动记忆的第一个闭环。
---
# 快速开始
本页用于完成第一次可运行闭环。需要完整配置字段时查看[基础配置](./configuration.md);接入 HTTP 或 MCP 时查看[服务与部署](./services.md)。
## 安装
ReMe 要求 Python 3.11+。
@ -228,3 +235,5 @@ reme start \
```bash
reme start config=/path/to/custom.yaml
```
下一步可以查看 [CLI 参考](./reference/cli.md)、[Job API 参考](./reference/jobs.md)和[诊断、备份与恢复](./operations.md)。

87
docs/zh/reference/cli.md Normal file
View file

@ -0,0 +1,87 @@
---
title: CLI 参考
description: ReMe 命令行语法、服务调用、配置覆盖与插件命令。
---
# CLI 参考
ReMe 的基本语法是:
```text
reme ACTION key=value ...
```
## 启动应用
```bash
reme start
reme start config=demo
reme start workspace_dir=/data/reme service.port=8181
reme start job=search query="关键词" limit=5
```
`start job=<name>` 运行一次性 Job;普通 `start` 启动配置中的 Service。
## 调用 Job
服务运行后,Action 名就是 Job 名:
```bash
reme help
reme health_check
reme search query="项目决策" limit=10
reme read path=digest/wiki/project.md start_line=1 end_line=80
```
复杂值使用 JSON:
```bash
reme auto_memory \
session_id=example \
messages='[{"role":"user","content":"记住这条偏好"}]'
```
服务选择参数 `backend`、`transport`、`host`、`port`、`timeout`、`command`、`args` 和 `show_metadata` 只用于构造客户端,不会泄漏到 Job 参数。
## 配置覆盖
```bash
reme start \
config=/path/to/custom.yaml \
service.port=8181 \
service.web_enabled=false \
plugins='["auto-fin"]'
```
参数前的 `-` 或 `--` 可以省略。嵌套键使用点号,数组和对象使用 JSON。
## 服务发现
```bash
reme find_reme
```
成功时输出可复用的服务信息;找不到服务时不会自动启动新进程。
## 插件命令
插件包管理是本地命令,不经过 HTTP 或 MCP:
```bash
reme plugins list
reme plugins show auto-fin
reme plugins validate auto-fin
reme plugins install reme-auto-fin
reme plugins uninstall auto-fin
```
完整说明见[插件管理](../plugin_management.md)。
## 发现当前能力
默认 Job 参数见 [Job API 参考](./jobs.md)。运行配置可能由插件和自定义 YAML 改变,因此自动化程序应优先调用:
```bash
reme help
reme app_config
```

131
docs/zh/services.md Normal file
View file

@ -0,0 +1,131 @@
---
title: 服务与部署
description: 使用 ReMe 的 HTTP、SSE、MCP 和 Studio 服务,并理解默认安全边界。
---
# 服务与部署
ReMe 可以作为本地 HTTP 服务、独立 MCP Server 或一次性 CLI Job 运行。默认模式是在 `127.0.0.1:2333` 启动 HTTP 服务,并在同一进程中提供 JSON API、SSE、MCP 与可选的 ReMe Studio。
## HTTP 服务
```bash
reme start
reme start service.host=127.0.0.1 service.port=8181
```
普通 Job 暴露为 `POST /<job-name>`:
```bash
curl -s http://127.0.0.1:2333/search \
-H 'Content-Type: application/json' \
-d '{"query":"用户偏好","limit":5}'
```
请求体允许 Job 参数位于顶层。响应遵循 `Response`:
```json
{
"success": true,
"answer": "...",
"metadata": {}
}
```
未被 Step 捕获的错误会转换成 `success: false` 的响应。
## Streaming 与 SSE
`backend: stream` 的 Job 仍使用 `POST /<job-name>`,但响应类型是 `text/event-stream`。每个 chunk 使用统一的 streaming schema;失败时会发出错误 chunk,并以终止事件结束。
默认 `chat` 是 Stream Job:
```bash
curl -N http://127.0.0.1:2333/chat \
-H 'Content-Type: application/json' \
-d '{"query":"总结我的长期偏好"}'
```
MCP 不暴露 Stream Job。
## MCP
默认 HTTP 服务会在 `/mcp` 挂载 streamable HTTP MCP:
```yaml
service:
backend: http
mcp_enabled: true
mcp_path: /mcp
mcp_stateless_http: false
```
也可以改用独立 MCP Service:
```bash
reme start service.backend=mcp service.transport=stdio
reme start service.backend=mcp service.transport=sse service.port=2333
reme start service.backend=mcp service.transport=streamable-http service.port=2333
```
MCP tool 来自 `enable_serve: true` 的非流式 Job。`service.jobs` 可以设置允许列表;`injected_job_kwargs` 可以注入服务端管理、调用方不能覆盖的参数。
```yaml
service:
backend: http
jobs: [search, read, traverse, auto_memory]
injected_job_kwargs:
tenant_id: local-user
tool_error_on_failure: true
```
## ReMe Studio
安装 `reme-ai[web]` 或 `reme-ai[core]` 后,默认 HTTP 地址同时提供 ReMe Studio:
```text
http://127.0.0.1:2333/
```
可通过配置关闭或指定自定义构建:
```yaml
service:
web_enabled: false
# web_static_dir: /absolute/path/to/static
```
静态资源缺失不会阻止 Job API 启动。
## 一次性 Job
需要脚本式执行而不常驻服务时:
```bash
reme start job=search query="用户偏好" limit=5
```
它会切换到一次性 CLI Service,但仍经过正常的 Application、Component 和 Job 生命周期。
## 服务发现
```bash
reme find_reme
```
ReMe 会记录本机运行服务的启动参数。普通 `reme <action>` 优先使用实际运行服务的 backend、host、port 和 transport;找不到运行服务时才回退到本地配置。
## 安全边界
ReMe 默认定位为本地服务:
- 默认绑定 `127.0.0.1`;
- HTTP CORS 配置允许任意 origin;
- Job 可进行文件写入、移动和删除;
- 当前服务层不提供通用用户认证。
不要直接把默认服务暴露到公网。需要远程访问时,在受控网络或带身份认证、TLS、访问控制和请求大小限制的反向代理后部署,并通过 `service.jobs` 只开放必要 Job。
## OpenAPI
HTTP 服务使用 FastAPI。启用 HTTP 服务时,可以通过 `/docs`、`/redoc` 和 `/openapi.json` 查看当前配置实际注册的端点;Studio 的 SPA fallback 不会覆盖这些保留路径。

View file

@ -1,8 +1,8 @@
# ReMe GitHub Pages
# ReMe documentation site
This directory contains the standalone Vite documentation site published at <https://reme.agentscope.io>. The
GitHub Pages fallback is <https://agentscope-ai.github.io/ReMe/>. It does not depend on the ReMe Studio application in
`reme_studio/`.
This package builds the VitePress site published at <https://reme.agentscope.io>. The canonical documentation lives in
`docs/`; selected product, integration, plugin, and benchmark READMEs are mirrored into a disposable source tree during
the build. Do not edit `.generated/` or `dist/`.
## Requirements
@ -11,79 +11,41 @@ GitHub Pages fallback is <https://agentscope-ai.github.io/ReMe/>. It does not de
## Local development
From the repository root:
```bash
cd github-pages
npm install
npm ci
npm run dev
```
Open the URL printed by Vite, normally <http://localhost:5173/>. The development server watches the frontend source.
When a repository Markdown file changes, restart the development command to regenerate the documentation content.
The development server prints its local URL. Restart it after changing a mirrored README or `reme/config/default.yaml`
so the generated source tree and Job reference are refreshed. Changes under `docs/` are also refreshed on restart.
For subsequent installs or CI-compatible dependency installation, use:
```bash
npm ci
```
## Preview the production build
Build and start the preview server:
## Validation
```bash
npm test
npm run build
npm run preview
```
Open the URL printed by Vite, normally <http://localhost:4173/>. Production assets use relative paths so the same build
works on both the custom domain and the GitHub Pages project path.
The test suite verifies bilingual core pages, canonical-source mappings, generated Job coverage, and disposable output.
The production build is written to `github-pages/dist/` for the existing GitHub Pages workflow.
The generated `dist/` and `.generated/` directories are disposable build output and are excluded from Git.
## Sources
## Documentation sources
The build script reads the canonical repository files directly. Do not edit generated copies under `.generated/` or
`dist/`.
- `README.md` and `README_ZH.md`: project introductions
- `docs/en/` and `docs/zh/`: English and Chinese guides
- `docs/figure/`: documentation images
- `reme_studio/README.md` and `reme_studio/README_ZH.md`: ReMe Studio guide
- `typescript/README.md` and `typescript/README_ZH.md`: TypeScript client, DeepSeek Harness, and OpenClaw integration guide
- `docs/`: canonical guides, VitePress configuration, theme, and brand assets
- `reme/config/default.yaml`: generated callable Job reference
- `reme_studio/README*.md`: ReMe Studio
- `typescript/README*.md`: TypeScript client and adapters
- `plugins/*/README*.md`: plugin guides
- `benchmark/{beam,longmemeval,pibench,toolmemory}/README*.md`: benchmark guides and results
- `AGENTS.md`: repository development guide
- `benchmark/*/README*.md`: benchmark guides
- `scripts/generate-content.mjs`: source mirroring and reference generation
To add or reorganize a document in the site navigation, update
[`scripts/generate-content.mjs`](./scripts/generate-content.mjs). Presentation and interaction code lives in `src/`.
## Project structure
```text
github-pages/
├── index.html
├── package.json
├── scripts/
│ └── generate-content.mjs
├── src/
│ ├── main.js
│ └── styles.css
└── vite.config.js
```
When adding a canonical guide, add both `docs/zh/<name>.md` and `docs/en/<name>.md`, then include it in the appropriate
sidebar in `docs/.vitepress/config.mts`. Add repository-owned READMEs to `externalDocuments` in the generator rather than
duplicating their full content under `docs/`.
## Deployment
The repository workflow `.github/workflows/deploy-docs.yml` builds this directory and publishes `dist/` to GitHub Pages.
It runs after relevant documentation or site files change on `main`, and it can also be started manually from the
GitHub Actions page.
The repository's **Settings → Pages → Build and deployment → Source** must be set to **GitHub Actions**. Its custom
domain must be set to `reme.agentscope.io`; `public/CNAME` preserves that domain in the published artifact.
Useful links:
- ReMe documentation: <https://reme.agentscope.io>
- GitHub Pages fallback: <https://agentscope-ai.github.io/ReMe/>
- ReMe repository: <https://github.com/agentscope-ai/ReMe>
`.github/workflows/deploy-docs.yml` uses the reusable documentation build workflow and publishes `dist/` to GitHub
Pages. `public/CNAME` preserves the `reme.agentscope.io` custom domain.

View file

@ -1,17 +0,0 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="ReMe documentation — a local-first, file-native memory system for agents."
/>
<link rel="icon" type="image/svg+xml" href="%BASE_URL%favicon.svg" />
<title>ReMe Documentation</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -7,16 +7,14 @@
"node": ">=22.13.0"
},
"scripts": {
"dev": "node scripts/generate-content.mjs && vite",
"build": "node scripts/generate-content.mjs && vite build",
"preview": "vite preview",
"test": "node scripts/generate-content.mjs && node --test tests/*.test.mjs"
},
"dependencies": {
"dompurify": "^3.2.6",
"marked": "^16.2.1"
"content": "node scripts/generate-content.mjs",
"dev": "npm run content && vitepress dev .generated/site",
"build": "npm run content && vitepress build .generated/site --outDir dist && node scripts/verify-build.mjs",
"preview": "vitepress preview .generated/site --outDir dist",
"test": "npm run content && node --test tests/*.test.mjs"
},
"devDependencies": {
"vite": "^7.1.1"
"vitepress": "1.6.4",
"yaml": "^2.8.1"
}
}

View file

@ -1,249 +1,191 @@
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { parse as parseYaml } from "yaml";
const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const repoDir = path.resolve(siteDir, "..");
const outputDir = path.join(siteDir, ".generated", "content");
const outputDir = path.join(siteDir, ".generated", "site");
const navigationGroupOrder = [
"overview",
"start",
"integration",
"fundamentals",
"automation",
"concepts",
"workspace",
"plugins",
"benchmarks",
"development",
const externalDocuments = [
["en/integrations/claude-code.md", "integrations/claude_code/README.md"],
["en/integrations/hermes.md", "integrations/hermes_agent/README.md"],
["zh/integrations/typescript.md", "typescript/README_ZH.md"],
["en/integrations/typescript.md", "typescript/README.md"],
["zh/workspace/studio.md", "reme_studio/README_ZH.md"],
["en/workspace/studio.md", "reme_studio/README.md"],
["zh/plugins/daily-paper.md", "plugins/daily_paper/README_ZH.md"],
["en/plugins/daily-paper.md", "plugins/daily_paper/README.md"],
["zh/plugins/auto-fin.md", "plugins/auto-fin/README_ZH.md"],
["en/plugins/auto-fin.md", "plugins/auto-fin/README.md"],
["zh/plugins/lme.md", "plugins/lme/README_ZH.md"],
["en/plugins/lme.md", "plugins/lme/README.md"],
["zh/plugins/beam.md", "plugins/beam/README_ZH.md"],
["en/plugins/beam.md", "plugins/beam/README.md"],
["zh/benchmarks/beam.md", "benchmark/beam/README_ZH.md"],
["en/benchmarks/beam.md", "benchmark/beam/README.md"],
["zh/benchmarks/longmemeval.md", "benchmark/longmemeval/README_ZH.md"],
["en/benchmarks/longmemeval.md", "benchmark/longmemeval/README.md"],
["zh/benchmarks/pibench.md", "benchmark/pibench/README_ZH.md"],
["en/benchmarks/pibench.md", "benchmark/pibench/README.md"],
["zh/benchmarks/toolmemory.md", "benchmark/toolmemory/README_ZH.md"],
["en/benchmarks/toolmemory.md", "benchmark/toolmemory/README.md"],
];
const topicOrder = [
"quick_start",
"plugin_management",
"memory_as_file",
"memory_search",
"auto_memory",
"auto_resource",
"auto_link",
"auto_dream",
"proactive",
"reme_scene",
"framework",
"reme-blog",
"contributing",
];
const groups = {
quick_start: "start",
plugin_management: "start",
memory_as_file: "fundamentals",
memory_search: "fundamentals",
auto_memory: "automation",
auto_resource: "automation",
auto_link: "automation",
auto_dream: "automation",
proactive: "automation",
reme_scene: "concepts",
framework: "concepts",
"reme-blog": "concepts",
contributing: "development",
const groupNames = {
zh: {
system: "系统与诊断",
memory: "记忆演化",
retrieval: "检索与图谱",
daily: "Daily Note",
files: "文件操作",
},
en: {
system: "System and diagnostics",
memory: "Memory evolution",
retrieval: "Retrieval and graph",
daily: "Daily notes",
files: "File operations",
},
};
const localizedTitles = {
quick_start: { zh: "快速开始", en: "Quick Start" },
plugin_management: { zh: "插件管理", en: "Plugin Management" },
memory_as_file: { zh: "文件即记忆", en: "Memory as File" },
memory_search: { zh: "记忆检索", en: "Memory Search" },
auto_memory: { zh: "自动记忆", en: "Auto Memory" },
auto_resource: { zh: "自动资料整理", en: "Auto Resource" },
auto_link: { zh: "自动关联", en: "Auto Link" },
auto_dream: { zh: "自动沉淀", en: "Auto Dream" },
proactive: { zh: "主动发现", en: "Proactive" },
reme_scene: { zh: "ReMe 应用场景", en: "ReMe Application Scenarios" },
framework: { zh: "ReMe 代码框架", en: "ReMe Framework" },
"reme-blog": { zh: "ReMe 博客", en: "ReMe Blog" },
contributing: { zh: "开源与贡献", en: "Open Source and Contributing" },
const jobGroups = {
version: "system",
app_config: "system",
chat: "system",
health_check: "system",
status: "system",
help: "system",
auto_dream: "memory",
auto_memory: "memory",
auto_memory_cc: "memory",
auto_resource: "memory",
proactive: "memory",
traverse: "retrieval",
graph_snapshot: "retrieval",
reindex: "retrieval",
search: "retrieval",
node_search: "retrieval",
daily_list: "daily",
daily_reindex: "daily",
daily_write: "daily",
frontmatter_delete: "files",
frontmatter_read: "files",
frontmatter_update: "files",
stat: "files",
list: "files",
move: "files",
delete: "files",
read: "files",
load: "files",
read_image: "files",
write: "files",
save: "files",
edit: "files",
};
const productDocuments = [
{
slug: "typescript",
source: "typescript",
titles: { zh: "TypeScript Agent 集成", en: "TypeScript Agent Integrations" },
descriptions: {
zh: "配置统一 HTTP client,以及 DeepSeek Harness 和 OpenClaw 原生适配器。",
en: "Configure the shared HTTP client and native DeepSeek Harness and OpenClaw adapters.",
},
group: "integration",
},
{
slug: "studio",
source: "reme_studio",
titles: { zh: "ReMe 工作台", en: "ReMe Studio" },
descriptions: {
zh: "浏览、编辑和搜索本地记忆,并探索记忆图谱。",
en: "Browse, edit, search, and explore local memory from the web workspace.",
},
group: "workspace",
},
{
slug: "daily-paper",
source: "plugins/daily_paper",
titles: { zh: "每日论文插件", en: "Daily Paper Plugin" },
descriptions: {
zh: "发现论文、解析 PDF,并生成阅读笔记与每日简报。",
en: "Discover papers, analyze PDFs, and produce reading notes and a daily brief.",
},
group: "plugins",
},
{
slug: "auto-fin",
source: "plugins/auto-fin",
titles: { zh: "Auto Fin 插件", en: "Auto Fin Plugin" },
descriptions: {
zh: "结合最新财联社新闻与本地历史记忆生成研究报告。",
en: "Research recent CLS news with historical context from local memory.",
},
group: "plugins",
},
{
slug: "beam",
source: "benchmark/beam",
titles: { zh: "BEAM", en: "BEAM" },
descriptions: {
zh: "评测大规模记忆检索能力。",
en: "Evaluate memory retrieval at scale.",
},
group: "benchmarks",
},
{
slug: "longmemeval",
source: "benchmark/longmemeval",
titles: { zh: "LongMemEval", en: "LongMemEval" },
descriptions: {
zh: "评测跨会话长期记忆问答能力。",
en: "Evaluate long-term, cross-session memory question answering.",
},
group: "benchmarks",
},
{
slug: "pibench",
source: "benchmark/pibench",
titles: { zh: "π-Bench", en: "π-Bench" },
descriptions: {
zh: "评测带持久记忆的个人智能体。",
en: "Evaluate personal agents with persistent memory.",
},
group: "benchmarks",
},
{
slug: "toolmemory",
source: "benchmark/toolmemory",
titles: { zh: "Tool Memory / ExpG", en: "Tool Memory / ExpG" },
descriptions: {
zh: "通过经验驱动的自适应指导增强 Agent 工具使用。",
en: "Improve agent tool use through experience-driven adaptive guidance.",
},
group: "benchmarks",
},
];
const sharedDocuments = [
{
id: "agents-guide",
path: "AGENTS.md",
sourcePath: "AGENTS.md",
titles: {
zh: "Agent 开发指南",
en: "Agent Development Guide",
},
description: "Repository contracts, lifecycle rules, safety boundaries, and validation.",
group: "development",
language: "shared",
},
];
async function markdownTitle(filePath) {
const source = await readFile(filePath, "utf8");
return source.match(/^#\s+(.+)$/m)?.[1]?.replace(/[`*_]/g, "") || path.basename(filePath, ".md");
function typeLabel(schema = {}) {
if (schema.oneOf) return schema.oneOf.map(typeLabel).join(" or ");
if (schema.type === "array") return `${typeLabel(schema.items || {})}[]`;
return schema.type || "any";
}
async function buildManifest() {
const documents = [
{
id: "readme-zh",
path: "README_ZH.md",
sourcePath: "README_ZH.md",
title: "ReMe 项目介绍",
description: "核心理念、快速开始、使用场景与社区入口。",
group: "overview",
language: "zh",
},
{
id: "readme-en",
path: "README.md",
sourcePath: "README.md",
title: "Introducing ReMe",
description: "Core ideas, quick start, use cases, and community resources.",
group: "overview",
language: "en",
},
];
function markdownCell(value) {
if (value === undefined) return "—";
const rendered = typeof value === "string" ? value : JSON.stringify(value);
return rendered
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("|", "\\|")
.replaceAll("\n", " ");
}
for (const language of ["zh", "en"]) {
for (const topic of topicOrder) {
const sourcePath = `docs/${language}/${topic}.md`;
documents.push({
id: `${language}-${topic}`,
path: sourcePath,
sourcePath,
title: localizedTitles[topic]?.[language] || (await markdownTitle(path.join(repoDir, sourcePath))),
description: "",
group: groups[topic],
language,
});
}
function buildJobReference(config, language) {
const isZh = language === "zh";
const jobs = Object.entries(config.jobs || {}).filter(([, job]) => !["background", "cron"].includes(job.backend));
const sections = new Map();
for (const product of productDocuments) {
const filename = language === "zh" ? "README_ZH.md" : "README.md";
documents.push({
id: `${product.slug}-${language}`,
path: `${product.source}/${filename}`,
sourcePath: `${product.source}/${filename}`,
title: product.titles[language],
description: product.descriptions[language],
group: product.group,
language,
});
}
for (const [name, job] of jobs) {
const group = jobGroups[name] || "system";
if (!sections.has(group)) sections.set(group, []);
sections.get(group).push([name, job]);
}
return [...documents, ...sharedDocuments].sort(
(left, right) => navigationGroupOrder.indexOf(left.group) - navigationGroupOrder.indexOf(right.group),
);
const lines = [
"---",
`title: ${isZh ? "Job API 参考" : "Job API Reference"}`,
`description: ${isZh ? "从默认配置自动生成的可调用 Job、参数和服务边界。" : "Callable jobs, parameters, and service boundaries generated from the default configuration."}`,
"---",
"",
`# ${isZh ? "Job API 参考" : "Job API Reference"}`,
"",
isZh
? "本页从 `reme/config/default.yaml` 自动生成。它描述默认应用中的可调用 Job;插件和自定义配置可以增加、删除或覆盖 Job。运行 `reme help` 可查看当前服务的实际能力。"
: "This page is generated from `reme/config/default.yaml`. It describes callable jobs in the default application; plugins and custom configurations may add, remove, or override jobs. Run `reme help` to inspect the active service.",
"",
isZh
? "> 后台 Job 和 Cron Job 不通过服务暴露,因此不列入调用参考。"
: "> Background and cron jobs are not service-exposed and are omitted from the callable reference.",
"",
];
for (const [group, entries] of sections) {
lines.push(`## ${groupNames[language][group]}`, "");
for (const [name, job] of entries) {
const properties = job.parameters?.properties || {};
const required = new Set(job.parameters?.required || []);
lines.push(`### \`${name}\``, "", markdownCell(job.description || ""), "");
lines.push("```bash", `reme ${name}${Object.keys(properties).length ? " ..." : ""}`, "```", "");
if (!Object.keys(properties).length) {
lines.push(isZh ? "无参数。" : "No parameters.", "");
continue;
}
lines.push(
isZh
? "| 参数 | 类型 | 必填 | 默认值 | 说明 |"
: "| Parameter | Type | Required | Default | Description |",
"|---|---|---:|---|---|",
);
for (const [parameter, schema] of Object.entries(properties)) {
lines.push(
`| \`${parameter}\` | \`${markdownCell(typeLabel(schema))}\` | ${required.has(parameter) ? (isZh ? "是" : "yes") : (isZh ? "否" : "no")} | ${markdownCell(schema.default)} | ${markdownCell(schema.description || "—")} |`,
);
}
lines.push("");
}
}
return `${lines.join("\n")}\n`;
}
await rm(path.join(siteDir, ".generated"), { recursive: true, force: true });
await mkdir(outputDir, { recursive: true });
await cp(path.join(siteDir, "public", "favicon.svg"), path.join(siteDir, ".generated", "favicon.svg"));
await cp(path.join(siteDir, "public", "CNAME"), path.join(siteDir, ".generated", "CNAME"));
for (const file of ["README.md", "README_ZH.md", "AGENTS.md"]) {
await cp(path.join(repoDir, file), path.join(outputDir, file));
}
await cp(path.join(repoDir, "docs"), path.join(outputDir, "docs"), {
await cp(path.join(repoDir, "docs"), outputDir, {
recursive: true,
filter: (source) => path.basename(source) !== ".DS_Store",
filter: (source) => ![".DS_Store", "plans"].includes(path.basename(source)),
});
for (const product of productDocuments) {
await mkdir(path.join(outputDir, product.source), { recursive: true });
for (const filename of ["README.md", "README_ZH.md"]) {
await cp(path.join(repoDir, product.source, filename), path.join(outputDir, product.source, filename));
}
await cp(path.join(siteDir, "public", "CNAME"), path.join(outputDir, "public", "CNAME"));
const sourceMap = {};
for (const [destination, source] of externalDocuments) {
const destinationPath = path.join(outputDir, destination);
await mkdir(path.dirname(destinationPath), { recursive: true });
await cp(path.join(repoDir, source), destinationPath);
sourceMap[destination] = source;
}
await writeFile(
path.join(outputDir, "manifest.json"),
`${JSON.stringify({ documents: await buildManifest() }, null, 2)}\n`,
);
for (const language of ["zh", "en"]) {
await cp(
path.join(repoDir, "benchmark/toolmemory/gitcha.png"),
path.join(outputDir, language, "benchmarks", "gitcha.png"),
);
}
const defaultConfig = parseYaml(await readFile(path.join(repoDir, "reme/config/default.yaml"), "utf8"));
for (const language of ["zh", "en"]) {
const destination = path.join(outputDir, language, "reference", "jobs.md");
await mkdir(path.dirname(destination), { recursive: true });
await writeFile(destination, buildJobReference(defaultConfig, language));
sourceMap[`${language}/reference/jobs.md`] = "reme/config/default.yaml";
}
await writeFile(path.join(outputDir, ".source-map.json"), `${JSON.stringify(sourceMap, null, 2)}\n`);

View file

@ -0,0 +1,91 @@
import assert from "node:assert/strict";
import { access, readFile, readdir } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const outputDir = path.join(siteDir, "dist");
async function collectFiles(directory, prefix = "") {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
const relativePath = path.posix.join(prefix, entry.name);
if (entry.isDirectory()) files.push(...await collectFiles(path.join(directory, entry.name), relativePath));
else files.push(relativePath);
}
return files;
}
function pageUrl(relativePath) {
if (relativePath === "index.html") return "/";
if (relativePath.endsWith("/index.html")) return `/${relativePath.slice(0, -"index.html".length)}`;
return `/${relativePath.slice(0, -".html".length)}`;
}
function routeExists(pathname, files) {
const relativePath = decodeURIComponent(pathname).replace(/^\/+/, "");
if (!relativePath) return files.has("index.html");
if (relativePath.endsWith("/")) return files.has(`${relativePath}index.html`);
return files.has(relativePath) || files.has(`${relativePath}.html`) || files.has(`${relativePath}/index.html`);
}
const requiredFiles = [
"index.html",
"404.html",
"CNAME",
"favicon.svg",
"hashmap.json",
"sitemap.xml",
"llms.txt",
"llms-full.txt",
"zh/index.html",
"en/index.html",
"zh/configuration.html",
"en/configuration.html",
"zh/services.html",
"en/services.html",
"zh/reference/jobs.html",
"en/reference/jobs.html",
"zh/configuration/llms.txt",
"en/configuration/llms.txt",
];
for (const relativePath of requiredFiles) await access(path.join(outputDir, relativePath));
assert.equal((await readFile(path.join(outputDir, "CNAME"), "utf8")).trim(), "reme.agentscope.io");
const homepage = await readFile(path.join(outputDir, "index.html"), "utf8");
assert.ok(homepage.includes('href="/en/"'), "root language switch must link to /en/");
assert.ok(!homepage.includes('href="/en/ex"'), "root language switch must not produce /en/ex");
assert.ok(homepage.includes('"studio-en":"/en/workspace/studio"'), "legacy redirects must be embedded");
const sitemap = await readFile(path.join(outputDir, "sitemap.xml"), "utf8");
assert.ok(sitemap.includes("<lastmod>"), "sitemap must include canonical-source update times");
assert.ok(!sitemap.includes("<loc>https://reme.agentscope.io/</loc>"), "root redirect must not be indexed");
assert.ok(!sitemap.includes('hreflang="zh-CN" href="https://reme.agentscope.io/"'), "root must not duplicate zh-CN");
const ChineseConfiguration = await readFile(path.join(outputDir, "zh/configuration.html"), "utf8");
assert.match(ChineseConfiguration, /搜索文档/);
assert.match(ChineseConfiguration, /复制 Markdown/);
assert.match(ChineseConfiguration, /在 GitHub 查看源文件/);
const jobReference = await readFile(path.join(outputDir, "en/reference/jobs.html"), "utf8");
assert.match(jobReference, /Job API Reference/);
assert.match(jobReference, /auto_memory/);
const outputFiles = new Set(await collectFiles(outputDir));
const missingLinks = [];
for (const relativePath of [...outputFiles].filter((file) => file.endsWith(".html"))) {
const html = await readFile(path.join(outputDir, relativePath), "utf8");
const currentUrl = new URL(pageUrl(relativePath), "https://reme-docs.local");
for (const match of html.matchAll(/<a\b[^>]*\bhref="([^"]+)"/g)) {
const href = match[1].replaceAll("&amp;", "&");
if (href.startsWith("#")) continue;
const target = new URL(href, currentUrl);
if (target.origin !== currentUrl.origin) continue;
if (!routeExists(target.pathname, outputFiles)) missingLinks.push(`${relativePath}: ${href}`);
}
}
assert.deepEqual(missingLinks, [], `missing internal links:\n${missingLinks.join("\n")}`);
console.log(`Verified ${requiredFiles.length} documentation build artifacts.`);

View file

@ -1,458 +0,0 @@
import DOMPurify from "dompurify";
import { marked } from "marked";
import { stripMarkdownFrontmatter } from "./markdown.js";
import "./styles.css";
const baseUrl = import.meta.env.BASE_URL;
const repositoryUrl = "https://github.com/agentscope-ai/ReMe";
const copy = {
zh: {
docs: "文档",
home: "首页",
search: "搜索文档…",
noResults: "没有找到匹配的文档",
menu: "打开导航",
toc: "本页目录",
edit: "在 GitHub 查看源文件",
quickStart: "快速开始",
groups: {
overview: "项目介绍",
start: "开始使用",
fundamentals: "核心原理",
automation: "自动化能力",
concepts: "架构与场景",
integration: "Agent 集成",
workspace: "工作区",
plugins: "插件",
benchmarks: "评测",
development: "开发者规范",
},
},
en: {
docs: "Documentation",
home: "Home",
search: "Search documentation…",
noResults: "No matching documents",
menu: "Open navigation",
toc: "On this page",
edit: "View source on GitHub",
quickStart: "Quick start",
groups: {
overview: "Introduction",
start: "Get started",
fundamentals: "Fundamentals",
automation: "Automation",
concepts: "Architecture & scenarios",
integration: "Agent integration",
workspace: "Workspace",
plugins: "Plugins",
benchmarks: "Benchmarks",
development: "Development",
},
},
};
const state = {
language: localStorage.getItem("reme-docs-language") || "zh",
documents: [],
activeDocument: null,
query: "",
};
const homeCopy = {
zh: {
eyebrow: "LOCAL-FIRST · FILE-NATIVE",
title: "让 Agent 真正记住,\n也让记忆始终属于你。",
description: "ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的 Markdown,并提供从工作区管理到主动研究的一整套工具。",
start: "快速开始",
project: "了解 ReMe",
explore: "按目标探索",
exploreDescription: "选择你现在想完成的事情。每个入口都直接连接到对应的完整文档。",
cards: [
{ id: "studio-zh", icon: "◫", label: "管理记忆", title: "ReMe 工作台", description: "在本地 Web 工作区中浏览、编辑、搜索记忆,并探索 wikilink 图谱。", tone: "mint" },
{ id: "daily-paper-zh", icon: "◌", label: "发现与分析", title: "每日论文插件", description: "从论文榜单筛选值得阅读的工作,解析 PDF,并生成笔记与五分钟简报。", tone: "blue" },
{ id: "auto-fin-zh", icon: "↗", label: "主题研究", title: "Auto Fin 插件", description: "连接最新财联社新闻和本地历史记忆,生成可追溯的研究报告。", tone: "amber" },
],
benchmark: "验证记忆能力",
benchmarkDescription: "从检索规模、跨会话问答、个人智能体到工具经验,查看 ReMe 的四套评测与复现实验。",
benchmarkAction: "从 BEAM 开始",
},
en: {
eyebrow: "LOCAL-FIRST · FILE-NATIVE",
title: "Memory that works for agents.\nFiles that remain yours.",
description: "ReMe turns conversations and resources into readable, editable, searchable, interconnected Markdown—with tools spanning workspace management and proactive research.",
start: "Quick start",
project: "Meet ReMe",
explore: "Explore by goal",
exploreDescription: "Start with what you want to accomplish. Every entry opens the complete guide.",
cards: [
{ id: "studio-en", icon: "◫", label: "Manage memory", title: "ReMe Studio", description: "Browse, edit, and search memory in a local web workspace, then explore its wikilink graph.", tone: "mint" },
{ id: "daily-paper-en", icon: "◌", label: "Discover & analyze", title: "Daily Paper Plugin", description: "Select useful papers from rankings, analyze PDFs, and create notes plus a five-minute brief.", tone: "blue" },
{ id: "auto-fin-en", icon: "↗", label: "Research topics", title: "Auto Fin Plugin", description: "Connect recent CLS news with historical local memory to produce traceable research reports.", tone: "amber" },
],
benchmark: "Validate memory systems",
benchmarkDescription: "Explore four reproducible evaluations covering retrieval scale, cross-session QA, personal agents, and tool-use experience.",
benchmarkAction: "Start with BEAM",
},
};
const app = document.querySelector("#app");
app.innerHTML = `
<header class="topbar">
<a class="brand" href="${baseUrl}" aria-label="ReMe documentation home">
<span class="brand-mark">R</span>
<span>ReMe</span>
<span class="brand-divider"></span>
<span class="brand-section" data-copy="docs"></span>
</a>
<nav class="top-actions" aria-label="Global navigation">
<div class="language-switch" role="group" aria-label="Language">
<button type="button" data-language="zh">中</button>
<button type="button" data-language="en">EN</button>
</div>
<a class="quick-start-link" href="?doc=zh-quick_start" data-doc="zh-quick_start" data-copy="quickStart"></a>
<a class="github-link" href="${repositoryUrl}" target="_blank" rel="noreferrer">GitHub ↗</a>
<button class="menu-button" type="button" aria-expanded="false" data-action="menu"></button>
</nav>
</header>
<div class="docs-shell">
<aside class="sidebar" aria-label="Documentation navigation">
<label class="search-box">
<span aria-hidden="true">⌕</span>
<input type="search" autocomplete="off" />
<kbd>⌘K</kbd>
</label>
<nav class="document-nav"></nav>
<div class="sidebar-footer">
<span class="status-dot"></span>
Local-first · File-native
</div>
</aside>
<main class="article-wrap">
<article class="article"><div class="loading-line"></div></article>
</main>
<aside class="toc-panel"><nav class="toc"></nav></aside>
</div>
<button class="sidebar-backdrop" type="button" aria-label="Close navigation"></button>
`;
const sidebar = app.querySelector(".sidebar");
const docsShell = app.querySelector(".docs-shell");
const documentNav = app.querySelector(".document-nav");
const article = app.querySelector(".article");
const toc = app.querySelector(".toc");
const searchInput = app.querySelector("input[type='search']");
const menuButton = app.querySelector(".menu-button");
const backdrop = app.querySelector(".sidebar-backdrop");
function slugify(value) {
return value
.toLowerCase()
.trim()
.replace(/<[^>]+>/g, "")
.replace(/[^\p{Letter}\p{Number}]+/gu, "-")
.replace(/^-|-$/g, "");
}
function resolveDocumentPath(currentPath, target) {
const cleanTarget = target.split("#")[0].split("?")[0];
const currentParts = currentPath.split("/");
currentParts.pop();
for (const part of cleanTarget.split("/")) {
if (!part || part === ".") continue;
if (part === "..") currentParts.pop();
else currentParts.push(part);
}
return currentParts.join("/");
}
function configureMarkdown(document) {
const renderer = new marked.Renderer();
const headingIds = new Map();
renderer.heading = ({ tokens, depth }) => {
const text = tokens.map((token) => token.text || token.raw || "").join("");
const baseSlug = slugify(text) || "section";
const count = headingIds.get(baseSlug) || 0;
headingIds.set(baseSlug, count + 1);
const id = count ? `${baseSlug}-${count + 1}` : baseSlug;
return `<h${depth} id="${id}">${text}</h${depth}>`;
};
renderer.image = ({ href, title, text }) => {
const url = /^(https?:|data:)/.test(href)
? href
: `${baseUrl}content/${resolveDocumentPath(document.path, href)}`;
const titleAttribute = title ? ` title="${title}"` : "";
return `<img src="${url}" alt="${text}" loading="lazy"${titleAttribute}>`;
};
renderer.link = ({ href, title, tokens }) => {
const label = tokens.map((token) => token.text || token.raw || "").join("");
const titleAttribute = title ? ` title="${title}"` : "";
if (href.startsWith("#")) return `<a href="${href}"${titleAttribute}>${label}</a>`;
if (!/^(https?:|mailto:)/.test(href)) {
const resolved = resolveDocumentPath(document.path, href);
const localDocument = state.documents.find((item) => item.path === resolved);
if (localDocument) return `<a href="?doc=${localDocument.id}" data-doc="${localDocument.id}">${label}</a>`;
return `<a href="${repositoryUrl}/blob/main/${resolved}" target="_blank" rel="noreferrer">${label}</a>`;
}
return `<a href="${href}" target="_blank" rel="noreferrer"${titleAttribute}>${label}</a>`;
};
marked.use({ renderer, gfm: true, breaks: false });
}
function availableDocuments() {
return state.documents.filter(
(document) => document.language === state.language || document.language === "shared",
);
}
function documentTitle(document) {
return document.titles?.[state.language] || document.title;
}
function renderChrome() {
const labels = copy[state.language];
app.querySelector("[data-copy='docs']").textContent = labels.docs;
const quickStartLink = app.querySelector("[data-copy='quickStart']");
quickStartLink.textContent = `${labels.quickStart} →`;
quickStartLink.href = `?doc=${state.language}-quick_start`;
quickStartLink.dataset.doc = `${state.language}-quick_start`;
searchInput.placeholder = labels.search;
menuButton.textContent = labels.menu;
document.documentElement.lang = state.language === "zh" ? "zh-CN" : "en";
app.querySelectorAll("[data-language]").forEach((button) => {
button.classList.toggle("active", button.dataset.language === state.language);
});
}
function renderNavigation() {
const labels = copy[state.language];
const query = state.query.trim().toLocaleLowerCase();
const filtered = availableDocuments().filter((document) =>
`${documentTitle(document)} ${document.title || ""} ${document.description}`.toLocaleLowerCase().includes(query),
);
const groups = [...new Set(filtered.map((document) => document.group))];
if (!filtered.length) {
documentNav.innerHTML = `
<a href="${baseUrl}" data-home class="home-link ${state.activeDocument ? "" : "active"}">${labels.home}</a>
<p class="empty-state">${labels.noResults}</p>
`;
return;
}
documentNav.innerHTML = `
<a href="${baseUrl}" data-home class="home-link ${state.activeDocument ? "" : "active"}">${labels.home}</a>
` + groups
.map(
(group) => `
<section class="nav-group">
<h2>${labels.groups[group]}</h2>
${filtered
.filter((document) => document.group === group)
.map(
(document) => `
<a href="?doc=${document.id}" data-doc="${document.id}" class="${state.activeDocument?.id === document.id ? "active" : ""}">
<span>${documentTitle(document)}</span>
</a>`,
)
.join("")}
</section>`,
)
.join("");
}
function renderHome(pushHistory = true) {
const labels = homeCopy[state.language];
state.activeDocument = null;
docsShell.classList.add("home-view");
article.dataset.group = "home";
renderNavigation();
article.innerHTML = `
<section class="home-hero">
<p class="home-eyebrow">${labels.eyebrow}</p>
<h1>${labels.title.replace("\n", "<br>")}</h1>
<p class="home-lead">${labels.description}</p>
<div class="home-actions">
<a href="${baseUrl}?doc=${state.language}-quick_start" class="primary-action">${labels.start} →</a>
<a href="${baseUrl}?doc=readme-${state.language}" class="secondary-action">${labels.project}</a>
</div>
</section>
<section class="home-explore">
<p class="section-kicker">01 / PRODUCT & PLUGINS</p>
<h2>${labels.explore}</h2>
<p class="section-lead">${labels.exploreDescription}</p>
<div class="feature-grid">
${labels.cards.map((card) => `
<a href="${baseUrl}?doc=${card.id}" class="feature-card ${card.tone}">
<span class="feature-icon">${card.icon}</span>
<span class="feature-label">${card.label}</span>
<strong>${card.title}</strong>
<span class="feature-description">${card.description}</span>
<span class="feature-arrow">→</span>
</a>`).join("")}
</div>
</section>
<section class="benchmark-callout">
<div>
<p class="section-kicker">02 / BENCHMARKS</p>
<h2>${labels.benchmark}</h2>
<p>${labels.benchmarkDescription}</p>
</div>
<a href="${baseUrl}?doc=beam-${state.language}">${labels.benchmarkAction} →</a>
</section>
`;
toc.innerHTML = "";
closeMenu();
if (pushHistory) history.pushState({ home: true }, "", baseUrl);
window.scrollTo({ top: 0, behavior: "instant" });
}
function renderToc() {
const headings = [...article.querySelectorAll("h2, h3")];
if (!headings.length) {
toc.innerHTML = "";
return;
}
toc.innerHTML = `
<h2>${copy[state.language].toc}</h2>
${headings
.map(
(heading) => `<a class="toc-${heading.tagName.toLowerCase()}" href="#${heading.id}">${heading.childNodes[0]?.textContent || heading.textContent}</a>`,
)
.join("")}
`;
}
function rewriteRenderedUrls(document) {
article.querySelectorAll("img[src]").forEach((image) => {
const source = image.getAttribute("src");
if (source && !/^(https?:|data:|\/)/.test(source)) {
image.src = `${baseUrl}content/${resolveDocumentPath(document.path, source)}`;
}
});
article.querySelectorAll("a[href]").forEach((link) => {
const href = link.getAttribute("href");
if (!href || /^(https?:|mailto:|#|\/)/.test(href) || link.dataset.doc) return;
const resolved = resolveDocumentPath(document.path, href);
const localDocument = state.documents.find((item) => item.path === resolved);
if (localDocument) {
link.href = `?doc=${localDocument.id}`;
link.dataset.doc = localDocument.id;
link.removeAttribute("target");
return;
}
link.href = `${repositoryUrl}/blob/main/${resolved}`;
link.target = "_blank";
link.rel = "noreferrer";
});
}
async function openDocument(id, pushHistory = true) {
const fallbackId = state.language === "zh" ? "readme-zh" : "readme-en";
const document = state.documents.find((item) => item.id === id) || state.documents.find((item) => item.id === fallbackId);
if (document.language !== "shared" && document.language !== state.language) {
state.language = document.language;
localStorage.setItem("reme-docs-language", state.language);
renderChrome();
}
state.activeDocument = document;
docsShell.classList.remove("home-view");
article.dataset.group = document.group;
renderNavigation();
article.innerHTML = `<div class="loading-line"></div>`;
const response = await fetch(`${baseUrl}content/${document.path}`);
if (!response.ok) throw new Error(`Unable to load ${document.path}`);
configureMarkdown(document);
const markdown = stripMarkdownFrontmatter(await response.text());
const body = DOMPurify.sanitize(await marked.parse(markdown), {
ADD_ATTR: ["target"],
});
article.innerHTML = `
<div class="article-meta">
<span>${copy[state.language].groups[document.group]}</span>
<span>·</span>
<span>${document.sourcePath}</span>
</div>
<div class="markdown-body">${body}</div>
<footer class="article-footer">
<a href="${repositoryUrl}/blob/main/${document.sourcePath}" target="_blank" rel="noreferrer">${copy[state.language].edit} ↗</a>
</footer>
`;
rewriteRenderedUrls(document);
renderToc();
closeMenu();
if (pushHistory) history.pushState({ doc: document.id }, "", `?doc=${document.id}`);
window.scrollTo({ top: 0, behavior: "instant" });
}
function closeMenu() {
sidebar.classList.remove("open");
backdrop.classList.remove("visible");
menuButton.setAttribute("aria-expanded", "false");
}
function toggleMenu() {
const open = !sidebar.classList.contains("open");
sidebar.classList.toggle("open", open);
backdrop.classList.toggle("visible", open);
menuButton.setAttribute("aria-expanded", String(open));
}
app.addEventListener("click", (event) => {
const homeLink = event.target.closest("[data-home]");
if (homeLink) {
event.preventDefault();
renderHome();
return;
}
const documentLink = event.target.closest("[data-doc]");
if (documentLink) {
event.preventDefault();
openDocument(documentLink.dataset.doc);
}
if (event.target.closest("[data-action='menu']")) toggleMenu();
const languageButton = event.target.closest("[data-language]");
if (languageButton && languageButton.dataset.language !== state.language) {
state.language = languageButton.dataset.language;
localStorage.setItem("reme-docs-language", state.language);
state.query = "";
searchInput.value = "";
renderChrome();
renderHome();
}
});
searchInput.addEventListener("input", () => {
state.query = searchInput.value;
renderNavigation();
});
document.addEventListener("keydown", (event) => {
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
event.preventDefault();
searchInput.focus();
}
if (event.key === "Escape") closeMenu();
});
backdrop.addEventListener("click", closeMenu);
window.addEventListener("popstate", (event) => {
const id = event.state?.doc || new URLSearchParams(location.search).get("doc");
if (id) openDocument(id, false);
else renderHome(false);
});
const manifest = await fetch(`${baseUrl}content/manifest.json`).then((response) => response.json());
state.documents = manifest.documents;
renderChrome();
const initialDocument = new URLSearchParams(location.search).get("doc");
if (initialDocument) await openDocument(initialDocument, false);
else renderHome(false);

View file

@ -1,6 +0,0 @@
const FRONTMATTER_PATTERN = /^\uFEFF?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/;
/** Remove a leading YAML frontmatter block before rendering Markdown. */
export function stripMarkdownFrontmatter(markdown) {
return markdown.replace(FRONTMATTER_PATTERN, "");
}

View file

@ -1,200 +0,0 @@
:root {
color: #17221d;
background: #f4f7f5;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
--ink: #17221d;
--muted: #65716a;
--line: #dce5e0;
--paper: #ffffff;
--green: #087f6a;
--blue: #3156d9;
--green-soft: #e5f5ef;
--code: #f3f5f2;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 92px; }
body {
margin: 0;
min-width: 320px;
background:
radial-gradient(circle at 8% 9%, rgba(27, 193, 164, 0.06), transparent 26rem),
#f4f7f5;
}
button, input { font: inherit; }
a { color: inherit; }
.topbar {
position: fixed;
inset: 0 0 auto 0;
z-index: 30;
height: 66px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 28px;
border-bottom: 1px solid rgba(213, 225, 219, 0.85);
background: rgba(250, 252, 251, 0.88);
backdrop-filter: blur(18px) saturate(140%);
}
.brand { display: flex; align-items: center; gap: 11px; color: var(--ink); text-decoration: none; font-weight: 760; }
.brand-mark {
display: grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 11px;
color: white;
background: linear-gradient(145deg, #19c9b0, #3156d9 82%);
box-shadow: 0 7px 18px rgba(24, 123, 114, 0.2);
font-family: Georgia, serif;
font-size: 21px;
}
.brand-divider { width: 1px; height: 20px; background: var(--line); margin-left: 2px; }
.brand-section { color: var(--muted); font-weight: 520; }
.top-actions { display: flex; align-items: center; gap: 18px; }
.github-link, .quick-start-link { color: #37443d; text-decoration: none; font-size: 13px; font-weight: 650; }
.github-link:hover, .quick-start-link:hover { color: var(--green); }
.quick-start-link { padding: 8px 12px; border: 1px solid #d7e5df; border-radius: 9px; background: rgba(255, 255, 255, 0.72); }
.language-switch { display: flex; padding: 3px; border: 1px solid var(--line); border-radius: 9px; background: #f5f7f4; }
.language-switch button { padding: 5px 9px; border: 0; border-radius: 6px; color: var(--muted); background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; }
.language-switch button.active { color: var(--ink); background: white; box-shadow: 0 1px 3px rgba(20, 40, 30, 0.1); }
.menu-button { display: none; border: 1px solid var(--line); border-radius: 8px; background: white; padding: 7px 10px; cursor: pointer; }
.docs-shell { display: grid; grid-template-columns: 276px minmax(0, 1fr) 224px; max-width: 1540px; min-height: 100vh; margin: 0 auto; padding-top: 66px; }
.docs-shell.home-view { grid-template-columns: 276px minmax(0, 1fr); }
.docs-shell.home-view .toc-panel { display: none; }
.sidebar { position: sticky; top: 66px; height: calc(100vh - 66px); padding: 25px 20px 18px; overflow-y: auto; border-right: 1px solid var(--line); background: rgba(247, 250, 248, 0.78); }
.search-box { display: flex; align-items: center; gap: 8px; height: 41px; padding: 0 11px; border: 1px solid #d8e3dd; border-radius: 11px; color: #7a857e; background: rgba(255, 255, 255, 0.84); box-shadow: 0 5px 18px rgba(29, 65, 48, 0.035); }
.search-box:focus-within { border-color: #78aa8e; box-shadow: 0 0 0 3px rgba(22, 120, 76, 0.1); }
.search-box input { width: 100%; border: 0; outline: 0; color: var(--ink); background: transparent; font-size: 13px; }
.search-box kbd { padding: 2px 5px; border: 1px solid var(--line); border-radius: 4px; background: #f7f8f6; font-size: 10px; }
.document-nav { padding: 12px 0 52px; }
.home-link { position: relative; display: block; margin-top: 9px; padding: 9px 10px 9px 13px; border-radius: 8px; color: #536058; text-decoration: none; font-size: 13px; }
.home-link:hover { color: var(--ink); background: #f0f3ef; }
.home-link.active { color: #086b5a; background: linear-gradient(90deg, #dff3ec, #eaf6f2); font-weight: 700; }
.nav-group { margin-top: 21px; }
.nav-group h2 { margin: 0 10px 7px; color: #8a948e; font-size: 10px; font-weight: 800; letter-spacing: 0.11em; text-transform: uppercase; }
.nav-group a { position: relative; display: block; padding: 7px 10px 7px 13px; border-radius: 8px; color: #536058; text-decoration: none; font-size: 13px; line-height: 1.4; }
.nav-group a:hover { color: var(--ink); background: #f0f3ef; }
.nav-group a.active { color: #086b5a; background: linear-gradient(90deg, #dff3ec, #eaf6f2); font-weight: 700; }
.nav-group a.active::before { position: absolute; top: 9px; bottom: 9px; left: 0; width: 3px; border-radius: 3px; background: linear-gradient(#17b79d, #3470d8); content: ""; }
.empty-state { padding: 24px 10px; color: var(--muted); font-size: 13px; }
.sidebar-footer { position: sticky; bottom: -18px; display: flex; align-items: center; gap: 8px; margin: 0 -20px; padding: 14px 22px 18px; border-top: 1px solid var(--line); color: #7c8880; background: #f7faf8; font: 600 10px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.04em; text-transform: uppercase; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: #29a869; box-shadow: 0 0 0 3px #dff3e8; }
.article-wrap { min-width: 0; padding: 58px clamp(32px, 5.8vw, 86px) 100px; background: rgba(255, 255, 255, 0.94); }
.article { width: 100%; max-width: 820px; margin: 0 auto; }
.article[data-group="home"] { max-width: 980px; }
.home-hero { padding: 34px 0 76px; }
.home-eyebrow, .section-kicker { margin: 0 0 17px; color: #12806d; font: 750 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.13em; }
.home-hero h1 { margin: 0; color: #102019; font-size: clamp(46px, 6.3vw, 76px); line-height: 1.15; letter-spacing: -0.055em; }
.home-lead { margin: 27px 0 0; color: #526159; font-size: 18px; line-height: 1.72; }
.home-actions { display: flex; flex-wrap: wrap; gap: 11px; margin-top: 31px; }
.home-actions a { padding: 11px 17px; border-radius: 10px; text-decoration: none; font-size: 14px; font-weight: 720; }
.primary-action { color: white; background: #087f6a; box-shadow: 0 8px 22px rgba(8, 127, 106, 0.2); }
.secondary-action { border: 1px solid #d5e2dc; color: #34443c; background: white; }
.home-explore { padding-top: 58px; border-top: 1px solid var(--line); }
.home-explore h2, .benchmark-callout h2 { margin: 0; color: #15251d; font-size: 30px; letter-spacing: -0.025em; }
.section-lead { max-width: 620px; margin: 10px 0 25px; color: var(--muted); line-height: 1.65; }
.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.feature-card { position: relative; display: flex; min-height: 260px; flex-direction: column; padding: 23px; overflow: hidden; border: 1px solid #dce8e2; border-radius: 16px; color: var(--ink); background: linear-gradient(155deg, #fff, #f4faf7); text-decoration: none; transition: transform 160ms ease, box-shadow 160ms ease; }
.feature-card:hover { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(29, 68, 49, 0.1); }
.feature-card.blue { background: linear-gradient(155deg, #fff, #f1f5ff); }
.feature-card.amber { background: linear-gradient(155deg, #fff, #fbf7ec); }
.feature-icon { display: grid; place-items: center; width: 42px; height: 42px; margin-bottom: 32px; border-radius: 12px; color: #08705e; background: #dbf2ea; font-size: 22px; }
.blue .feature-icon { color: #3156b8; background: #e5ebff; }
.amber .feature-icon { color: #9b6818; background: #f8eac8; }
.feature-label { margin-bottom: 7px; color: #718078; font: 700 10px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.feature-card strong { font-size: 20px; }
.feature-description { margin-top: 10px; color: #66736c; font-size: 13px; line-height: 1.6; }
.feature-arrow { position: absolute; right: 22px; bottom: 18px; color: #6a7870; font-size: 19px; }
.benchmark-callout { display: flex; align-items: end; justify-content: space-between; gap: 35px; margin-top: 62px; padding: 36px; border-radius: 17px; color: white; background: linear-gradient(125deg, #142a22, #1c473b); }
.benchmark-callout .section-kicker { color: #74d4bb; }
.benchmark-callout h2 { color: white; }
.benchmark-callout p:not(.section-kicker) { max-width: 610px; margin: 10px 0 0; color: #c3d4cd; line-height: 1.65; }
.benchmark-callout > a { flex: none; padding: 10px 14px; border: 1px solid rgba(255,255,255,.25); border-radius: 9px; color: white; text-decoration: none; font-size: 13px; font-weight: 700; }
.article-meta { display: flex; gap: 8px; margin-bottom: 22px; color: #7d8d84; font: 600 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
.article-meta span:first-child { padding: 3px 8px; border-radius: 999px; color: #08705e; background: #e6f5ef; }
.markdown-body { color: #27332d; font-size: 16px; line-height: 1.78; }
.markdown-body > :first-child { margin-top: 0; }
.markdown-body h1 { margin: 0 0 30px; color: #112019; font-size: clamp(36px, 5vw, 54px); line-height: 1.06; letter-spacing: -0.04em; }
.markdown-body h2 { margin: 58px 0 18px; padding-top: 4px; color: #14241c; font-size: 27px; line-height: 1.25; letter-spacing: -0.02em; }
.markdown-body h3 { margin: 35px 0 12px; color: #1f3027; font-size: 20px; line-height: 1.35; }
.markdown-body p, .markdown-body ul, .markdown-body ol { margin: 14px 0; }
.markdown-body li { margin: 5px 0; }
.markdown-body a { color: var(--green); text-decoration-color: #9cc8ae; text-underline-offset: 3px; }
.markdown-body a:hover { text-decoration-color: var(--green); }
.markdown-body img { display: block; max-width: 100%; height: auto; margin: 30px auto; border-radius: 14px; }
/* README badges are linked images. Keep them in a compact row instead of applying the figure layout above. */
.article[data-group="overview"] .markdown-body > p:has(> a > img[src*="img.shields.io"]) {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 7px;
max-width: 690px;
margin: 20px auto;
}
.article[data-group="overview"] .markdown-body > p > a > img[src*="img.shields.io"] {
display: inline-block;
width: auto;
max-height: 24px;
margin: 0;
border-radius: 4px;
vertical-align: middle;
}
.markdown-body p[align="center"] { text-align: center; }
.article[data-group="overview"] .markdown-body > p:first-child img { max-width: min(470px, 72%); margin-top: 8px; filter: drop-shadow(0 18px 25px rgba(44, 85, 164, 0.1)); }
.markdown-body code { padding: 2px 5px; border-radius: 5px; background: var(--code); color: #315844; font: 0.88em/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.markdown-body pre { margin: 24px 0; padding: 19px 21px; overflow-x: auto; border: 1px solid #dce5e0; border-radius: 12px; background: linear-gradient(145deg, #f5f8f6, #f8faf9); box-shadow: inset 3px 0 0 #7bd0b6; }
.markdown-body pre code { padding: 0; color: #263b30; background: none; font-size: 13px; }
.markdown-body blockquote { margin: 24px 0; padding: 4px 20px; border-left: 3px solid #68aa85; color: #53635a; background: #f5faf7; }
.markdown-body table { display: block; width: 100%; margin: 24px 0; overflow-x: auto; border-collapse: collapse; font-size: 14px; }
.markdown-body th, .markdown-body td { padding: 10px 13px; border: 1px solid var(--line); text-align: left; }
.markdown-body th { background: #f4f6f3; }
.markdown-body hr { margin: 44px 0; border: 0; border-top: 1px solid var(--line); }
.article-footer { margin-top: 70px; padding-top: 20px; border-top: 1px solid var(--line); }
.article-footer a { color: var(--muted); text-decoration: none; font-size: 13px; }
.article-footer a:hover { color: var(--green); }
.loading-line { width: 55%; height: 12px; margin-top: 40px; border-radius: 9px; background: linear-gradient(90deg, #edf0ec, #f8faf7, #edf0ec); background-size: 200% 100%; animation: loading 1.2s infinite; }
@keyframes loading { to { background-position: -200% 0; } }
.toc-panel { position: sticky; top: 66px; height: calc(100vh - 66px); padding: 58px 24px; overflow-y: auto; border-left: 1px solid #e9efeb; background: rgba(252, 253, 252, 0.92); }
.toc h2 { margin: 0 0 12px; color: #8a948e; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; }
.toc a { display: block; padding: 5px 0; color: #7a857e; text-decoration: none; font-size: 12px; line-height: 1.45; }
.toc a:hover { color: var(--green); }
.toc .toc-h3 { padding-left: 12px; }
.sidebar-backdrop { display: none; }
@media (max-width: 1120px) {
.docs-shell { grid-template-columns: 250px minmax(0, 1fr); }
.toc-panel { display: none; }
}
@media (max-width: 760px) {
.topbar { height: 60px; padding: 0 16px; }
.brand-section, .brand-divider, .github-link { display: none; }
.quick-start-link { font-size: 12px; }
.menu-button { display: block; max-width: 112px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.top-actions { gap: 9px; }
.docs-shell { display: block; padding-top: 60px; }
.sidebar { position: fixed; z-index: 25; top: 60px; bottom: 0; left: 0; width: min(310px, 86vw); height: auto; transform: translateX(-105%); transition: transform 180ms ease; box-shadow: 16px 0 35px rgba(20, 40, 30, 0.12); }
.sidebar.open { transform: translateX(0); }
.sidebar-backdrop { position: fixed; z-index: 20; inset: 60px 0 0; width: 100%; border: 0; background: rgba(18, 30, 23, 0.35); }
.sidebar-backdrop.visible { display: block; }
.article-wrap { padding: 38px 20px 72px; }
.article-meta { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.markdown-body { font-size: 15px; }
.markdown-body h1 { font-size: 34px; }
.markdown-body h2 { margin-top: 46px; font-size: 24px; }
.article[data-group="overview"] .markdown-body > p:first-child img { max-width: 84%; }
.home-hero { padding: 20px 0 54px; }
.home-hero h1 { font-size: 43px; }
.home-lead { font-size: 16px; }
.feature-grid { grid-template-columns: 1fr; }
.feature-card { min-height: 225px; }
.benchmark-callout { align-items: flex-start; flex-direction: column; padding: 28px 24px; }
}

View file

@ -1,23 +0,0 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
const manifestUrl = new URL("../.generated/content/manifest.json", import.meta.url);
test("omits retired Agent documents and places Agent integration after getting started", async () => {
const manifest = JSON.parse(await readFile(manifestUrl, "utf8"));
const documents = manifest.documents;
const groups = [...new Set(documents.map((document) => document.group))];
assert.equal(documents.some((document) => document.id === "reme-memory-skill"), false);
assert.equal(documents.some((document) => document.sourcePath.endsWith("agent_integration_plan.md")), false);
assert.deepEqual(
documents
.filter((document) => document.group === "plugins")
.map((document) => document.title || document.titles?.en),
["每日论文插件", "Auto Fin 插件", "Daily Paper Plugin", "Auto Fin Plugin"],
);
assert.equal(documents.some((document) => document.group === "cookbooks"), false);
assert.ok(groups.indexOf("integration") > groups.indexOf("start"));
assert.ok(groups.indexOf("integration") < groups.indexOf("fundamentals"));
});

View file

@ -0,0 +1,90 @@
import assert from "node:assert/strict";
import { access, readFile } from "node:fs/promises";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
import { parse as parseYaml } from "yaml";
import { legacyRoutes } from "../../docs/.vitepress/legacy-routes.mjs";
const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const repoDir = path.resolve(siteDir, "..");
const generatedDir = path.join(siteDir, ".generated", "site");
test("generates every required bilingual guide", async () => {
const names = [
"configuration.md",
"services.md",
"operations.md",
"integrations.md",
"plugin_development.md",
"faq.md",
"reference/cli.md",
"reference/jobs.md",
];
for (const language of ["zh", "en"]) {
for (const name of names) await access(path.join(generatedDir, language, name));
}
await access(path.join(generatedDir, "zh/integrations/claude-code.md"));
await access(path.join(generatedDir, "en/integrations/claude-code.md"));
await access(path.join(generatedDir, "zh/integrations/hermes.md"));
await access(path.join(generatedDir, "en/integrations/hermes.md"));
});
test("maps mirrored pages back to their canonical repository sources", async () => {
const sourceMap = JSON.parse(await readFile(path.join(generatedDir, ".source-map.json"), "utf8"));
assert.equal(sourceMap["zh/integrations/typescript.md"], "typescript/README_ZH.md");
assert.equal(sourceMap["en/integrations/claude-code.md"], "integrations/claude_code/README.md");
assert.equal(sourceMap["en/integrations/hermes.md"], "integrations/hermes_agent/README.md");
assert.equal(sourceMap["en/workspace/studio.md"], "reme_studio/README.md");
assert.equal(sourceMap["zh/plugins/lme.md"], "plugins/lme/README_ZH.md");
assert.equal(sourceMap["en/reference/jobs.md"], "reme/config/default.yaml");
});
test("generates the callable Job reference from default.yaml", async () => {
const config = parseYaml(await readFile(path.join(repoDir, "reme/config/default.yaml"), "utf8"));
const callableJobs = Object.entries(config.jobs)
.filter(([, job]) => !["background", "cron"].includes(job.backend))
.map(([name]) => name);
for (const language of ["zh", "en"]) {
const reference = await readFile(path.join(generatedDir, language, "reference", "jobs.md"), "utf8");
for (const job of callableJobs) assert.ok(reference.includes(`### ${"`"}${job}${"`"}`), job);
}
});
test("keeps generated content disposable and excludes internal plans", async () => {
await assert.rejects(access(path.join(generatedDir, "plans")));
await access(path.join(generatedDir, ".vitepress", "config.mts"));
await access(path.join(generatedDir, "public", "favicon.svg"));
assert.equal(
(await readFile(path.join(generatedDir, "public", "CNAME"), "utf8")).trim(),
"reme.agentscope.io",
);
});
test("maps every legacy query-string document ID to a generated page", async () => {
assert.equal(Object.keys(legacyRoutes).length, 45);
assert.equal(legacyRoutes["studio-en"], "/en/workspace/studio");
assert.equal(legacyRoutes["en-quick_start"], "/en/quick_start");
assert.equal(legacyRoutes["agents-guide"], "https://github.com/agentscope-ai/ReMe/blob/main/AGENTS.md");
for (const [id, route] of Object.entries(legacyRoutes)) {
if (route.startsWith("https://")) continue;
assert.match(route, /^\/(?:zh|en)\//, id);
const relative = route.endsWith("/") ? `${route.slice(1)}index.md` : `${route.slice(1)}.md`;
await access(path.join(generatedDir, relative));
}
});
test("tracks every generated input in documentation CI and deployment", async () => {
const requiredPaths = [
"reme/config/default.yaml",
"integrations/claude_code/README.md",
"integrations/hermes_agent/README.md",
"benchmark/toolmemory/gitcha.png",
];
for (const workflow of ["ci-docs.yml", "deploy-docs.yml"]) {
const source = await readFile(path.join(repoDir, ".github/workflows", workflow), "utf8");
for (const requiredPath of requiredPaths) assert.ok(source.includes(requiredPath), `${workflow}: ${requiredPath}`);
}
});

View file

@ -1,15 +0,0 @@
import assert from "node:assert/strict";
import test from "node:test";
import { stripMarkdownFrontmatter } from "../src/markdown.js";
test("strips leading YAML frontmatter before rendering", () => {
assert.equal(
stripMarkdownFrontmatter("---\nname: reme_memory\ndescription: Memory skill\n---\n\n# ReMe Memory\n"),
"\n# ReMe Memory\n",
);
});
test("preserves Markdown without frontmatter", () => {
const markdown = "# ReMe Memory\n\nContent\n";
assert.equal(stripMarkdownFrontmatter(markdown), markdown);
});

View file

@ -1,10 +0,0 @@
import { defineConfig } from "vite";
export default defineConfig({
base: "./",
publicDir: ".generated",
build: {
outDir: "dist",
emptyOutDir: true,
},
});