diff --git a/.dev.vars.example b/.dev.vars.example index a31df632..395d746c 100644 --- a/.dev.vars.example +++ b/.dev.vars.example @@ -10,3 +10,12 @@ FLAREMO_TRUSTED_ORIGINS= BETTER_AUTH_SECRET= FLAREMO_BOOTSTRAP_SECRET= FLAREMO_RECOVERY_SECRET= + +# 可选:Cloudflare 资源用量面板(设置→用量)。生产用 `pnpm setup:usage` +# 写入 Worker secret;本地 dev 想看这块时手动填入(token 只需要 +# Account Analytics: Read 权限)。留空则面板隐藏该区块。 +# FLAREMO_CF_ANALYTICS_TOKEN= +# FLAREMO_CF_ACCOUNT_ID=<32-hex-account-id> +# FLAREMO_CF_WORKER_NAME=flaremo +# FLAREMO_CF_D1_ID= +# FLAREMO_CF_R2_BUCKET=flaremo-attachments diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 59fa4779..8424c614 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,16 +1,16 @@ name: Deploy to Cloudflare Workers on: - push: - branches: - - main - paths-ignore: - - ".github/**" - - "README.md" - - "README.en.md" - - "CHANGELOG.md" - - "docs/**" workflow_dispatch: + inputs: + dry_run: + description: "Validate build, resources and bundle without production writes" + type: boolean + default: true + backup_confirmed: + description: "For production enter BACKUP_CONFIRMED after securing a D1 backup" + type: string + default: "" permissions: contents: read @@ -43,10 +43,39 @@ jobs: - name: Build web assets run: pnpm --filter @flaremo/web build - - name: Deploy to Cloudflare Workers - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: f6ff30bba40fbc7d791d83ab3e70daf9 - workingDirectory: "." - command: deploy --config ./wrangler.jsonc + - name: Check release authorization + env: + DRY_RUN: ${{ inputs.dry_run }} + BACKUP_CONFIRMED: ${{ inputs.backup_confirmed }} + run: | + if [ "$DRY_RUN" != "true" ] && [ "$BACKUP_CONFIRMED" != "BACKUP_CONFIRMED" ]; then + echo "Production publication requires a verified D1 backup." + exit 1 + fi + + - name: Check existing Cloudflare resources + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: f6ff30bba40fbc7d791d83ab3e70daf9 + run: pnpm provision:remote --check + + - name: Validate Worker bundle + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: f6ff30bba40fbc7d791d83ab3e70daf9 + run: pnpm exec wrangler deploy --config ./wrangler.jsonc --dry-run + + - name: Apply D1 migrations and deploy + if: ${{ !inputs.dry_run }} + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: f6ff30bba40fbc7d791d83ab3e70daf9 + run: | + pnpm migrate:remote + pnpm exec wrangler deploy --config ./wrangler.jsonc + + - name: Smoke check public origin + if: ${{ !inputs.dry_run }} + run: pnpm smoke:check + env: + FLAREMO_PUBLIC_URL: https://flaremo.48kr.top diff --git a/AGENTS.md b/AGENTS.md index c311279c..3111e17b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,25 +23,42 @@ pnpm install pnpm check pnpm test pnpm build -pnpm verify pnpm backup:drill ``` +`pnpm verify`(13 步全量门禁)只在维护者明确要求时运行,见下方「敏捷开发节奏」。 + 本地开发: ```bash pnpm migrate:local -pnpm dev +pnpm dev:hot # 推荐:前端 HMR + Worker 热重载 +pnpm dev # 无热重载:先构建前端,再由 Worker 托管该产物 ``` +`pnpm dev:hot` 同时起 Vite(前端,默认 **5573**)和 `wrangler dev`(Worker,默认 8787),浏览器只开 **5573**: + +- 改前端源码 → Vite 即时热更新(React Fast Refresh,页面状态不丢) +- 改 worker / packages 源码 → wrangler 自动重新打包,刷新即见 +- API、SSR 分享页 `/share/:token`、文章页 `/article/:slug`、R2 附件 `/file/*`、`/mcp`、feed 与 sitemap 由 Vite 代理到 Worker,因此同源,cookie 与 origin 校验照常工作 + +端口用 `FLAREMO_DEV_WEB_PORT` / `FLAREMO_DEV_WORKER_PORT` 覆盖(第二个 checkout 或端口转发时用)。首次运行若 `apps/web/dist` 不存在会先构建一次(`wrangler dev` 要求 assets 目录存在);该分支只跑 `vite build`,不含 `tsc`,不会因类型错误挡住 dev 启动。 + +改 `wrangler.jsonc` 的 `assets.run_worker_first` 时必须同步 `apps/web/vite.config.ts` 的 `server.proxy`——两者不一致会让某条路径在 dev 下走错的服务器(`apps/web/src/dev-proxy-parity.test.ts` 会失败提醒)。 + +dev:hot 下额外启用 **React Grab**(`apps/web/src/dev/react-grab.ts`):悬停任意界面元素即可复制其组件名、源码行列与 CSS 选择器,粘贴给 agent 比用文字描述位置准确得多。引入点在 `main.tsx` 的 `import.meta.env.DEV` 分支,生产构建实测不含其任何代码;`pnpm dev`(Worker 托管构建产物)与部署产物同样没有它。SSR 分享页/文章页是 Worker 渲染的独立文档,不在其作用范围。初始化显式传 `telemetry: false`,不向 react-grab.com 发版本检查。 + +已知小限制:组件栈里的文件名只有基名(`input.tsx` 而非完整路径)——Vite dev 的 sourcemap `sources` 本身就只发布基名,拾取器只是如实转述。靠组件名 + 行列号 + CSS 选择器足以定位。 + 生产部署: ```bash -pnpm verify pnpm deploy:dry-run pnpm deploy ``` +`pnpm verify` 是 13 步全量门禁(含 e2e),**只在维护者明确要求时执行**(发版也不需要)。 + 只验证 Cloudflare 配置和打包: ```bash @@ -50,29 +67,29 @@ pnpm deploy:dry-run ## 修改规则 -- 改数据库结构时,先改 `packages/db/src/schema.ts`,再运行 `pnpm db:generate`。 +- 改数据库结构时,先改 `packages/db/src/schema/` 下的对应模块(`schema.ts` 只是 barrel),再运行 `pnpm db:generate`。 - 生成的 SQL migration 必须提交到 `migrations/`。 - 自动部署会先迁移再发布 Worker;migration 必须向后兼容上一正式版本,破坏性收缩要拆到后续 release。 - 业务访问数据必须通过 Drizzle 和 domain services,不要在路由里堆散装 SQL。 - `/api/v1/*` 是兼容层;新增字段或行为时同步检查 `packages/memos` 和 OpenAPI。 - `/api/app/*` 可以服务前端体验,但必须复用同一套 domain services。 - 前端只展示已经接上后端能力的入口;不要放未实现功能的按钮、菜单或文案。 +- 产品与界面改动遵循设计原则「简约不简单,克制不放肆」;视觉、动效和文案细节见 `docs/design-system.md`。 - 应用层认证边界是 Better Auth;不要新增绕开 Better Auth 的登录页、共享密码或第二套应用令牌。Cloudflare Access 可以作为可选外层防线,但不能被误当成应用用户身份映射。 - 凭据相关的 Origin 契约必须保持不变:cookie session 的状态变更请求(包括 `POST`、`PATCH`、`DELETE` 等非安全方法)必须携带并精确匹配 `FLAREMO_PUBLIC_URL` 或 `FLAREMO_TRUSTED_ORIGINS`;PAT 请求可以省略 Origin,但一旦携带也必须精确匹配同一 allowlist,否则返回 `403`。不要用 wildcard、`Referer` 或 Cloudflare Access headers 替代 Origin 校验。 - 不得把 `BETTER_AUTH_SECRET`、`FLAREMO_BOOTSTRAP_SECRET`、初始密码、cookie 或 `memos_pat_` 明文写进代码、文档、migration、issue、PR、日志或聊天;生产 secret 只能通过 Wrangler secret 或 Cloudflare 控制台安全配置。 - `Temp/` 是参考仓库目录,不能提交。 -- 使用 GitHub Actions 时只允许两类 workflow:`.github/workflows/ci.yml`(瘦 CI:format / lint / typecheck / 单元测试,兕底与外部 PR 门禁)和 `.github/workflows/flaremo-update.yml`(只服务自部署用户自己的部署仓库,同步上游 Release 并创建升级 PR)。永远不用 GitHub Actions 做生产部署器(no CI/CD),不跑 E2E 进 CI;完整门禁 `pnpm verify` 仍由维护者发版前本地执行。不重新启用 Dependabot 或 Workers Builds。 -- 改路由、守卫、导航行为时必须同步补 e2e 用例(2026-09-10 事故教训:v0.15.3 守卫重构后门禁拦不住匿名首页无限加载,因为 e2e 只覆盖了深度链接没覆盖 `/`;门禁的有效性 = 测试覆盖率)。 +- 使用 GitHub Actions 时只允许两类 workflow:`.github/workflows/ci.yml`(瘦 CI:format / lint / typecheck / 单元测试,兕底与外部 PR 门禁)和 `.github/workflows/flaremo-update.yml`(只服务自部署用户自己的部署仓库,同步上游 Release 并创建升级 PR)。永远不用 GitHub Actions 做生产部署器(no CI/CD),不跑 E2E 进 CI;全量门禁 `pnpm verify` 只在维护者明确要求时于本地执行(发版也不需要)。不重新启用 Dependabot 或 Workers Builds。 +- 改路由、守卫、导航行为时必须同步补 e2e 用例(2026-09-10 事故教训:v0.15.3 守卫重构后门禁拦不住匿名首页无限加载,因为 e2e 只覆盖了深度链接没覆盖 `/`;门禁的有效性 = 测试覆盖率)。**补用例 ≠ 主动跑**:写进仓库即可,跑不跑由维护者决定。 +- 敏捷开发节奏(2026-09-16 维护者定调):小步快跑,验证只跑与改动直接相关的定向用例(单个 Vitest 文件 / e2e spec)。**全量门禁 `pnpm verify` 只在维护者明确要求时执行——发版、部署、日常提交都不跑**。质量底线不变:改动涉及的测试必须绿、`pnpm format` 通过。 ## Issue 和 PR 流程 -`main` 永远代表可发布状态。不要直接在 `main` 上做功能或文档任务;除非是维护者明确要求的紧急修正,否则都按 issue 分支和 PR 流程走。 +`main` 永远代表可发布状态。 -标准流程: +**维护者当面指派的任务直接在 `main` 上提交并 push(2026-09-17 维护者定调)。** Kim 的日常节奏是"在 main 上快速迭代"——给他做的小步功能/修复,做完 `git switch main && git pull` 后直接 commit + push,**不要开分支、不要开 PR**(2026-09-17 我给一个已完成的设置弹窗改动擅自开 PR,被当即否决:"我要在 main 上面快速迭代")。 -```text -issue -> branch -> commit -> push -> PR -> squash merge -> delete branch -> update local main -``` +issue -> branch -> PR 流程只用于:对外贡献者、维护者明确要求走分支/审查的大型改动、以及需要 CI 门禁的外部 PR 门禁场景。Agent 拿不准时按直接进 main 处理,拿得准要开 PR 的理由时先问一句。 操作规则: @@ -83,37 +100,32 @@ issue -> branch -> commit -> push -> PR -> squash merge -> delete branch -> upda - 合并使用 squash merge;合并后删除远端任务分支。 - 合并后本地执行 `git switch main && git pull --ff-only`,确认 `main` 已包含合并提交。 -验证强度按改动类型选择: +验证强度按改动类型选择(默认只跑定向测试,不跑全量 `pnpm verify`): - 纯文档、拼写、链接:`pnpm format:check`。 - 部署、Wrangler、D1、R2、Access 相关文档或配置:`pnpm format:check` 和 `pnpm deploy:dry-run`。 -- API、domain service、Memos 兼容、测试夹具:`pnpm verify`。 -- UI 改动:`pnpm verify`,再用 `pnpm dev` 检查桌面和移动端。 -- 真实 Cloudflare 资源演练:`pnpm verify`、`pnpm deploy:dry-run`,再执行对应的远端 D1/R2/Access 验证。 - -## 验收口径 +- API、domain service、Memos 兼容、测试夹具:改动文件对应的 vitest 用例(如 `pnpm exec vitest run apps/worker/src/api/memo-crud.test.ts -t "<用例名>"`)。 +- UI 改动:秒级静态检查(`tsc --noEmit` + 改动文件的 vitest)+ 开发过程中目检。 +- 全量 `pnpm verify`:**只在维护者明确要求时执行**,其他任何时候(含发版)都不要跑。 -改动完成前至少跑: +**E2E 一律 opt-in(2026-09-16 维护者定调)**:Agent 任何时候都不要主动跑 Playwright e2e——开发中途不跑、收尾也不跑,即使是很小的功能也如此。只有维护者明确说"跑 e2e"时才跑。日常 UI 改动用秒级手段验收:`tsc --noEmit`、定向 vitest、`pnpm build`(几百毫秒)、必要时 `pnpm dev` 起服务目检。e2e 冷启动要分钟级(wrangler dev + 全量构建),是本次主题色开发实测出的最大时间黑洞;新写的 e2e 用例留在仓库里,等维护者要求跑或下次触碰同一 spec 时一起跑。 -```bash -pnpm verify -``` - -涉及部署、Wrangler、D1、R2 或 Access 的改动,还要跑: +## 验收口径 -```bash -pnpm deploy:dry-run -``` +敏捷开发期(2026-09-16 起)默认验收线: -涉及 UI 的改动,要启动本地服务检查桌面和移动端: +- 改动涉及的测试必须绿:只跑该改动相关的 vitest 文件(e2e 见上:opt-in,不主动跑)。 +- 提交前跑一次 `pnpm format`(很快)。 +- 部署不需要先跑全量门禁:直接部署(`deploy-kosx.mjs` 默认已跳过 verify),迁移、构建和线上冒烟仍由部署脚本执行。 +- 全量 `pnpm verify` 只在维护者明确要求时执行;发版脚本(`pnpm release`)默认不跑,`--verify` 可按需开启。 -```bash -pnpm dev -``` +涉及部署、Wrangler、D1、R2 或 Access 的改动,建议跑 `pnpm deploy:dry-run`。 +涉及 UI 的改动,用 `pnpm dev:hot` 目检桌面和移动端(改样式即时生效,省掉每次重建);e2e 不主动跑(见上)。 ## 文档入口 - `README.md`:项目入口和部署入口。 +- **README 语言版本规则(2026-09-22 整理后定案)**:`README.md` 是唯一基准(英文),`README.zh-CN` / `.ja` / `.ko` / `.fr` / `.es` / `.ru` / `.ar` 七个翻译版是它的结构镜像(逐节 1:1,只差译文)。改主 README 时必须同步更新对应翻译版;只动某一节也要把该节同步到全部语言版。各版本专有约定:zh-CN 的文档链接指向中文文档根 `docs/`,其他语言版指向 `docs/en/`(`plugin-platform-standard.md` 无英文专版,保持根路径);语言切换器里的链接名保持各语言原生写法,不算混排。 - `docs/tech-stack.md`:确定的技术栈。 - `docs/architecture-notes.md`:架构和兼容边界。 - `docs/deploy.md`:人类部署指南。 diff --git a/CHANGELOG.md b/CHANGELOG.md index 619da577..3a8501fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,146 @@ FlareMo 使用 SemVer。每个 release 都要写清楚升级影响、Cloudflare 资源变化和 Memos 兼容面变化。 +## v0.22.0 + +团队项目工作台版本:新增团队项目视图(外部贡献合并),并完成一轮安全审计、全量 E2E 修复与工程卫生清理。**无 migration、无新增 Cloudflare 资源、无 API 破坏性变化。** + +### 新增与改进 + +- **团队项目工作台**(`/team-projects`,外部贡献 PR #148):集中查看项目、负责人、状态、当前进展、下一步与跟进日期,并可进入详情维护概况、记录进展/会议结论、上传与查看资料。项目仍是团队 memo,通过正文里的 ```kosx-pm 元数据块(协议 `kosx.pm/1`)识别;只有含有效元数据块的记录进入项目列表,普通笔记不受影响。团队/我的项目筛选只改变展示、不构成权限隔离;个人任务看板 `/projects` 原样保留。 +- 团队项目完整文案提供中文与英文两套;其余语言使用英文文案。 + +### 安全审计 + +对本次发布范围(含新合并的外部贡献)做了完整审计,结论与处置: + +- **依赖**:`pnpm audit` 无已知漏洞;供应链策略(`minimumReleaseAge`、构建脚本白名单、overrides 钉版本)校验通过。 +- **新贡献的代码**:无 DOM XSS sink(无 `dangerouslySetInnerHTML`/`innerHTML`/`eval`/`new Function`);元数据块解析全程 fail-closed(非法 JSON、重复块、未闭合块均降级为错误提示,不误解析);所有跳转均为内部路径且经 `validListUrl` 校验,无开放重定向;引用的外链经 `http/https` 协议白名单校验;不存在可触达的原型污染(仅保留 `__proto__`/`constructor` 作为普通键,无递归合并);**未新增任何后端端点、表、migration 或权限模型**,读写全部经由既有的、由服务端重新鉴权的 API。 +- **凭据卫生**:本次范围内无密钥泄漏,`.dev.vars` 与 `wrangler.jsonc` 均未被 git 跟踪。 +- **已修**:英文文案 23 处以 `":s"` 结尾的错字(所有英文标签与错误提示此前显示为 `Owner:s`、`Save failed:s`),成因是目录构造处的 `replace(/\\s/g, " ")` 在模板字符串里匹配的是字面反斜杠而非空白符。 + +**需要维护者决策的既有行为(非本次引入,未改动)**:`canEditMemo` 是「仅作者」,不含角色谓词,因此一名曾被设为成员、发表过团队笔记、之后被降级为读者的账号,仍可编辑自己既有的团队笔记内容(读者被明确拒绝的是**新建/重新发布**,即 `canPublishTeamMemo`)。这是作者本位权限模型的固有性质,改动会波及 Web/API/MCP/兼容面四个入口,故留待明确决策,不在本版静默变更。 + +### 修复 + +- 团队项目路由的 `validateSearch` 不再把 `edit=false` 写进 URL(此前每次访问都会被序列化,导致鉴权守卫捕获的跳转目标与实际落地地址不一致)。 +- 团队项目英文文案的 `":s"` 错字(见上)。 +- 删除时间轴组件里为旧 E2E 契约保留的隐藏 `monthLabels` 容器及其已失效的 prop 链路——纯死代码,唯一消费者是一个已失效的断言。 + +### 工程卫生 + +- 清理 24 条既有 biome 告警(未使用导入/变量、可选链、非空断言),全为语义等价的机械修正;仓库告警从 44 降至 20,剩余的均为移植 CSS 的 `noDescendingSpecificity`(样式组织建议、非错误,已按视觉验收,重排有回归风险故保留)。 +- **全量 E2E 达到 75/75 全绿**。此前长期有 1–2 个用例在整轮里必挂、单独跑却通过,逐个查明都是共享浏览器 profile 的持久状态串味,而非产品缺陷: + - workspace 布局统一改版后,多份 spec 仍指向旧 UI(内联搜索已迁至 ⌘K spotlight 弹窗、设置面板按断点挂载两份、语音设置改为「状态行 + 配置弹窗」、记忆页改为内联快速输入器、`/capture` 并入统一工作区)。 + - 新增 `startWithCleanClientState` 测试助手,统一清理 composer 发送目标偏好(`localStorage`)、标签折叠、PWA 提示,并清空 IndexedDB 的草稿与提交队列。发送目标是 per-space 记忆的:前一个用例把个人空间记成 private 后,团队空间里本该 protected 的新记录会被正确地填成个人,于是「它出现在团队时间线」的断言失败——产品行为正确,是测试互相污染。 + - 清理 IndexedDB 一律「清空 object store」而非 `deleteDatabase`:删除在应用持有连接时会被阻塞并落到用例中途,把刚写好的草稿一起删掉;且对不存在的库调用 `open` 会创建空库,使应用自身的 `open` 跳过 `onupgradeneeded`。 + - 另修掉两处测试自身缺陷:共享页 `preload="none"` 音频的断言与模板设计相悖(访客按下播放前浏览器不取媒体,duration 恒为空)、记忆编辑用例未限定卡片的 `.first()` 会打开别人的菜单。 + +### Memos 兼容面变化 + +- 无变化。 + +### 数据库 migration + +- 无。 + +### Cloudflare 资源变化 + +- 无新增或改名。部署前无需手动操作。 + +### Better Auth 应用认证变化 + +- 无。cookie session、bootstrap、注册开关、PAT 前缀与撤销行为、`FLAREMO_PUBLIC_URL` / `FLAREMO_TRUSTED_ORIGINS` 与各 secret 的配置要求均不变。 + +### 升级步骤 + +1. 直接部署即可(无 migration、无资源变化)。 +2. 部署后 `/team-projects` 立即可用;既有的 `/projects` 个人任务看板不受影响。 + +### 已知问题 + +- 团队项目工作台目前只有中英文文案,其余语言回落到英文。 +- 项目记录的原始协议元数据在普通 memo 视图中可见(记录本身仍是团队 memo)。 +- 工作台使用内置的精简 fetch 层而非共享 API client,因此不会在 401 时触发全局「需重新登录」事件,而是就地显示未登录状态;服务端仍会对每个请求独立鉴权。 +- 上述「降级为读者后仍可编辑自己既有团队笔记」的权限边界待决策。 +- 兼容面仍未覆盖完整 CEL、完整上游 webhook 事件/egress 语义、完整多用户 ACL、原生 JWT parity,第三方客户端 smoke test 仍未完成。 + +## v0.21.0 + +记忆账本与启动性能版本:Harness 适配落地(ZCode / Codex / Antigravity 插件 + 本地核心 + 原生记忆搬家)、记忆账本 v2 领域层与审核减负、Horizon 天文表盘、Workspace 统一布局,以及一轮幅度很大的 Worker 启动图瘦身。**这是自 v0.20.1 以来的 81 个提交的合并发布,含 4 个新 migration(纯新增 + 回填,向后兼容)。** + +### 新增与改进 + +- **记忆账本 v2**:事实键版本断代、证据链、双时态与混合召回;接入层与 CLI(裁决/复原/版本链端点、`memory_compile` 工具、退出码契约);v2.4 审核减负(AI 提炼默认直接生效、一眼扫替代逐条裁决、seed 直生效);键槽口径对齐唯一索引,提案不占位、不被误退位。 +- **Harness 适配(P0 替换式)**:ZCode / Codex / Antigravity 插件、本地核心与原生记忆搬家,另含 Pi Agent 进程内扩展;导入遇瞬时不可达会退避重试。 +- **Web**:独立子页收敛为持久 Workspace 布局;记忆项目面板、FilterPill 与设计系统对齐;Horizon 天文表盘(12 小时表盘、月/周/年视图深度对齐、卫星光子)。 +- **导出走 Queue**:绑定 `flaremo-data-export` 后 `POST /api/v1/export/tasks` 投递 `{taskId}` 并立即返回 queued,由 queue 消费端与 scheduled maintenance 共用的同一幂等 executor 执行导出;未绑定 Queue 的部署维持请求内执行不变。cron 兜底(stale 任务过期标记 + 过期产物清理)语义不变。 + +### 性能 + +- **Worker 启动图**:静态可达模块 1229 → 488、源码 5.87 → 2.54 MiB(约 -57%)。移出启动图的有 Better Auth 全簇、kysely、@noble、jose、@bufbuild(Connect protobuf)、cel-js、transliteration、fflate、shiki,以及 articles / SSR 公开页 / MCP / 采集四块路由树的入口态惰性化。可用 `pnpm startup-graph` 持续度量。 +- **D1 读放大**:新增按「作者 + UTC 小时 + 当前 status」分桶的聚合表 `memo_hourly_counts`,`stats` 的 `counts` / `active_days` / `activity` 三项与写入路径的 memo 配额检查不再扫 `memos` 全表;成本从「随 memo 总数增长」变为「随实际有活动的 UTC 小时数增长」。`tags` 字段与带 space 分区的查询仍是实时扫表(见 `docs/deploy.md` 的覆盖范围表)。 +- **其他**:记忆分页与批量水合、认证单查、import 与 webhook 串行往返收拢为 `inArray` + `db.batch`、前端首屏减重(ShareImageDialog / 插件 payload 懒加载 + i18n 按需语言包)。 + +### 修复 + +- **重复邮箱身份修复(`/api/app/*`)**:添加成员、修改邮箱使用一个已被占用的地址时,此前会落到 `users` / `auth_users` 唯一索引的裸驱动错误上,返回 500 `Internal server error`——真实原因只在日志里。现在由 domain 层统一判定为 409 `That email is already in use.`,前端映射为本地化文案;邮箱在写入 domain 表时归一化为小写并做大小写不敏感占用比较,堵掉「大小写变体绕过字节唯一索引、进而留下一条无团队归属的孤儿成员记录」的路径。`jsonError` 同时收口:保留框架异常(Better Auth 等)自带的 4xx 与其可读文案,不再一律压成 500(仅 4xx 生效,5xx 与无状态错误仍返回泛化文案,避免内部细节外泄)。**状态码变更**:修改邮箱撞已占用地址从 400 改为 409(`/api/app/*` 自有面,仅前端消费)。 +- **聚合表一致性**:评论创建与成员移除此前会写坏 `memo_hourly_counts`;聚合表重算会撞穿 D1 的 100 绑定参数上限,导致夜间校准从未真正生效——两者均已修复。 +- **Memos 兼容面错误处理收口**:`/api/v1` current REST 与 social 树的 `{code, message, details}` 错误信封合并为单份实现;`CompatValidationError` 与非法 JSON body 现在正确映射 400(此前会落 500)。 +- **评论创建的 memo 配额检查此前从未生效**;编辑 memo 时从新内容重新提取标签;年视图按导航年份锚定活动窗口、热力图年视图请求 366 天窗口;跳年笔记的绝对时间补上年份。 +- **Web**:服务端拒绝退出时不再假装已退出;403 不再误报「登录状态已失效」,Origin 不受信时给出明确提示;侧栏跳时间线补全 search 参数;窗口聚焦时的全量重拉导致的列表重放动画已停止(全局 staleTime 30s + 关闭 refetchOnWindowFocus)。 +- **分享卡**:长文本自适应高度、段落间距收紧、容器不再溢出裁切;视频内联播放的 403 已修(video.twimg.com 按 Referer 白名单防盗链)。 +- **备份/恢复**:`memo_hourly_counts` 此前未登记持久化清单,导致恢复演练既不覆盖也不校验它——现已归类为可重建表,并在恢复时从 `memos` 直接重算(不重放陈旧计数)。恢复清单另补记忆账本三张新表。恢复脚本的 R2 bucket 校验改为从 `wrangler.jsonc` 读取绑定名(此前硬编码 `flaremo-attachments`,任何自定义 bucket 名的部署都会卡在发布门禁上)。 + +### Memos 兼容面变化 + +- current REST 与 social 树的错误信封合并为单份实现;非法 JSON body 与 `CompatValidationError` 由 500 归位 400。 +- Connect 与 current 面的注册路径同样受重复邮箱修复影响(域层统一判定)。 +- 兼容子集范围未扩大:仍不主张完整 Memos Server parity。 + +### 数据库 migration + +- `0030_memory_ledger_v2`:新增 `memory_evidence`、`memory_events`、`memory_rejections` 三表 + `memory_items` 的 `(user_id, scope_type, scope_key, fact_key)` 索引。 +- `0031_memory_ledger_followup`:新增 `memory_compile_archives`;删除 `memory_items_user_fingerprint_idx`(已被 0030 的复合索引取代)。 +- `0032_omniscient_energizer`:新增两个部分索引(推理待审扫描、向量回收扫描)。 +- `0033_large_multiple_man`:新增 `memo_hourly_counts` 聚合并从 `memos` 回填(用 `strftime` 打 ISO 时间戳,避免被下一次校准误判为陈旧墓碑而删除)。 +- 全部为**纯新增 + 回填**,向后兼容上一正式版本的 Worker;自动部署会先迁移再发布。 + +### Cloudflare 资源变化 + +- 可选新增 Queue:`flaremo-data-export`(binding `DATA_EXPORT_QUEUE`)。不绑定则该能力维持旧的请求内执行,无需强制创建。 +- `wrangler.jsonc.example` 的 `assets.run_worker_first` 补齐 worker 渲染路径(`/article/*`、`/share/*`、`/feed.xml`、`/sitemap.xml`、`/sitemap-articles.xml`、`/favicon.ico`)。**自托管升级需同步自己的 `wrangler.jsonc`**,否则这些路径会落回 SPA fallback。 +- 无 R2 / D1 / Vectorize 资源的新增或改名。 + +### Better Auth 应用认证变化 + +- 无 cookie session、bootstrap、注册开关、PAT 前缀或撤销行为的变化。 +- 新增的重复邮箱判定改变了「修改邮箱撞已占用地址」的状态码(400 → 409)与「添加成员撞已占用邮箱」的响应(500 → 409),均为 `/api/app/*` 自有面。 +- `FLAREMO_PUBLIC_URL`、`FLAREMO_TRUSTED_ORIGINS` 与各 secret 的配置要求不变。 + +### 升级步骤 + +1. 备份 D1 与 R2(认证表在恢复清单内)。 +2. 同步自己的 `wrangler.jsonc`:补 `run_worker_first` 路径;如需队列导出能力,创建 `flaremo-data-export` 并加 `DATA_EXPORT_QUEUE` 绑定。 +3. 部署(自动先应用 migration 再发布 Worker)。 +4. 升级后无需手动操作:聚合表已在迁移里回填,夜间校准会继续自愈。 + +### 已知问题 + +- `tags` 字段与带 space 分区的 stats 查询仍是实时扫表;D1 免费套餐下高频调用 `/api/app/stats` 仍可能触顶每日行数预算(估算方法与量级换算见 `docs/deploy.md`)。 +- 仓库内仍有 44 条 biome warning(未使用导入/变量、可选链风格、非空断言等),非门禁项,不影响发布。 +- 兼容面仍未覆盖完整 CEL、完整上游 webhook 事件/egress 语义、完整多用户 ACL、原生 JWT parity,第三方客户端 smoke test 仍未完成。 + +## v0.20.1 + +稳健性收口版本:修复工作区乐观更新的一处缓存键读取错位——乐观插入与乐观更新此前按错误的槽位解析时间线缓存键,主时间线的新记录只能等服务端失效后才出现,回收站/归档视图里的更新会把卡片短暂闪没;现在按真实键形(space/view/query/tag/untagged)逐槽读取并按可见性判定落位(私有记录不再被乐观插进团队时间线)。/calendar 日历页正式下线:任务管理收敛至 /projects(看板/截止日期/逾期提醒),逾期推送、通知铃、任务搜索、PWA 快捷方式全部改指 /projects,文案统一为「任务」;探索页只读小月历与逾期提醒保留;后端 `/api/app/calendar` 聚合端点保留(Memos 兼容面零变化)。地基清理:删除日历下线后的 8 语言死键(46 键/语言)、前端孤儿 `getCalendarView`、SPA 白名单死表项;README 与 PRD 的日历段落改写/补注记;长文件按职责再拆(worker 装配入口、import/export、管理端用户卡),函数体逐字搬移、消费方 import 零改动。稳健性:注册开关与集成凭证读取失败时留下错误日志(此前静默降级);`pnpm release` 新增 tag 与全部 workspace 版本、`FLAREMO_API_VERSION` 的一致性断言,漏 bump 会在发版前置被拦截。无 API 变化、无数据变化。 + +### 升级影响 + +- 无 migration、无资源变化、无 API 变化,直接部署即可。 +- 依赖 /calendar 深链的自动化或收藏请改指 /projects(/calendar 路由已移除);站内所有跳转已同步迁移。 +- 乐观更新修复会让「发送后卡片立即出现」恢复为预期行为,此前依赖失效回包的延迟不受影响。 + ## v0.20.0 工作区流畅度版本:搜索输入 250ms 防抖并随新请求中断旧请求(列表与语义搜索均可携带 AbortSignal);worker 端 /memos 的创建者昵称与附件两个查询并行化,减少首屏往返;搜索/筛选场景的乐观更新修复——编辑搜索结果不再被时间线状态过滤错误吞掉;加载失败可恢复——刷新/加载更多失败时保留已加载内容并给出重试入口,筛选命中为空、归档、回收站各有专属空态;搜索与标签筛选新增一键清除;工作区编辑器与搜索抽为独立组件(WorkspaceComposer/WorkspaceSearch),App.tsx 大幅瘦身。无 API 变化、无数据变化。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3612974..7347b9a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,9 +7,11 @@ ```bash pnpm install pnpm migrate:local -pnpm dev +pnpm dev:hot # 前端 HMR + Worker 热重载,浏览器开 http://localhost:5573 ``` +`pnpm dev:hot` 起 Vite 与 `wrangler dev` 两个进程,把 Worker 拥有的路径(API、SSR 分享页、R2 附件、feed)代理给后者,所以浏览器只访问一个源。前端跑在 **5573**(避开其他 Vite 项目的默认 5173)。该模式下还启用了 React Grab——悬停元素即可复制其组件与源码位置给 agent。想要无热重载的单进程模式仍可用 `pnpm dev`(Worker 托管前端构建产物,端口 8787)。 + 推荐 Node.js 22+ 和仓库声明的 pnpm 版本: ```bash @@ -21,16 +23,17 @@ pnpm install ```bash pnpm format:check -pnpm verify ``` +默认只跑与改动直接相关的测试(对应的 Vitest 文件或 e2e spec),不要求全量 `pnpm verify`。全量门禁(9 步,含 E2E)只在维护者明确要求时运行。 + 涉及 Cloudflare 配置、D1、R2 或部署脚本时: ```bash pnpm deploy:dry-run ``` -仓库带一个瘦 CI(`.github/workflows/ci.yml`:format / lint / typecheck / 单元测试,约 3 分钟),作为兜底与外部 PR 的强制门禁。它不跑 E2E、不部署;完整门禁 `pnpm verify`(含 Playwright E2E)仍由 PR 作者在本地跑完并在 PR 里写明结果。仓库中的 `flaremo-update.yml` 只服务自部署用户自己的部署仓库,用于把上游 Release 准备成升级 PR。 +仓库带一个瘦 CI(`.github/workflows/ci.yml`:format / lint / typecheck / 单元测试,约 3 分钟),作为兜底与外部 PR 的强制门禁。它不跑 E2E、不部署;如果维护者要求,PR 作者再在本地跑完整门禁 `pnpm verify`(含 Playwright E2E)并在 PR 里写明结果。仓库中的 `flaremo-update.yml` 只服务自部署用户自己的部署仓库,用于把上游 Release 准备成升级 PR。 如需自动修复格式: @@ -41,6 +44,7 @@ pnpm format ## PR 要求 - 描述用户可见变化。 +- 产品与界面改动遵循设计原则「简约不简单,克制不放肆」,细节见 `docs/design-system.md`。 - 说明是否影响 D1 migration。 - 说明是否影响 Memos 兼容 API。 - 说明是否影响 Cloudflare Access、D1、R2 或部署流程。 @@ -78,7 +82,7 @@ Memos 兼容问题请说明客户端、请求路径、请求体和返回体。 - `main` 永远代表可发布状态。 - 功能开发使用 `feat/*` 或 `codex/*` 分支。 - 每个 release 必须有 Git tag、GitHub Release、`CHANGELOG.md` 条目和升级说明。 -- 维护者发布前执行 `pnpm verify`、`pnpm deploy:dry-run` 和 `pnpm backup:drill`。 +- 维护者发布前执行 `pnpm deploy:dry-run` 和 `pnpm backup:drill`;全量 `pnpm verify` 仅在明确要求时(`pnpm release vX.Y.Z --verify`)执行。 ## 社区和支持 diff --git a/README.ar.md b/README.ar.md new file mode 100644 index 00000000..03d486de --- /dev/null +++ b/README.ar.md @@ -0,0 +1,223 @@ +
+ +# FlareMo 🔥 + +

+ بدون خوادم · تكلفة صيانة صفرية · متاح عالمياً على مدار الساعة · تحكم وسيادة كاملة في بياناتك
+ للأفراد: مساحة هادئة لتدوين الأفكار وعقل ثانٍ مدعوم بالذكاء الاصطناعي. ولفرق العمل: قاعدة معرفية مشتركة مع إدارة دقيقة للأدوار والصلاحيات. +

+ +

+ English • + 简体中文 • + 日本語 • + Français • + Español • + 한국어 • + Русский • + العربية +

+ +

+ GitHub stars + License + Cloudflare Workers + Memos Compatible + Better Auth + Website +

+ +
+ +| ☀️ سطح المكتب · الوضع الفاتح | 🌙 سطح المكتب · الوضع الداكن | 📱 الهاتف المحمول · متجاوب | +| :---: | :---: | :---: | +| واجهة FlareMo في الوضع الفاتح | واجهة FlareMo في الوضع الداكن | واجهة FlareMo للهاتف | + +لقطات حية لتجربة الاستخدام: تبديل سلس بين الوضعين الفاتح والداكن وتجاوب كامل مع الهواتف الذكية. جميع الميزات المعروضة متصلة بقدرات فعلية في الواجهة الخلفية. + +
+ +--- + +## 💡 لماذا تختار FlareMo؟ + +أثبتت أدوات مثل Flomo وMemos القيمة الهائلة للتدوين السريع بلا عوائق والخط الزمني الخالي من التشتت. لكن استضافة نظام تدوين تقليدي ذاتياً تعني عادةً استئجار خادم VPS، وإعداد Docker وPostgreSQL، وكتابة نصوص نسخ احتياطي آلي، والخوف الدائم من أعطال القرص أو العتاد. + +يجيب FlareMo عن سؤال أبسط: **هل يمكنك الحصول على قاعدة معرفية تعمل 24/7 ومرونة عالية وتسريع عالمي، من دون أي صيانة خوادم، وباستخدام حساب Cloudflare مجاني فقط؟** + +- **Serverless حقيقي**: تعمل الشيفرة والأصول الثابتة على عُقد حافة Cloudflare Workers القريبة منك بزمن استجابة يُقاس بأجزاء الثانية. +- **متانة بمستوى المؤسسات من الصندوق**: تتولى Cloudflare D1 الملاحظات والبيانات الوصفية، بينما تحفظ Cloudflare R2 المرفقات الوسائطية مع نسخ متعددة المناطق. +- **عقل ثانٍ أصيل الذكاء الاصطناعي (AI-Native)**: يأتي مركز «Agent Memory» مع واجهة سطر أوامر (CLI) ومهارة تعمل عبر الوكلاء، تتيحان لوكلاء الذكاء الاصطناعي (Claude، Cursor، Codex، ChatGPT، ZCode) قراءة تفضيلاتك طويلة المدى وسياقات ذاكرتك وتحديثها؛ كما تبقى نقاط نهاية MCP متاحة. +- **هدوء للفرد وقوة للفريق**: الوضع الافتراضي مساحة شخصية خاصة ومشفّرة، وبمجرد تفعيل وضع الفريق تتحول فوراً إلى بيئة عمل تعاونية بأدوار ورؤية ثلاثية المستويات. +- **بسيط لا ناقص**: تبقى الواجهة هادئة ولكل عنصر تحكم مكانه المستحق — لا زخرفة تصرخ لجذب الانتباه، ولا ميزة مفيدة غائبة. + +--- + +## ✨ المميزات الرئيسية + +### 1. تدوين فوري ومراجعة ملهمة +- **دوّن في أجزاء من الثانية**: خط زمني على شكل بطاقات، وسوم، دعم Markdown/GFM، ومعاينات لمرفقات الصور والصوت. +- **بحث سريع كالبرق**: فهرسة نصية كاملة عبر SQLite FTS5 مع معاملات بحث (`has:attachment`، `is:pinned`، `before:YYYY-MM-DD`، `after:YYYY-MM-DD`، `in:timeline|archive|trash`). +- **بحث دلالي «اكتشف» (Vector Search)**: تضمينات Workers AI مقترنة بفهارس Vectorize المشتقة لاستدعاء الملاحظات حسب السياق، مع إعادة التحقق من الصلاحيات مقابل D1 وتراجع سلس إلى FTS5. +- **تنشيط الأفكار**: **مراجعة يومية** مدمجة (في مثل هذا اليوم)، و**مسار عشوائي** (تجوال في شبكات الوسوم والروابط الخلفية مع ملخصات بطاقات بريدية)، واقتراحات ملاحظات ذات صلة. +- **سجل المراجعات**: مقارنة كاملة بين الإصدارات واستعادة أي نسخة سابقة بنقرة واحدة. + +### 2. ذاكرة الذكاء الاصطناعي طويلة المدى (CLI + Skills) +- **Agent Memory**: يتضمن أداة سطر الأوامر `flaremo` والمهارة `flaremo-memory` — يسجّل وكلاء الذكاء الاصطناعي الذاكرة طويلة المدى عبر الجلسات ويحدّثونها (التفضيلات، قرارات المشاريع، القيود، الدروس المستفادة) على أساس REST مشترك. +- **الإنسان في الحلقة**: راجع الذكريات التي سجّلها الذكاء الاصطناعي وتحقق منها وثبّتها أو صححها عبر `/memory`. +- **نظام بيئي مفتوح**: المسار الموصى به هو CLI + Skills، مع بقاء نقاطي النهاية `/memory/mcp` (Streamable HTTP MCP) و`/mcp` متاحتين لعملاء MCP الحاليين. + +### 3. المشاريع والمهام +- **اجمع العمل ضمن المشاريع**: نظّم الملاحظات والمهام في مشاريع، مع لوحة كانبان (سحب بين أعمدة الحالة)، وأولويات، وترتيب يدوي، ومواعيد استحقاق. +- **شخصي بطبعه وحذف قابل للتراجع**: المهام تخص مالكاً واحداً؛ الحذف ينقلها إلى سلة المهملات حتى تُستعاد أو تُمسح تلقائياً. + +### 4. إدارة المهام والتذكيرات +- **المهام تعيش في المشاريع**: لوحة `/projects` (سحب بين أعمدة الحالة)، والأولويات، والترتيب اليدوي، ومواعيد الاستحقاق تجعل صفحات المشاريع الموطن الوحيد لجدولة العمل. +- **الأطر الزمنية في لمحة**: تجمع الواجهة الرئيسية للاستكشاف بين تقويم شهري مصغّر وتذكيرات للمتأخر ومهام اليوم، فلا يختبئ ما يستحق بسببك خلف اللوحة. +- **تذكيرات المتأخر**: تُطلق المهام المتأخرة إشعارات داخل التطبيق، مع Web Push اختياري في المتصفح. + +### 5. تعاون الفرق والرؤية ثلاثية المستويات +- **حوكمة بالأدوار**: أدوار `owner` و`admin` و`member`. يدعو المسؤولون الأعضاء عبر روابط تفعيل لمرة واحدة (يختار كل عضو كلمة مروره بنفسه؛ ولا يطّلع المسؤولون على أي بيانات دخول صريحة). +- **3 مستويات للرؤية**: + - 🔒 **خاص**: لا يراه إلا مؤلفه. + - 👥 **للفريق**: قراءة مشتركة فقط لأعضاء الفريق النشطين. + - 🌐 **عام**: قراءة مجهولة عبر روابط مشاركة محددة المدة. +- **مغادرة آمنة**: إزالة عضو تطلق تنظيفاً خلفياً موثوقاً يمحو بياناته الخاصة بينما تحتفظ بملاحظات الفريق والعامة. +- **مقاعد قرّاء**: منح مقعد قراءة فقط بمدة محددة — للقرّاء الضيوف، أو دفعات الدورات، أو تسليم العملاء. تنتهي المقاعد تلقائياً عند انتهاء صلاحيتها (فشل مغلق عند حل بيانات الاعتماد، دون حاجة إلى cron). أدرها من صفحة الأعضاء، أو افتحها عبر البريد بواسطة `PUT /api/app/admin/team/reader` مع رمز وصول شخصي (انظر `docs/team-mode.md`). + +### 6. أولوية العمل دون اتصال وتجربة PWA +- **PWA قابل للتثبيت**: ثبّته على الشاشة الرئيسية في macOS أو Windows أو iOS أو Android بتجربة تحس كأنها تطبيق أصلي. +- **مزامنة موثوقة دون اتصال**: تُحفظ المسودات محلياً فوراً، وتصطف الإرسالات والملفات المرفوعة دون اتصال في قائمة انتظار تُعاد تلقائياً فور عودة الاتصال. +- **إملاء صوتي حي**: افتح `/capture` لتحويل مباشر للكلام إلى نص (ASR) بالبث في الوقت الفعلي. + +### 7. أمان متين عبر Better Auth +- **مدعوم بـ Better Auth**: جلسات كوكيز في المتصفح بخاصيتي `HttpOnly` و`SameSite=Lax`؛ ورموز وصول شخصية `memos_pat_` قابلة للإلغاء للنصوص البرمجية وواجهة سطر الأوامر وMCP. +- **حماية صارمة للـ Origin**: تفرض الطلبات التي تعدّل الحالة قائمة سماح مطابقة تماماً للمصدر. ويبقى Cloudflare Access متاحاً كمحيط دفاعي خارجي اختياري. + +### 8. التوافق مع Memos والترحيل السلس +- **توافق `/api/v1` مع Memos**: يوفر نقاط نهاية API الأساسية لـ Memos (بصيغة camelCase افتراضياً، وsnake_case القديمة عبر ترويسة) ومخطط OpenAPI. +- **جاهز لتطبيقات الطرف الثالث**: يعمل مباشرة مع عملاء الهاتف مثل Moe Memos. +- **استيراد وتصدير باتجاهين**: استيراد بنقرة واحدة من Memos / flomo مع استراتيجيات للتعامل مع التعارض، وحزم تصدير خام كاملة. + +--- + +### 9. نظام الإضافات: البطاقات كإضافات +- **خمس بطاقات مدمجة**: عادية، يومية، تذكرة، بطاقة بريدية، إضافة إلى عرض توضيحي «ختم بريدي» مرسوم على canvas. +- **المتجر والتنسيق**: تصفّح الأدلة، وتثبيت بنقرة واحدة (بتحقق SHA-256)، وتفعيل/تعطيل، وإعادة ترتيب، وتعيين البطاقة الافتراضية، والإخفاء — كل ذلك من إعدادات الحساب. الدليل الرسمي متاح على [flaremo.app/plugins](https://flaremo.app/plugins/registry.json). +- **ارفع إضافاتك الخاصة**: يمكن للمديرين تثبيت حزمة محلية — توجد على تلك النسخة فقط ولا تُرسل إلى أي مكان. +- **أدوات التأليف**: `pnpm plugin:new` ينشئ الهيكل، و`pnpm plugin:check` يتحقق وفق **نفس القواعد التي تفرضها النسخ عند التثبيت**، و`pnpm plugins:build` يحزم. بطاقات المستندات تخطيطات JSON صافية؛ وبطاقات sandbox تشغّل HTML/CSS/JS الخاصة بك. راجع [دليل الإضافات](./docs/en/plugins.md). +- **آمنة افتراضياً**: تعمل البطاقات في صندوق رملي بأصل معتم و**من دون أي وصول إلى الشبكة**؛ وتبقى حزم المجتمع والعلامات التجارية معطلة حتى يفعّلها المدير. + +## 📊 ما مدى سخاء الخطة المجانية من Cloudflare؟ + +يظن كثيرون أن «مجاني» يعني «مقيّد بشدة». أما بالنسبة لقاعدة معرفية شخصية يغلب عليها النص، فحصة Cloudflare المجانية تكاد تكون لا تنضب: + +| المورد | الحصة المجانية | السعة المكافئة | العمر الافتراضي العملي | +| :--- | :--- | :--- | :--- | +| **Cloudflare D1** | **قاعدة بيانات 5 جيجابايت** | نحو **2.5 مليون** ملاحظة نصية | كتابة 100 ملاحظة يومياً تستغرق **68 عاماً** لملئها | +| **Cloudflare R2** | **تخزين 10 جيجابايت** | نحو **5,000–10,000** صورة / **80 ساعة** صوت | **صفر تكاليف egress**؛ المشاركة العامة لن تُطلق فواتير عرض نطاق | +| **Cloudflare Workers** | حدود طلبات مجانية سخية | أكثر من 300 موقع حافة عالمياً | زمن استجابة بالميلي ثانية في كل مكان دون إقلاع بارد | + +--- + +## 🥊 مقارنة: الحل الأصيل على Cloudflare مقابل NAS منزلي مقابل VPS تقليدي + +| البُعد | أصيل على Cloudflare (FlareMo) | NAS منزلي / حاسوب مصغّر | VPS تقليدي | +| :--- | :--- | :--- | :--- | +| **متانة البيانات** | **نسخ متعددة المناطق بمستوى المؤسسات**، بلا أي خطر عطل عتاد | عطل قرص واحد أو انقطاع كهرباء قد يفقدك بياناتك كلها | يعتمد على روتين لقطات ونسخ احتياطي يدوي | +| **الصيانة** | **صفر**: لا تحديثات نظام، ولا Docker compose، ولا صيانة قاعدة بيانات | تحديثات نظام، وعناية بـ Docker، وتنبيهات SMART، وإعدادات موجّه | ترقيات نواة، وترقيعات أمنية، وبرامج مراقبة | +| **زمن الوصول** | **CDN حافة عالمي**، استجابة دون 100ms من أي مكان | يتطلب أنفاق DDNS / frp / Tailscale، ومقيّد بخط المنزل الصاعد | يعتمد على منطقة سحابية واحدة؛ زمن وصول عالٍ عبر الحدود | +| **SSL والنطاقات** | **HTTPS تلقائي** وربط نطاقات مخصصة | إصدار شهادات يدوي وإعداد وكيل عكسي | إعداد Nginx / Caddy وصيانة تجديد Let's Encrypt | +| **التكلفة المالية** | **0$ / شهر** على الخطة المجانية | تكلفة عتاد مسبقة مرتفعة + كهرباء مستمرة | فواتير شهرية / سنوية مستمرة للخادم وعرض النطاق | + +--- + +## 🚀 النشر السريع خلال 5 دقائق + +### الطريقة 1: نشر بنقرة واحدة إلى Cloudflare + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/realchendahuang/FlareMo) + +يستنسخ المستودع إلى حسابك على GitHub ويجهّز D1 وR2 وQueues وVectorize تلقائياً. بعد النشر الأولي، اضبط `FLAREMO_PUBLIC_URL` والأسرار (انظر [docs/en/deploy.md](./docs/en/deploy.md#one-click-deploy-community-supported)). إن جاءت المحاولة الأولى برسالة «Github API Limit Exceeded» فانتظر بضع دقائق وأعد المحاولة. + +### الطريقة 2: GitHub Action (استضافة ذاتية عبر fork) + +في نسختك المتفرعة (fork)، شغّل سير العمل **Deploy to Cloudflare** من تبويب Actions لتجهيز الموارد ونشر الـ Worker ومزامنة أسرار المصادقة. عمليات الدفع (push) لا تنشر من تلقاء نفسها. انظر [docs/en/github-action-deploy.md](./docs/en/github-action-deploy.md). + +### الطريقة 3: النشر بواسطة وكيل ذكاء اصطناعي (موصى بها) + +قدّم المستودع إلى وكيل قادر على تنفيذ أوامر الطرفية (مثل Claude Code أو Cursor Agent أو Codex) مع المستند [docs/en/agent-deploy.md](./docs/en/agent-deploy.md): +> «يرجى نشر FlareMo على حسابي في Cloudflare باتباع docs/en/agent-deploy.md». + +--- + +### الطريقة 4: النشر اليدوي في 3 خطوات + +#### 1. إنشاء موارد Cloudflare +```bash +pnpm exec wrangler whoami +pnpm exec wrangler d1 create flaremo +pnpm exec wrangler r2 bucket create flaremo-attachments +``` + +أو شغّل `pnpm provision:remote` بدلاً من ذلك: ينشئ موارد D1 / R2 / Queue / Vectorize الناقصة ويكتب `database_id` الخاص بـ D1 في `wrangler.jsonc` نيابة عنك. وهو أمر idempotent — الموارد القائمة تُتخطى. + +#### 2. ضبط الإعدادات والأسرار +```bash +cp wrangler.jsonc.example wrangler.jsonc +``` +املأ `database_id` الناتج واضبط `FLAREMO_PUBLIC_URL` على نطاقك الإنتاجي. ثم اضبط الأسرار: +```bash +pnpm exec wrangler secret put BETTER_AUTH_SECRET --config ./wrangler.jsonc +pnpm exec wrangler secret put FLAREMO_BOOTSTRAP_SECRET --config ./wrangler.jsonc +``` + +#### 3. النشر +```bash +pnpm deploy:dry-run +pnpm deploy +``` + +(تُشغَّل بوابة `pnpm verify` الكاملة فقط عندما يطلبها المشرف صراحةً.) +افتح نطاقك الإنتاجي على المسار `/setup` وأدخل `FLAREMO_BOOTSTRAP_SECRET` لتهيئة حساب المالك. + +أدلة مفصلة: [دليل النشر](./docs/en/deploy.md) · [النشر عبر GitHub Action](./docs/en/github-action-deploy.md) · [دليل التحديث](./docs/en/update.md). + +--- + +## 🧱 البنية المعمارية وحزمة التقنيات + +```mermaid +flowchart LR + Browser["FlareMo Web UI (React 19 / PWA)"] --> Worker["Cloudflare Worker"] + Clients["Memos Clients / Scripts / MCP"] --> Worker + + Worker --> Auth["Better Auth (Session / PAT)"] + Worker --> D1["Cloudflare D1 (Memos / Relations / Settings)"] + Access["Cloudflare Access (Optional Outer Perimeter)"] -.-> Worker + Worker --> R2["Cloudflare R2 (Attachments & Exports)"] + Worker --> Assets["Workers Static Assets"] +``` + +- **بيئة التشغيل**: Cloudflare Workers +- **الواجهة الأمامية**: React 19، Vite، TanStack Router، Tailwind CSS 4، Radix UI +- **قاعدة البيانات**: Cloudflare D1، Drizzle ORM +- **التخزين**: Cloudflare R2 +- **المصادقة**: Better Auth (جلسة كوكيز HttpOnly + رمز `memos_pat_` قابل للإلغاء) +- **الذكاء الاصطناعي والبحث**: Workers AI، Vectorize، SQLite FTS5 +- **الإضافات**: منصة امتدادات قائمة على الفتحات ([المعيار](./docs/plugin-platform-standard.md)، [الدليل](./docs/en/plugins.md))؛ تعيش الحزم في R2 وتعمل البطاقات في صندوق رملي دون وصول إلى الشبكة + +--- + +## 🌟 سجل النجوم (Star History) + +[![Star History Chart](https://api.star-history.com/svg?repos=realchendahuang/FlareMo&type=Date)](https://star-history.com/#realchendahuang/FlareMo&Date) + +--- + +## 📄 الترخيص + +مشروع مفتوح المصدر بموجب ترخيص [GNU AGPL-3.0](./LICENSE). +Copyright (c) 2026 realchendahuang. + +
diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 2812020d..00000000 --- a/README.en.md +++ /dev/null @@ -1,200 +0,0 @@ -# FlareMo - -**A Cloudflare-native team knowledge base that runs all day on a free Cloudflare account. Use it alone and it is a quiet personal notebook; use it with a team and it becomes a shared knowledge base with roles and three visibility levels (private, team-visible, public). It ships with D1, R2, Better Auth native authentication, an optional Cloudflare Access outer layer, a quiet memo timeline, and a Memos-compatible API subset.** - -[![GitHub stars](https://img.shields.io/github/stars/realchendahuang/FlareMo?style=social)](https://github.com/realchendahuang/FlareMo) -[![license](https://img.shields.io/github/license/realchendahuang/FlareMo)](./LICENSE) -[![Powered by Cloudflare](https://img.shields.io/badge/powered%20by-Cloudflare-F38020?logo=cloudflare&logoColor=white)](https://www.cloudflare.com/) -[![Memos compatible](https://img.shields.io/badge/Memos-compatible-0466c1)](https://github.com/usememos/memos) - -[中文 README](./README.md) - -

- FlareMo desktop timeline - FlareMo mobile timeline -

- -The screenshots show the current backend-backed timeline, editor, filtering, and mobile navigation. Features that are not implemented yet, such as messaging-app capture, are not exposed as placeholder UI. - -## What It Does - -- Quick memo capture with tags and attachments. -- Timeline, archive, trash, D1 FTS5 search, tag filtering, and activity heatmap. Search spans timeline and archived notes by default and supports `has:attachment`, `is:pinned`, `before:YYYY-MM-DD`, `after:YYYY-MM-DD`, and `in:timeline|archive|trash`. -- Semantic search ("Find"): Workers AI embeddings plus a Vectorize derived index, with every hit re-checked against D1 permissions; it degrades to FTS5 keyword search when the embedding provider or index is absent. See [docs/semantic-search.md](./docs/semantic-search.md) for the boundary. -- Daily review (`/review/daily`, this-day-in-history), random walk (`/review/walk`, tag/relation strolls with a postcard summary), and related notes on the memo detail page. -- An installable PWA; new memo drafts are saved locally, while offline submissions (including attachments) wait in a local queue and are submitted in order when connectivity returns. -- Markdown/GFM with image and audio attachment previews. -- Memo detail pages with relations, backlinks, and revision restore. -- Revocable public share links. -- Team mode: `owner` / `admin` / `member` roles with member management. Admins add members in the Team Management page (name + email; the server issues a one-time activation link and members set their own passwords — the admin never handles or sees a password), promote or demote admins, and remove members with a retryable data cleanup that deletes private data while keeping team and public content. -- Three visibility levels: private (author only), team-visible (read-only for active members), and public (anonymous read-only). The web UI, Memos-compatible API, MCP, attachments, search, and SSE all share one permission matrix. -- Memos-style import and export with conflict strategies. -- A current Memos-style camelCase/protobuf-JSON `/api/v1` subset for memos, attachments, relations, shares, social resources, the auth facade, and PAT resources; the Connect JSON/protobuf/gRPC-Web surface also covers the single-user UserService webhook CRUD/signing-secret and notification list/update/delete subset, including comment/mention payloads. The legacy snake_case wire remains available through an explicit header. -- Chinese and English interface. - -FlareMo keeps the UI honest: if a feature is not wired to the backend, it does not appear as a fake entry point. - -## Deployment - -Deployment is manual by design: the repository does not track `wrangler.jsonc`, and there is no one-click button or automatic deployer. Two ways to get there — pick one. - -### Agent Deployment - -Use the repository [agent deployment runbook](./docs/agent-deploy.md) with Codex, Claude Code, Cursor Agent, or another command-capable agent. The agent copies `wrangler.jsonc.example`, creates the D1 / R2 resources, fills in the `database_id`, applies migrations, and deploys. - -### Manual Deployment - -```bash -pnpm install -pnpm exec wrangler d1 create flaremo -pnpm exec wrangler r2 bucket create flaremo-attachments -``` - -Copy `wrangler.jsonc.example` to `wrangler.jsonc` (the repository does not track `wrangler.jsonc` itself), fill in the generated D1 `database_id`, and set `FLAREMO_PUBLIC_URL` to your public origin, then run: - -```bash -pnpm verify -pnpm deploy:dry-run -pnpm deploy -``` - -Full deployment docs: [docs/deploy.md](./docs/deploy.md). - -### Pre-deployment Checklist - -- Wrangler is logged in to the target Cloudflare account: `pnpm exec wrangler whoami`. -- `wrangler.jsonc` uses `DB` as the D1 binding and contains the target D1 `database_id`. -- `wrangler.jsonc` uses `ATTACHMENTS` as the R2 binding, and the target bucket exists. -- `pnpm deploy` applies pending remote D1 migrations before publishing the Worker. -- `FLAREMO_PUBLIC_URL` is set to the production canonical origin, and Better Auth secrets are configured through Wrangler or the Cloudflare dashboard. -- The one-time owner bootstrap, native login, and PAT creation have been verified; if Cloudflare Access is enabled, the outer policy and application authentication have both been tested. -- Cloudflare Access policies are optional outer controls for human access, Service Tokens, and public share bypass routes. -- The release gate has passed: `pnpm verify` and `pnpm deploy:dry-run`. - -## Auth Boundary: Better Auth, with optional Access - -FlareMo's application authentication is provided by Better Auth. On the first production deployment, the operator manually enters the one-time bootstrap secret, username, display name, email, and password in the HTTPS `/setup` page to create the single owner. Public signup is disabled after bootstrap. The main team path is adding members in the Team Management page: the server issues a one-time activation link and members set their own passwords. The owner can still enable open registration as a compatibility path (off by default), letting anyone create a member account through `/register` or the Memos-compatible `signup`. `FLAREMO_SINGLE_USER_EMAIL` and `FLAREMO_SINGLE_USER_NAME` are legacy variables for existing `users/owner` domain metadata, not login credentials or bootstrap inputs; the setup form is authoritative. Team roles, visibility permissions, and member-removal semantics are documented in [docs/team-mode.md](./docs/team-mode.md). The data model leaves room for future mapped users without changing existing memo IDs. - -- Browser login uses an `HttpOnly`, `SameSite=Lax` cookie session. -- Scripts, MCP, and Memos-compatible clients use a revocable `memos_pat_` Personal Access Token created by an authenticated account. -- The plaintext PAT is returned only at creation time; list and revoke responses do not expose it. -- Cookie-session state-changing requests such as `POST`, `PATCH`, and `DELETE` must carry an `Origin` that exactly matches `FLAREMO_PUBLIC_URL` or `FLAREMO_TRUSTED_ORIGINS`; missing or untrusted origins return `403`. PAT requests may omit `Origin` for desktop scripts and MCP clients, but a supplied `Origin` must match the same allowlist or the request returns `403`. Wildcards, `Referer`, and Access headers are not substitutes for Origin validation. -- Cloudflare Access is an optional outer layer. An Access Service Token only passes the outer policy; it does not become a FlareMo user session. If Access is enabled, clients need both layers. -- Public shares continue to use FlareMo share tokens, expiry, and memo-state checks. - -Set the production `FLAREMO_PUBLIC_URL` to an origin without a path, query, or fragment, then configure the two Worker secrets interactively: - -```bash -pnpm exec wrangler secret put BETTER_AUTH_SECRET --config ./wrangler.jsonc -pnpm exec wrangler secret put FLAREMO_BOOTSTRAP_SECRET --config ./wrangler.jsonc -``` - -Never put real secrets, the initial password, cookies, or PATs in `wrangler.jsonc`, documentation, Git, logs, or chat. See the [deployment guide](./docs/deploy.md) for the setup sequence. - -Native PAT example (`FLAREMO_MEMOS_PAT` must come from a secure local configuration): - -```bash -curl "$FLAREMO_URL/api/v1/memos" \ - -H "Authorization: Bearer $FLAREMO_MEMOS_PAT" -``` - -When Access remains enabled, add its headers as well; an Access Service Token alone is not enough for private application data: - -```bash -curl "$FLAREMO_URL/api/v1/memos" \ - -H "CF-Access-Client-Id: $FLAREMO_ACCESS_CLIENT_ID" \ - -H "CF-Access-Client-Secret: $FLAREMO_ACCESS_CLIENT_SECRET" \ - -H "Authorization: Bearer $FLAREMO_MEMOS_PAT" -``` - -## Tech Stack - -- Runtime: Cloudflare Workers -- Web: React, Vite, Tailwind CSS, shadcn/radix primitives -- API: Hono-style Worker routes, Zod contracts, OpenAPI -- Database: Cloudflare D1, Drizzle -- Object storage: Cloudflare R2 -- Auth boundary: Better Auth; optional Cloudflare Access outer layer -- Package manager: pnpm - -D1 is the source of truth for notes, users, relations, shares, settings, and attachment metadata. R2 stores only binary objects and export bundles. - -## Positioning: AI-native personal knowledge management - -FlareMo is built as **AI-native personal knowledge management**: use it alone and it is a quiet personal notebook, use it with a team and it becomes a shared knowledge base, with semantic search, AI memory, and agent read/write as first-class parts of the product. Its goals differ from Memos, so **full compatibility is not a goal**. - -Memos is the ecosystem anchor FlareMo chose, not a template to reproduce: - -- **Reuse what works**: the domain model, resource naming, the `/api/v1` protocol, OpenAPI, import/export, and the MCP direction, along with the third-party clients and scripts built around them. -- **Extend where upstream has nothing**: Agent Memory, semantic search ("Find"), projects and tasks, and audio-transcript reading are FlareMo-native capabilities, not constrained by upstream shape. -- **Take upstream features on demand**: adopt them only where we need them and the semantics make sense. Upstream interfaces such as `AIService.Transcribe` return `501` in FlareMo, because AI is FlareMo's own track. - -**Diverging from upstream over time is an expected outcome, not an accident.** Compatibility is a way to start from a mature foundation and avoid wasted effort, not a product goal in itself; FlareMo's direction is defined by its own needs. - -One engineering discipline applies to the compatibility surface: the **existing fields and semantics of `/api/v1/*` are a contract for third-party clients**, so we only add to it and never reshape it. New capabilities go to the FlareMo-native `/api/app/*` surface first, or into the freely extensible memo payload. That keeps the Memos ecosystem usable without limiting where FlareMo can grow. See the compatibility strategy section in [docs/architecture-notes.md](./docs/architecture-notes.md). - ---- - -## Memos Compatibility - -FlareMo uses Memos as an ecosystem anchor, not as an internal server fork. The compatibility layer is an adapter over FlareMo domain services. - -Current docs: - -- [Memos compatibility matrix](./docs/memos-compatibility.md) -- [Memos ecosystem matrix](./docs/memos-ecosystem.md) -- [Semantic search architecture](./docs/semantic-search.md) -- [OpenAPI](./packages/contracts/src/openapi.ts) - -The important auth detail is the two-layer boundary. A third-party Memos client must send a FlareMo PAT, and if the production instance is behind Access it must also send these headers: - -```text -CF-Access-Client-Id -CF-Access-Client-Secret -``` - -`memos_pat_` is a FlareMo-native application credential, not proof of complete Memos Server auth parity. The Origin security direction follows the [Memos 0.30 MCP browser-origin model](https://usememos.com/docs/integrations/mcp). The default `/api/v1` wire is now the implemented current camelCase/protobuf-JSON subset, while `X-FlareMo-Wire: legacy` keeps the older snake_case surface available. The Better Auth-backed auth facade and PAT resources are implemented as a subset; `accessToken` is an opaque session-backed token, not a native Memos JWT. The root `/mcp` endpoint is a stateless Streamable HTTP MCP tool subset. FlareMo has bounded social routes and a UserService webhook CRUD/signing-secret plus notification list/update/delete subset, including comment/mention payloads, and a bounded asynchronous outbox for four memo webhook events with retries; complete Memos Server parity, full CEL/Connect/SSE, complete upstream comments/reactions/shortcuts service/wire parity, full multi-user notification ACL, complete upstream webhook event semantics/egress SSRF protection, and real smoke tests for third-party clients remain unfinished. See the [compatibility matrix](./docs/memos-compatibility.md) and [ecosystem matrix](./docs/memos-ecosystem.md). - -## Development - -```bash -pnpm install -pnpm migrate:local -pnpm dev -``` - -Local URL: - -```text -http://localhost:8787 -``` - -Quality gate: - -```bash -pnpm verify -pnpm deploy:dry-run -``` - -Maintenance commands: - -```bash -pnpm format:check -pnpm screenshots -pnpm backup:drill -pnpm release vX.Y.Z -``` - -A thin CI (format / lint / typecheck / unit tests, ~3 min) guards every push and external PR. It does not run E2E and never deploys. Maintainers run the full local release gate before publishing: A self-hosted deployment repository includes the least-privilege `Prepare FlareMo update` workflow that only prepares upstream Release updates as pull requests; Cloudflare Workers Builds remains the deployer for repositories connected to it. See [the update guide](./docs/en/update.md). - -## Contributing - -Read [CONTRIBUTING.md](./CONTRIBUTING.md), [SUPPORT.md](./SUPPORT.md), [SECURITY.md](./SECURITY.md), and [ROADMAP.md](./ROADMAP.md). - -## License - -FlareMo is open source under the [GNU AGPL-3.0](./LICENSE) (AGPL-3.0-only). - -- Self-hosting, modification, and redistribution follow the AGPL-3.0 terms; if you offer a modified version as a network service, you must make the corresponding source available to that service's users. -- Copyright (c) 2026 realchendahuang. The copyright holder may dual-license beyond AGPL-3.0 for the FlareMo hosted offering. -- The "FlareMo" name and marks are not licensed under AGPL-3.0; forks and derivatives must not use the FlareMo brand for promotion or imply official endorsement. diff --git a/README.es.md b/README.es.md new file mode 100644 index 00000000..ac8e907d --- /dev/null +++ b/README.es.md @@ -0,0 +1,219 @@ +# FlareMo 🔥 + +

+ Cero Servidores · Cero Costes de Mantenimiento · Disponibilidad Global 24/7 en el Edge · Control Absoluto de tus Datos
+ Para personas: un espacio íntimo de captura de ideas y segundo cerebro con IA. Para equipos: una base de conocimiento compartida con roles detallados. +

+ +

+ English • + 简体中文 • + 日本語 • + Français • + Español • + 한국어 • + Русский • + العربية +

+ +

+ GitHub stars + License + Cloudflare Workers + Memos Compatible + Better Auth + Website +

+ +
+ +| ☀️ Escritorio · Modo Claro | 🌙 Escritorio · Modo Oscuro | 📱 Móvil · Adaptable | +| :---: | :---: | :---: | +| Interfaz de escritorio de FlareMo en modo claro | Interfaz de escritorio de FlareMo en modo oscuro | Interfaz móvil de FlareMo | + +Capturas reales de la aplicación: alternancia fluida entre temas claro y oscuro y diseño móvil completamente adaptable. Cada funcionalidad mostrada está conectada al backend. + +
+ +--- + +## 💡 ¿Por qué elegir FlareMo? + +Herramientas como Flomo y Memos han demostrado el inmenso valor de registrar notas sin fricción en una línea de tiempo limpia. Sin embargo, autohospedar un sistema de notas tradicional suele significar pagar un VPS, configurar Docker y PostgreSQL, mantener scripts de respaldo y vivir con el miedo a un fallo de disco o de hardware. + +FlareMo responde a una pregunta más simple: **¿Es posible tener una base de conocimiento en línea las 24 horas, resiliente, con aceleración global y sin mantenimiento de servidores, usando solo una cuenta gratuita de Cloudflare?** + +- **Auténtico serverless**: El código y los archivos estáticos se ejecutan en los nodos perimetrales (edge) de Cloudflare Workers más cercanos a ti, con latencia de milisegundos. +- **Durabilidad de nivel empresarial desde el primer minuto**: Cloudflare D1 almacena notas y metadatos; Cloudflare R2 guarda los archivos adjuntos con replicación multirregional. +- **Segundo cerebro nativo de IA**: El centro «Agent Memory» incluye un CLI y un skill multiagente para que los agentes de IA (Claude, Cursor, Codex, ChatGPT, ZCode) lean y actualicen tus preferencias y ámbitos de memoria a largo plazo; también hay endpoints MCP disponibles. +- **Silencioso para uno, potente para muchos**: Por defecto es un santuario privado y cifrado de usuario único. Activa el modo de equipo y se transforma al instante en un espacio de trabajo colaborativo con roles y tres niveles de visibilidad. +- **Mínimo, no simplificado**: La interfaz serena y cada control se gana su lugar — nada de decoración estridente, ninguna función útil ausente. + +--- + +## ✨ Características principales + +### 1. Captura instantánea y revisión que inspira +- **Captura en milisegundos**: Línea de tiempo en tarjetas, etiquetas, Markdown/GFM y vista previa de imágenes y audios adjuntos. +- **Búsqueda ultrarrápida**: Indexación de texto completo SQLite FTS5 con operadores de consulta (`has:attachment`, `is:pinned`, `before:YYYY-MM-DD`, `after:YYYY-MM-DD`, `in:timeline|archive|trash`). +- **Búsqueda semántica («Find» vectorial)**: Embeddings de Workers AI combinados con índices vectoriales derivados de Vectorize para recordar por contexto; revalida permisos contra D1 y degrada con elegancia a FTS5. +- **Reactivación de ideas**: **Revisión diaria** (un día como hoy), **Paseo aleatorio** (deambular por los grafos de etiquetas y enlaces con resúmenes tipo postal) y sugerencias de notas relacionadas. +- **Historial de versiones**: Comparación completa de versiones y restauración histórica con un solo clic. + +### 2. Memoria de IA a largo plazo (CLI + Skills) +- **Agent Memory**: Incluye el CLI `flaremo` y el skill `flaremo-memory` — los agentes de IA registran y actualizan memoria a largo plazo entre sesiones (preferencias, decisiones de proyecto, restricciones, lecciones) sobre una base REST común. +- **Humano en el bucle**: En `/memory` puedes revisar, verificar, fijar o corregir las memorias registradas por la IA. +- **Ecosistema abierto**: La vía recomendada es CLI + Skills; los endpoints `/memory/mcp` (MCP por HTTP en streaming) y `/mcp` siguen disponibles para clientes MCP existentes. + +### 3. Proyectos y tareas +- **Agrupa el trabajo por proyectos**: Organiza notas y pendientes en proyectos, con tablero kanban (arrastrar entre columnas de estado), prioridades, orden manual y fechas límite. +- **Privado por diseño, borrado reversible**: Las tareas pertenecen a un solo propietario; al eliminarlas van a la papelera hasta que se restauren o se purguen automáticamente. + +### 4. Gestión de tareas y recordatorios +- **Las tareas viven en los proyectos**: El tablero de `/projects` (arrastrar entre columnas de estado), las prioridades, el orden manual y las fechas límite convierten las páginas de proyecto en el único hogar para planificar el trabajo. +- **Horizontes temporales de un vistazo**: La vista de inicio del explorador combina un mini calendario mensual con recordatorios de vencidos y de hoy, para que lo que vence nunca se esconda tras el tablero. +- **Recordatorios de vencidos**: Las tareas atrasadas generan notificaciones dentro de la app, con Web Push opcional en el navegador. + +### 5. Colaboración en equipo y visibilidad en 3 niveles +- **Gobernanza por roles**: Roles `owner`, `admin` y `member`. Los administradores invitan a los miembros mediante enlaces de activación de un solo uso (cada miembro elige su propia contraseña; los administradores nunca manejan credenciales en texto plano). +- **3 niveles de visibilidad**: + - 🔒 **Privado**: Solo visible para el autor. + - 👥 **Equipo**: Lectura compartida con los miembros activos del equipo. + - 🌐 **Público**: Lectura anónima mediante enlaces de compartir con tiempo límite. +- **Salida segura**: Al retirar a un miembro se activa una limpieza fiable en segundo plano que purga sus datos privados preservando las notas de equipo y públicas. +- **Asientos de lector**: Concede un asiento de solo lectura por tiempo limitado — lectores invitados, alumnos de un curso, entregas a clientes. Los asientos caducan automáticamente a su vencimiento (fail-closed al resolver la credencial, sin cron). Gestiónalos desde la página de miembros o aprovisiónalos por email mediante `PUT /api/app/admin/team/reader` con un token de acceso personal (ver `docs/team-mode.md`). + +### 6. Sin conexión primero y experiencia PWA +- **PWA instalable**: Instala FlareMo en la pantalla de inicio de macOS, Windows, iOS o Android con tacto de app nativa. +- **Sincronización offline fiable**: Los borradores se guardan al instante en local; los envíos y las subidas sin conexión se ponen en cola y se reproducen automáticamente al recuperar la red. +- **Captura de voz en vivo**: Accede a `/capture` para transcripción de voz a texto en streaming en tiempo real (ASR). + +### 7. Seguridad robusta con Better Auth +- **Impulsado por Better Auth**: Sesiones por cookie de navegador `HttpOnly` y `SameSite=Lax`; tokens de acceso personal `memos_pat_` revocables para scripts, CLI y MCP. +- **Protección estricta de Origin**: Las peticiones con cambio de estado exigen una lista blanca de origen exacta. Cloudflare Access sigue disponible como perímetro defensivo exterior opcional. + +### 8. Compatibilidad con Memos y migración sin costuras +- **Compatibilidad con la API `/api/v1` de Memos**: Proporciona los endpoints principales de la API de Memos (camelCase por defecto, snake_case heredado vía cabecera) y el esquema OpenAPI. +- **Listo para apps de terceros**: Funciona directamente con clientes móviles como Moe Memos. +- **Importación y exportación bidireccionales**: Importación en un clic desde Memos / flomo con estrategias de conflicto y paquetes de exportación completos en formato original. + +--- + +### 9. Sistema de plugins: las tarjetas son plugins +- **Cinco tarjetas incluidas**: Sencilla, Diaria, Billete, Postal y un Matasellos demo dibujado con canvas. +- **Tienda y curación**: explora directorios, instalación en un clic (verificación SHA-256), activar/desactivar, reordenar, fijar la predeterminada, ocultar — todo en los ajustes de cuenta. El directorio oficial vive en [flaremo.app/plugins](https://flaremo.app/plugins/registry.json). +- **Sube los tuyos**: los administradores pueden instalar un paquete local — solo existe en esa instancia y nunca se envía a ningún sitio. +- **Herramientas de autor**: `pnpm plugin:new` genera el esqueleto, `pnpm plugin:check` valida con **exactamente las reglas que las instancias aplican al instalar**, `pnpm plugins:build` empaqueta. Las tarjetas de documento son maquetaciones JSON puras; las tarjetas sandbox ejecutan tu propio HTML/CSS/JS. Ver la [guía de plugins](./docs/en/plugins.md). +- **Seguro por defecto**: las tarjetas se ejecutan en un sandbox de origen opaco y **sin acceso a la red**; los paquetes de comunidad y de marca permanecen desactivados hasta que un administrador los active. + +## 📊 ¿Cuán generoso es el plan gratuito de Cloudflare? + +Muchos asumen que «gratis» significa «severamente limitado». Para bases de conocimiento personales con mucho texto, la cuota gratuita de Cloudflare es prácticamente inagotable: + +| Recurso | Cuota gratuita | Capacidad equivalente | Vida útil práctica | +| :--- | :--- | :--- | :--- | +| **Cloudflare D1** | **Base de datos de 5 GB** | ~**2,5 millones** de notas de texto | Escribiendo 100 notas al día tardarías **68 años** en llenarla | +| **Cloudflare R2** | **10 GB de almacenamiento** | ~**5.000–10.000** fotos / **80 horas** de voz | **0 $ de costes de salida**; compartir en público no genera facturas de ancho de banda | +| **Cloudflare Workers** | Límites generosos de peticiones gratuitas | Más de 300 ubicaciones edge globales | Latencia de milisegundos en todo el mundo sin arranques en frío | + +--- + +## 🥊 Comparativa: Cloudflare nativo vs NAS casero vs VPS tradicional + +| Dimensión | Cloudflare nativo (FlareMo) | NAS casero / Mini PC | VPS tradicional | +| :--- | :--- | :--- | :--- | +| **Durabilidad de los datos** | **Replicación multirregional empresarial**, cero riesgo de fallo de hardware | Un fallo de disco o un corte de luz puede suponer la pérdida total de datos | Depende de rutinas manuales de snapshots y copias de seguridad | +| **Mantenimiento** | **Cero**: sin parches de SO, sin Docker compose, sin mantenimiento de BD | Actualizaciones del SO, mantenimiento de Docker, alertas SMART de disco, configuración del router | Actualizaciones de kernel, parches de seguridad, daemons de vigilancia | +| **Latencia de acceso** | **CDN global en el edge**, respuesta de menos de 100 ms en cualquier lugar | Requiere túneles DDNS / frp / Tailscale, limitado por la subida de casa | Depende de una única región cloud; alta latencia transfronteriza | +| **SSL y dominios** | **HTTPS automatizado** y enlace de dominios personalizados | Emisión manual de certificados, configuración de proxy inverso | Configuración de Nginx / Caddy y renovación de Let's Encrypt | +| **Coste económico** | **0 $ / mes** en el plan gratuito | Alto coste inicial de hardware + electricidad continua | Facturas mensuales / anuales continuas de servidor y ancho de banda | + +--- + +## 🚀 Despliegue rápido en 5 minutos + +### Método 1: Despliegue en un clic en Cloudflare + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/realchendahuang/FlareMo) + +Clona el repositorio en tu cuenta de GitHub y aprovisiona automáticamente D1, R2, Queues y Vectorize. Tras el despliegue inicial, configura `FLAREMO_PUBLIC_URL` y los secretos (ver [docs/en/deploy.md](./docs/en/deploy.md#one-click-deploy-community-supported)). Si el primer intento informa de «Github API Limit Exceeded», espera unos minutos y reintenta. + +### Método 2: GitHub Action (fork autohospedado) + +En tu fork, ejecuta **Deploy to Cloudflare** desde Actions para aprovisionar los recursos, publicar el Worker y sincronizar los secretos de autenticación. Los push no publican. Ver [docs/en/github-action-deploy.md](./docs/en/github-action-deploy.md). + +### Método 3: Despliegue con un agente IA (recomendado) + +Proporciona este repositorio a un agente capaz de ejecutar comandos de terminal (p. ej. Claude Code, Cursor Agent, Codex) junto con [docs/en/agent-deploy.md](./docs/en/agent-deploy.md): +> «Por favor, despliega FlareMo en mi cuenta de Cloudflare siguiendo docs/en/agent-deploy.md». + +--- + +### Método 4: Despliegue manual en 3 pasos + +#### 1. Crear recursos de Cloudflare +```bash +pnpm exec wrangler whoami +pnpm exec wrangler d1 create flaremo +pnpm exec wrangler r2 bucket create flaremo-attachments +``` + +O ejecuta `pnpm provision:remote` en su lugar: crea los recursos D1 / R2 / Queue / Vectorize que falten y escribe el `database_id` de D1 en `wrangler.jsonc` por ti. Es idempotente — los recursos existentes se omiten. + +#### 2. Configurar ajustes y secretos +```bash +cp wrangler.jsonc.example wrangler.jsonc +``` +Rellena el `database_id` generado y establece `FLAREMO_PUBLIC_URL` en tu dominio de producción. Después configura los secretos: +```bash +pnpm exec wrangler secret put BETTER_AUTH_SECRET --config ./wrangler.jsonc +pnpm exec wrangler secret put FLAREMO_BOOTSTRAP_SECRET --config ./wrangler.jsonc +``` + +#### 3. Desplegar +```bash +pnpm deploy:dry-run +pnpm deploy +``` + +(El gate completo `pnpm verify` solo se ejecuta cuando el mantenedor lo solicita explícitamente.) +Visita `/setup` en tu dominio de producción e introduce el `FLAREMO_BOOTSTRAP_SECRET` para inicializar tu cuenta de Owner. + +Guías detalladas: [Guía de despliegue](./docs/en/deploy.md) · [Despliegue con GitHub Action](./docs/en/github-action-deploy.md) · [Guía de actualización](./docs/en/update.md). + +--- + +## 🧱 Arquitectura y stack tecnológico + +```mermaid +flowchart LR + Browser["FlareMo Web UI (React 19 / PWA)"] --> Worker["Cloudflare Worker"] + Clients["Memos Clients / Scripts / MCP"] --> Worker + + Worker --> Auth["Better Auth (Session / PAT)"] + Worker --> D1["Cloudflare D1 (Memos / Relations / Settings)"] + Access["Cloudflare Access (Optional Outer Perimeter)"] -.-> Worker + Worker --> R2["Cloudflare R2 (Attachments & Exports)"] + Worker --> Assets["Workers Static Assets"] +``` + +- **Runtime**: Cloudflare Workers +- **Frontend**: React 19, Vite, TanStack Router, Tailwind CSS 4, Radix UI +- **Base de datos**: Cloudflare D1, Drizzle ORM +- **Almacenamiento**: Cloudflare R2 +- **Autenticación**: Better Auth (sesión por cookie HttpOnly + `memos_pat_` revocable) +- **IA y búsqueda**: Workers AI, Vectorize, SQLite FTS5 +- **Plugins**: plataforma de extensión basada en slots ([estándar](./docs/plugin-platform-standard.md), [guía](./docs/en/plugins.md)); los paquetes viven en R2 y las tarjetas sandbox se ejecutan sin acceso a la red + +--- + +## 🌟 Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=realchendahuang/FlareMo&type=Date)](https://star-history.com/#realchendahuang/FlareMo&Date) + +--- + +## 📄 Licencia + +Publicado como código abierto bajo licencia [GNU AGPL-3.0](./LICENSE). +Copyright (c) 2026 realchendahuang. diff --git a/README.fr.md b/README.fr.md new file mode 100644 index 00000000..d1095979 --- /dev/null +++ b/README.fr.md @@ -0,0 +1,219 @@ +# FlareMo 🔥 + +

+ Zéro serveur · Aucun coût de maintenance · Haute disponibilité globale 24/7 · Maîtrise totale de vos données
+ Pour les particuliers : un espace de prise de notes intime et second cerveau IA. Pour les équipes : une base de connaissances partagée avec gestion fine des rôles. +

+ +

+ English • + 简体中文 • + 日本語 • + Français • + Español • + 한국어 • + Русский • + العربية +

+ +

+ GitHub stars + License + Cloudflare Workers + Memos Compatible + Better Auth + Website +

+ +
+ +| ☀️ Bureau · Thème Clair | 🌙 Bureau · Thème Sombre | 📱 Mobile · Adaptatif | +| :---: | :---: | :---: | +| Interface FlareMo thème clair | Interface FlareMo thème sombre | Interface mobile FlareMo | + +Captures réelles d'utilisation : transition fluide entre modes clair et sombre, design mobile entièrement réactif. Toutes les fonctionnalités présentées sont reliées au backend. + +
+ +--- + +## 💡 Pourquoi choisir FlareMo ? + +Les outils comme Flomo ou Memos ont démontré la puissance d'une capture d'idées fluide et d'un fil chronologique sans distraction. Cependant, auto-héberger un système traditionnel implique souvent de payer un VPS chaque mois, de configurer Docker et PostgreSQL, d'écrire des scripts de sauvegarde et de redouter la panne d'un disque dur. + +FlareMo répond à une question plus simple : **Peut-on obtenir une base de connaissances en ligne 24h/24, résiliente, accélérée mondialement et sans maintenance de serveur, simplement avec un compte gratuit Cloudflare ?** + +- **Véritablement Serverless** : Le code et les fichiers statiques s'exécutent sur les nœuds edge Cloudflare les plus proches de vous, avec une latence en millisecondes. +- **Durabilité de classe entreprise dès l'installation** : Cloudflare D1 gère les notes et les métadonnées ; Cloudflare R2 stocke les pièces jointes avec réplication multi-régions. +- **Second cerveau pensé pour l'IA** : le hub « Agent Memory » embarque un CLI et un skill multi-agents permettant à vos agents IA (Claude, Cursor, Codex, ChatGPT, ZCode) de lire et mettre à jour vos préférences et votre mémoire à long terme ; des endpoints MCP restent également disponibles. +- **Discret pour un seul, puissant pour une équipe** : Par défaut, un sanctuaire personnel chiffré et privé. Activez le mode équipe, et il se transforme instantanément en espace de travail collaboratif avec rôles et visibilité à trois niveaux. +- **Minimal, pas simplifié** : L'interface reste silencieuse et chaque commande mérite sa place — rien de décoratif qui crie, rien d'utile qui manque. + +--- + +## ✨ Fonctionnalités clés + +### 1. Capture instantanée & Revue stimulante +- **Prise de note immédiate** : Flux chronologique sous forme de cartes, multi-étiquettes, Markdown/GFM, prévisualisation des pièces jointes image et audio. +- **Recherche plein texte ultra-rapide** : Indexation SQLite FTS5 avec opérateurs de requête (`has:attachment`, `is:pinned`, `before:YYYY-MM-DD`, `after:YYYY-MM-DD`, `in:timeline|archive|trash`). +- **Recherche sémantique vectorielle** : Embeddings Workers AI couplés aux index vectoriels dérivés de Vectorize pour un rappel contextuel ; re-vérification des permissions contre D1 et repli transparent sur FTS5. +- **Activation de la pensée** : **Revue quotidienne** (ce jour-là dans l'histoire), **Balade aléatoire** (dérive dans les graphes d'étiquettes et de liens retour, avec résumés en carte postale) et suggestions de notes connexes. +- **Historique des versions** : Diff complet des versions et restauration historique en un clic. + +### 2. Mémoire IA à long terme (CLI + Skills) +- **Agent Memory** : Livré avec le CLI `flaremo` et le skill `flaremo-memory` — vos agents IA enregistrent et mettent à jour une mémoire à long terme transversale aux sessions (préférences, décisions de projet, contraintes, leçons) sur une base REST commune. +- **Contrôle humain** : Consultez, vérifiez, verrouillez ou corrigez les souvenirs enregistrés par l'IA sur la page `/memory`. +- **Écosystème ouvert** : La voie recommandée est CLI + Skills ; les endpoints `/memory/mcp` (Streamable HTTP MCP) et `/mcp` restent disponibles pour les clients MCP existants. + +### 3. Projets et tâches +- **Rassemblez le travail par projet** : Notes et tâches organisées en projets, avec tableau kanban (glisser entre les colonnes de statut), priorités, tri manuel et échéances. +- **Privé par conception, suppression réversible** : Les tâches appartiennent à un seul propriétaire ; la suppression les déplace vers une corbeille jusqu'à restauration ou purge automatique. + +### 4. Gestion des tâches et rappels +- **Les tâches vivent dans les projets** : Le tableau `/projects` (glisser entre les colonnes de statut), les priorités, le tri manuel et les échéances font des pages projet l'endroit unique où planifier le travail. +- **Le temps en un coup d'œil** : La vue d'accueil de l'explorateur associe un mini-calendrier mensuel aux rappels des tâches en retard et du jour, pour qu'aucune échéance ne se cache derrière le tableau. +- **Rappels des retards** : Les tâches en retard déclenchent des notifications dans l'application, avec Web Push navigateur en option. + +### 5. Collaboration d'équipe & Visibilité à 3 niveaux +- **Gouvernance par rôles** : Rôles `owner`, `admin` et `member`. Les administrateurs invitent les membres via des liens d'activation à usage unique (les membres choisissent leur propre mot de passe ; les administrateurs ne manipulent jamais d'identifiants en clair). +- **Visibilité à 3 niveaux** : + - 🔒 **Privé** : Visible uniquement par l'auteur. + - 👥 **Équipe** : Lecture partagée avec les membres actifs de l'équipe. + - 🌐 **Public** : Lecture anonyme via des liens de partage à durée limitée. +- **Départ sécurisé** : Le retrait d'un membre déclenche un nettoyage fiable en arrière-plan qui purge les données privées tout en préservant les notes d'équipe et publiques. +- **Sièges lecteurs** : Attribuez un siège en lecture seule à durée limitée — lecteurs invités, cohortes de cours, livraisons client. Les sièges expirent automatiquement à leur échéance (fail-closed à la résolution des identifiants, sans cron). Gérez-les depuis la page membres, ou provisionnez-les par e-mail via `PUT /api/app/admin/team/reader` avec un jeton d'accès personnel (voir `docs/team-mode.md`). + +### 6. Mode hors-ligne & Expérience PWA +- **PWA installable** : Installez FlareMo sur l'écran d'accueil de macOS, Windows, iOS ou Android, avec une sensation d'application native. +- **Synchronisation hors-ligne fiable** : Les brouillons s'enregistrent localement instantanément. Les soumissions et envois hors-ligne sont mis en file d'attente et rejoués automatiquement dès le retour de la connexion. +- **Dictée vocale en direct** : Page `/capture` avec transcription vocale continue en temps réel (ASR). + +### 7. Sécurité applicative Better Auth +- **Propulsé par Better Auth** : Sessions par cookies `HttpOnly` et `SameSite=Lax` pour le navigateur ; jetons d'accès personnels révocables (`memos_pat_`) pour les scripts, le CLI et MCP. +- **Protection stricte de l'Origin** : Les requêtes modifiant l'état imposent une liste blanche d'origines exactes. Cloudflare Access reste disponible comme périmètre défensif externe optionnel. + +### 8. Compatibilité Memos & Migration sans friction +- **Compatibilité `/api/v1` de Memos** : Fournit les principaux endpoints de l'API Memos (camelCase par défaut, snake_case historique via en-tête) et le schéma OpenAPI. +- **Prêt pour les applications tierces** : Fonctionne directement avec des clients mobiles comme Moe Memos. +- **Import / Export bidirectionnel** : Import en un clic depuis Memos / flomo avec stratégies de gestion des conflits et paquets d'export brut complets. + +--- + +### 9. Système d'extensions : les cartes sont des extensions +- **Cinq cartes incluses** : Blanc, Citation du jour, Ticket, Carte postale, plus un Cachet dessiné au canvas. +- **Boutique et gestion** : dans les réglages du compte — parcourir les répertoires, installation en un clic (vérification SHA-256), activation/désactivation, réordonnancement, carte par défaut, masquage. Le répertoire officiel : [flaremo.app/plugins](https://flaremo.app/plugins/registry.json). +- **Importez les vôtres** : un administrateur peut installer un paquet local — il n'existe que sur cette instance et n'est jamais transmis ailleurs. +- **Outils d'auteur** : `pnpm plugin:new` génère un squelette, `pnpm plugin:check` valide avec **exactement les règles appliquées à l'installation**, `pnpm plugins:build` empaquette. Les cartes document sont de pures mises en page JSON ; les cartes sandbox exécutent votre HTML/CSS/JS. Voir le [guide des extensions](./docs/en/plugins.md). +- **Sûr par défaut** : les cartes s'exécutent dans un bac à sable à origine opaque, **sans aucun accès réseau** ; les paquets communautaires et de marque restent désactivés jusqu'à validation par un administrateur. + +## 📊 Jusqu'où va la générosité du niveau gratuit Cloudflare ? + +Beaucoup pensent que « gratuit » rime avec « sévèrement limité ». Pour une base de connaissances personnelle à dominante textuelle, le quota gratuit de Cloudflare est quasiment inépuisable : + +| Ressource | Quota gratuit Cloudflare | Équivalent en volume | Durée d'utilisation estimée | +| :--- | :--- | :--- | :--- | +| **Cloudflare D1** | **5 Go de base de données** | Env. **2,5 millions** de notes | À raison de 100 notes par jour : **68 ans** pour remplir | +| **Cloudflare R2** | **10 Go de stockage** | Env. **5 000 à 10 000 photos** / **80 h** de voix | **0 $ de frais de bande passante sortante** ; le partage public ne déclenche aucune facture | +| **Cloudflare Workers** | Limites de requêtes gratuites généreuses | 300+ points de présence edge mondiaux | Latence en millisecondes partout, sans démarrage à froid | + +--- + +## 🥊 Comparatif : Cloudflare natif vs NAS domestique vs VPS traditionnel + +| Dimension | Cloudflare natif (FlareMo) | NAS domestique / Mini PC | VPS traditionnel | +| :--- | :--- | :--- | :--- | +| **Durabilité des données** | **Réplication multi-régions de classe entreprise**, aucun risque de panne matérielle | Une panne de disque ou de courant peut entraîner une perte totale | Dépendant de routines manuelles de snapshot et de sauvegarde | +| **Maintenance** | **Zéro** : pas de mises à jour d'OS, pas de Docker compose, pas de maintenance de base | Mises à jour d'OS, entretien Docker, alertes SMART, configuration du routeur | Mises à niveau du noyau, correctifs de sécurité, démons de surveillance | +| **Latence d'accès** | **CDN edge mondial**, réponse en moins de 100 ms partout | Nécessite DDNS / frp / tunnels Tailscale, limité par la montée en débit domestique | Dépendant d'une seule région cloud ; latence transfrontalière élevée | +| **SSL & domaines** | **HTTPS automatisé** et liaison de domaines personnalisés | Émission manuelle de certificats, configuration de reverse proxy | Configuration Nginx / Caddy et renouvellement Let's Encrypt à maintenir | +| **Coût financier** | **0 $ / mois** au niveau gratuit | Investissement matériel initial élevé + électricité en continu | Factures mensuelles / annuelles de serveur et de bande passante | + +--- + +## 🚀 Déploiement rapide en 5 minutes + +### Méthode 1 : Déploiement en un clic vers Cloudflare + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/realchendahuang/FlareMo) + +Clone le dépôt dans votre compte GitHub et provisionne automatiquement D1, R2, Queues et Vectorize. Après le déploiement initial, définissez `FLAREMO_PUBLIC_URL` et les secrets (voir [docs/en/deploy.md](./docs/en/deploy.md#one-click-deploy-community-supported)). Si la première tentative signale « Github API Limit Exceeded », attendez quelques minutes puis réessayez. + +### Méthode 2 : GitHub Action (fork auto-hébergé) + +Sur votre fork, lancez **Deploy to Cloudflare** depuis les Actions pour provisionner les ressources, publier le Worker et synchroniser les secrets d'authentification. Les pushes ne publient rien. Voir [docs/en/github-action-deploy.md](./docs/en/github-action-deploy.md). + +### Méthode 3 : Déploiement par Agent IA (Recommandé) + +Confiez ce dépôt à un agent capable d'exécuter des commandes dans un terminal (Claude Code, Cursor Agent, Codex…) avec le fichier [docs/en/agent-deploy.md](./docs/en/agent-deploy.md) : +> « Veuillez déployer FlareMo sur mon compte Cloudflare en suivant docs/en/agent-deploy.md. » + +--- + +### Méthode 4 : Déploiement manuel en 3 étapes + +#### 1. Créer les ressources Cloudflare +```bash +pnpm exec wrangler whoami +pnpm exec wrangler d1 create flaremo +pnpm exec wrangler r2 bucket create flaremo-attachments +``` + +Ou lancez plutôt `pnpm provision:remote` : il crée les ressources D1 / R2 / Queue / Vectorize manquantes et écrit le `database_id` D1 dans `wrangler.jsonc` à votre place. L'opération est idempotente — les ressources existantes sont ignorées. + +#### 2. Configurer les réglages et les secrets +```bash +cp wrangler.jsonc.example wrangler.jsonc +``` +Renseignez le `database_id` généré et définissez `FLAREMO_PUBLIC_URL` sur votre domaine de production. Puis configurez les secrets : +```bash +pnpm exec wrangler secret put BETTER_AUTH_SECRET --config ./wrangler.jsonc +pnpm exec wrangler secret put FLAREMO_BOOTSTRAP_SECRET --config ./wrangler.jsonc +``` + +#### 3. Déployer +```bash +pnpm deploy:dry-run +pnpm deploy +``` + +(La barrière complète `pnpm verify` ne s'exécute que lorsque le mainteneur le demande explicitement.) +Rendez-vous sur `/setup` de votre domaine de production et saisissez le `FLAREMO_BOOTSTRAP_SECRET` pour initialiser votre compte Propriétaire. + +Guides détaillés : [Guide de déploiement](./docs/en/deploy.md) · [Déploiement GitHub Action](./docs/en/github-action-deploy.md) · [Guide de mise à jour](./docs/en/update.md). + +--- + +## 🧱 Architecture & Stack technique + +```mermaid +flowchart LR + Browser["FlareMo Web UI (React 19 / PWA)"] --> Worker["Cloudflare Worker"] + Clients["Memos Clients / Scripts / MCP"] --> Worker + + Worker --> Auth["Better Auth (Session / PAT)"] + Worker --> D1["Cloudflare D1 (Memos / Relations / Settings)"] + Access["Cloudflare Access (Optional Outer Perimeter)"] -.-> Worker + Worker --> R2["Cloudflare R2 (Attachments & Exports)"] + Worker --> Assets["Workers Static Assets"] +``` + +- **Runtime** : Cloudflare Workers +- **Frontend** : React 19, Vite, TanStack Router, Tailwind CSS 4, Radix UI +- **Base de données** : Cloudflare D1, Drizzle ORM +- **Stockage** : Cloudflare R2 +- **Authentification** : Better Auth (session cookie HttpOnly + `memos_pat_` révocable) +- **IA & recherche** : Workers AI, Vectorize, SQLite FTS5 +- **Extensions** : plateforme d'extension à slots ([standard](./docs/plugin-platform-standard.md), [guide](./docs/en/plugins.md)) ; les paquets résident dans R2, les cartes sandbox s'exécutent sans accès réseau + +--- + +## 🌟 Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=realchendahuang/FlareMo&type=Date)](https://star-history.com/#realchendahuang/FlareMo&Date) + +--- + +## 📄 Licence + +Projet open source sous licence [GNU AGPL-3.0](./LICENSE). +Copyright (c) 2026 realchendahuang. diff --git a/README.ja.md b/README.ja.md new file mode 100644 index 00000000..976f294c --- /dev/null +++ b/README.ja.md @@ -0,0 +1,219 @@ +# FlareMo 🔥 + +

+ サーバー不要 · 運用コストゼロ · 24時間グローバル常時稼働 · データの完全な自己所有
+ 個人では静かで集中できる思考記録スペース&第2の脳として、チームではきめ細やかな権限を持つ共有ナレッジベースとして。 +

+ +

+ English • + 简体中文 • + 日本語 • + Français • + Español • + 한국어 • + Русский • + العربية +

+ +

+ GitHub stars + License + Cloudflare Workers + Memos Compatible + Better Auth + Website +

+ +
+ +| ☀️ デスクトップ · ライトテーマ | 🌙 デスクトップ · ダークテーマ | 📱 モバイル · レスポンシブ | +| :---: | :---: | :---: | +| FlareMo デスクトップ ライト画面 | FlareMo デスクトップ ダーク画面 | FlareMo モバイル画面 | + +実際の稼働画面:ライト・ダークテーマのシームレスな切り替えと、フル機能のモバイルレスポンシブ表示。表示されている機能はすべて、実際に動作するバックエンド機能に接続されています。 + +
+ +--- + +## 💡 FlareMoを選ぶ理由 + +FlomoやMemosといったツールは、摩擦の少ないメモ記録と邪魔の入らないタイムラインが持つ大きな価値を証明しました。しかし、従来型のノート環境をセルフホストするには、VPSを契約し、DockerとPostgreSQLを構成し、自動バックアップをスクリプトで組み、ディスクやハードウェアの故障に怯え続けることが普通でした。 + +FlareMoが立ち向かう問いはもっとシンプルです:**無料のCloudflareアカウント1つだけで、サーバー保守ゼロで、24時間365日オンラインで耐障害性が高く、世界中から高速にアクセスできるナレッジベースを持てるのか?** + +- **真のサーバーレス**:コードも静的アセットも、ユーザーの近くにあるCloudflare Workersのエッジノード上でミリ秒応答。 +- **最初からエンタープライズ級の耐久性**:ノートとメタデータはCloudflare D1が担当し、メディア添付ファイルはマルチリージョンレプリケーション付きのCloudflare R2に保存。 +- **AIネイティブな第2の脳**:Agent MemoryハブにCLIとクロスエージェントSkillを同梱。AIエージェント(Claude、Cursor、Codex、ChatGPT、ZCode)が長期的な好みや記憶スコープを読み取り・更新できます。MCPエンドポイントも利用可能です。 +- **一人には静かに、大勢には強力に**:デフォルトは暗号化された一人用のプライベートな空間。チームモードを有効にすると、ロールと3段階の公開範囲を備えた共同作業スペースに即座に変わります。 +- **シンプル、でも粗削りではない**:インターフェースは静かに保たれ、すべての操作に存在する理由がある — 見せびらかす装飾はなく、役に立つものの欠落もありません。 + +--- + +## ✨ 主な機能 + +### 1. 瞬時のメモ作成とインスピレーションの振り返り +- **ミリ秒でキャプチャ**:カード型タイムライン、タグ、Markdown/GFM、画像・音声添付のプレビューに対応。 +- **高速検索**:SQLite FTS5による全文インデックスとクエリ演算子(`has:attachment`、`is:pinned`、`before:YYYY-MM-DD`、`after:YYYY-MM-DD`、`in:timeline|archive|trash`)。 +- **セマンティックな「探す」(ベクトル検索)**:Workers AIの埋め込みとVectorizeの派生ベクトルインデックスを組み合わせ、文脈に応じた呼び出しを実現。D1で権限を再検証し、必要時はFTS5へシームレスにフォールバック。 +- **思考を呼び起こす**:内蔵の**デイリーレビュー**(この日の記録)、**ランダムウォーク**(タグやバックリンクのグラフをポストカード風の要約とともに散策)、関連ノートのおすすめ。 +- **改訂履歴**:全バージョンの差分表示とワンクリックでの過去バージョン復元。 + +### 2. AI長期記憶(CLI + Skills) +- **Agent Memory**:`flaremo` CLIと`flaremo-memory` Skillを同梱。AIエージェントが共通のREST基盤を通じてセッションを跨いだ長期記憶(好み、プロジェクトの決定事項、制約、教訓)を記録・更新できます。 +- **人間が最終確認**:`/memory` 画面でAIが記録した記憶を確認・検証・ロック・修正できます。 +- **オープンなエコシステム**:推奨パスはCLI + Skills。既存のMCPクライアント向けに `/memory/mcp`(Streamable HTTP MCP)と `/mcp` エンドポイントも提供。 + +### 3. プロジェクトとタスク +- **プロジェクトで作業をまとめる**:ノートとTODOをプロジェクトに整理。ステータス列間をドラッグできるカンバンボード、優先度、手動並べ替え、期日に対応。 +- **個人専用設計・取り消せる削除**:タスクは単一のオーナーに帰属。削除するとゴミ箱に移動し、復元するか自動消去されるまで保持されます。 + +### 4. タスク管理とリマインダー +- **タスクはプロジェクトに住む**:`/projects` ボード(ステータス列間のドラッグ)、優先度、手動並べ替え、期日により、プロジェクトページが作業スケジュールの一元的な住処になります。 +- **時間の全体像をひと目で把握**:explorerホーム画面はミニ月カレンダーと期限超過・今日のリマインダーを並べて表示。期日がボードの裏に隠れることはありません。 +- **期限超過リマインダー**:期限を過ぎたタスクはアプリ内通知で知らせ、任意でブラウザのWeb Pushにも対応。 + +### 5. チームコラボレーションと3段階の公開範囲 +- **ロールガバナンス**:`owner`、`admin`、`member` の3ロール。管理者はワンタイムアクティベーションリンクでメンバーを招待します(パスワードはメンバー自身が設定し、管理者が平文の認証情報に触れることはありません)。 +- **3段階の公開範囲**: + - 🔒 **プライベート**:作成者本人のみ閲覧可能。 + - 👥 **チーム**:アクティブなチームメンバーに読み取り専用で共有。 + - 🌐 **公開**:期限付き共有リンク経由で匿名の読み取り専用アクセス。 +- **安全なメンバー削除**:メンバーを削除すると、信頼性の高いバックグラウンドクリーンアップが走り、プライベートデータのみを消去してチームと公開のノートは保持します。 +- **リーダーシート**:期限付きの読み取り専用シートを発行できます — ゲスト読者、講座の受講者、クライアントへの納品など。シートは有効期限が切れると自動的に失効します(認証解決時にフェイルクローズ、cron不要)。メンバー画面から管理するか、Personal Access Token で `PUT /api/app/admin/team/reader` を呼び出してメールアドレスから開通できます(`docs/team-mode.md` 参照)。 + +### 6. オフラインファースト&PWA体験 +- **インストール可能なPWA**:macOS、Windows、iOS、Androidのホーム画面に追加でき、ネイティブアプリのような操作感。 +- **信頼性の高いオフライン同期**:下書きは即座にローカル保存。オフライン中の送信やアップロードはキューに積まれ、接続回復時に自動で再生されます。 +- **ライブ音声キャプチャ**:`/capture` にアクセスすると、リアルタイムのストリーミング音声文字起こし(ASR)が利用できます。 + +### 7. Secure Better Authによるアプリケーションセキュリティ +- **Better Auth基盤**:ブラウザは `HttpOnly`・`SameSite=Lax` のクッキーセッション。スクリプト、CLI、MCP向けには失効可能な `memos_pat_` Personal Access Token を提供。 +- **厳格なOrigin保護**:状態を変更するリクエストには厳密なOriginのホワイトリストを強制。Cloudflare Accessも任意の外層防御として併用できます。 + +### 8. Memos互換とシームレスな移行 +- **Memos `/api/v1` 互換**:Memosの主要APIエンドポイント(デフォルトはcamelCase、ヘッダーでレガシーのsnake_case)とOpenAPIスキーマを提供。 +- **サードパーティアプリ対応**:Moe Memosなどのモバイルクライアントから直接接続できます。 +- **双方向のインポート&エクスポート**:Memos / flomoからワンクリックでインポート(競合戦略付き)。生データ一式の完全エクスポートにも対応。 + +--- + +### 9. プラグインシステム:カードはプラグイン +- **5種の内蔵カード**:プレーン、デイリー、チケット、ポストカード、さらにcanvasで描く消印(ポストマーク)デモ。 +- **ストアとキュレーション**:ディレクトリの閲覧、ワンクリックインストール(SHA-256検証済み)、有効化・無効化、並べ替え、デフォルト設定、非表示 — すべてアカウント設定から操作できます。公式ディレクトリは [flaremo.app/plugins](https://flaremo.app/plugins/registry.json)。 +- **自作カードの導入**:管理者はローカルパッケージをインストール可能 — そのインスタンス上にのみ存在し、外部へ送信されることはありません。 +- **制作ツール**:`pnpm plugin:new` で雛形生成、`pnpm plugin:check` は**インストール時にインスタンスが適用するのと同一のルール**で検証、`pnpm plugins:build` でパッケージ化。ドキュメントカードは純粋なJSONレイアウト、サンドボックスカードは自作のHTML/CSS/JSを動かせます。詳しくは[プラグインガイド](./docs/en/plugins.md)。 +- **デフォルトで安全**:カードは不透明オリジンのサンドボックス上で動作し、**ネットワークアクセスは一切ありません**。コミュニティやブランドパックは管理者が有効化するまでオフのままです。 + +## 📊 Cloudflareの無料枠はどれだけ寛容か? + +「無料」=「大きく制限されている」と考えがちですが、テキスト中心の個人ナレッジベースにとって、Cloudflareの無料枠は事実上枯渇しないレベルです: + +| リソース | 無料枠 | 想定容量 | 実用上の寿命 | +| :--- | :--- | :--- | :--- | +| **Cloudflare D1** | **5 GBデータベース** | 約 **250万件** のテキストメモ | 毎日100件書いても **68年** かかる容量 | +| **Cloudflare R2** | **10 GBストレージ** | 写真 約 **5,000〜10,000枚** / 音声 **80時間** | **Egress料金 $0**。公開共有でも帯域請求は発生しない | +| **Cloudflare Workers** | 寛容な無料リクエスト枠 | 世界300以上のエッジロケーション | コールドブートなしで世界中ミリ秒応答 | + +--- + +## 🥊 比較:Cloudflareネイティブ vs 自宅NAS vs 従来型VPS + +| 項目 | Cloudflareネイティブ (FlareMo) | 自宅NAS / ミニPC | 従来型VPS | +| :--- | :--- | :--- | :--- | +| **データの耐久性** | **エンタープライズ級マルチリージョンレプリケーション**、ハードウェア故障リスクゼロ | ドライブ故障や停電で全データ消失の恐れ | 手動のスナップショット・バックアップ運用に依存 | +| **保守** | **ゼロ**:OSパッチなし、Docker composeなし、DB保守なし | OSアップデート、Docker管理、SMART監視、ルーター設定 | カーネル更新、セキュリティパッチ、監視デーモン | +| **アクセス遅延** | **グローバルエッジCDN**、どこからでも100ms未満 | DDNS / frp / Tailscaleトンネルが必要で、自宅上り回線に律速 | 単一クラウドリージョンに依存、越境遅延が大きい | +| **SSLとドメイン** | **HTTPS自動化**とカスタムドメイン紐付け | 証明書の手動発行、リバースプロキシ設定 | Nginx / Caddy設定とLet's Encrypt更新の保守 | +| **費用** | 無料枠で **$0 / 月** | 高額な初期ハードウェア投資+継続的な電気代 | サーバー・帯域の月額/年額料金が継続 | + +--- + +## 🚀 5分でできるクイックデプロイ + +### 方法1:Cloudflareへワンクリックデプロイ + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/realchendahuang/FlareMo) + +リポジトリをGitHubアカウントにクローンし、D1・R2・Queues・Vectorizeを自動でプロビジョニングします。初回デプロイ後、`FLAREMO_PUBLIC_URL` とシークレットを設定してください([docs/en/deploy.md](./docs/en/deploy.md#one-click-deploy-community-supported) 参照)。初回試行で「Github API Limit Exceeded」と表示された場合は、数分待ってから再試行してください。 + +### 方法2:GitHub Action(フォークでのセルフホスト) + +フォーク上でActionsの **Deploy to Cloudflare** を実行すると、リソースのプロビジョニング、Workerの公開、認証シークレットの同期が行われます。pushでは公開されません。詳しくは [docs/en/github-action-deploy.md](./docs/en/github-action-deploy.md) を参照してください。 + +### 方法3:AIエージェントによるデプロイ(推奨) + +ターミナルコマンドを実行できるエージェント(Claude Code、Cursor Agent、Codexなど)に、[docs/en/agent-deploy.md](./docs/en/agent-deploy.md) とともにリポジトリを渡してください: +> 「docs/en/agent-deploy.md に従って、FlareMo を私の Cloudflare アカウントにデプロイしてください。」 + +--- + +### 方法4:手動3ステップデプロイ + +#### 1. Cloudflareリソースの作成 +```bash +pnpm exec wrangler whoami +pnpm exec wrangler d1 create flaremo +pnpm exec wrangler r2 bucket create flaremo-attachments +``` + +または `pnpm provision:remote` を実行する方法もあります:不足しているD1 / R2 / Queue / Vectorizeリソースを作成し、D1の `database_id` を `wrangler.jsonc` に書き込みます。冪等に動作し、既存リソースはスキップされます。 + +#### 2. 設定とシークレットの構成 +```bash +cp wrangler.jsonc.example wrangler.jsonc +``` +生成された `database_id` を記入し、`FLAREMO_PUBLIC_URL` を本番ドメインに設定します。その後、シークレットを設定します: +```bash +pnpm exec wrangler secret put BETTER_AUTH_SECRET --config ./wrangler.jsonc +pnpm exec wrangler secret put FLAREMO_BOOTSTRAP_SECRET --config ./wrangler.jsonc +``` + +#### 3. デプロイ +```bash +pnpm deploy:dry-run +pnpm deploy +``` + +(フルの `pnpm verify` ゲートは、メンテナーが明示的に求めた場合のみ実行されます。) +本番ドメインの `/setup` にアクセスし、`FLAREMO_BOOTSTRAP_SECRET` を入力してOwnerアカウントを初期化します。 + +詳細ガイド:[デプロイガイド](./docs/en/deploy.md) · [GitHub Actionデプロイ](./docs/en/github-action-deploy.md) · [アップデートガイド](./docs/en/update.md)。 + +--- + +## 🧱 アーキテクチャと技術スタック + +```mermaid +flowchart LR + Browser["FlareMo Web UI (React 19 / PWA)"] --> Worker["Cloudflare Worker"] + Clients["Memos Clients / Scripts / MCP"] --> Worker + + Worker --> Auth["Better Auth (Session / PAT)"] + Worker --> D1["Cloudflare D1 (Memos / Relations / Settings)"] + Access["Cloudflare Access (Optional Outer Perimeter)"] -.-> Worker + Worker --> R2["Cloudflare R2 (Attachments & Exports)"] + Worker --> Assets["Workers Static Assets"] +``` + +- **ランタイム**:Cloudflare Workers +- **フロントエンド**:React 19、Vite、TanStack Router、Tailwind CSS 4、Radix UI +- **データベース**:Cloudflare D1、Drizzle ORM +- **ストレージ**:Cloudflare R2 +- **認証**:Better Auth(HttpOnlyクッキーセッション + 失効可能な `memos_pat_`) +- **AIと検索**:Workers AI、Vectorize、SQLite FTS5 +- **プラグイン**:スロットベースの拡張プラットフォーム([標準仕様](./docs/plugin-platform-standard.md)、[ガイド](./docs/en/plugins.md))。パッケージはR2に置かれ、サンドボックス化されたカードはネットワークアクセスなしで動作します + +--- + +## 🌟 Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=realchendahuang/FlareMo&type=Date)](https://star-history.com/#realchendahuang/FlareMo&Date) + +--- + +## 📄 ライセンス + +[GNU AGPL-3.0](./LICENSE) ライセンスでオープンソースとして公開されています。 +Copyright (c) 2026 realchendahuang. diff --git a/README.ko.md b/README.ko.md new file mode 100644 index 00000000..eb0618a4 --- /dev/null +++ b/README.ko.md @@ -0,0 +1,219 @@ +# FlareMo 🔥 + +

+ 서버 제로 · 유지보수 비용 제로 · 24시간 글로벌 엣지 상시 가동 · 완전한 데이터 소유권
+ 개인에게는 조용하고 집중할 수 있는 생각 기록 공간이자 제2의 뇌, 팀에게는 세밀한 권한 관리를 갖춘 공유 지식 베이스. +

+ +

+ English • + 简体中文 • + 日本語 • + Français • + Español • + 한국어 • + Русский • + العربية +

+ +

+ GitHub stars + License + Cloudflare Workers + Memos Compatible + Better Auth + Website +

+ +
+ +| ☀️ 데스크톱 · 라이트 모드 | 🌙 데스크톱 · 다크 모드 | 📱 모바일 · 반응형 | +| :---: | :---: | :---: | +| FlareMo 데스크톱 라이트 모드 | FlareMo 데스크톱 다크 모드 | FlareMo 모바일 화면 | + +실제 운영 화면 스크린샷: 라이트/다크 모드 매끄러운 전환과 모바일 완전 반응형. 화면에 보이는 모든 기능은 실제로 동작하는 백엔드 기능과 연결되어 있습니다. + +
+ +--- + +## 💡 왜 FlareMo인가? + +Flomo와 Memos 같은 도구들은 부담 없는 메모 기록과 방해받지 않는 타임라인이 지닌 큰 가치를 증명해 왔습니다. 하지만 전통적인 노트 환경을 자체 호스팅하려면 VPS 비용을 지불하고, Docker와 PostgreSQL을 설정하고, 자동 백업 스크립트를 작성·관리하면서 디스크나 하드웨어 고장을 걱정해야 했습니다. + +FlareMo는 더 단순한 질문에 답합니다: **무료 Cloudflare 계정 하나만으로, 서버 유지보수 없이 24시간 온라인으로 살아있고 전 세계에서 가속되는 지식 베이스를 만들 수 없을까?** + +- **진정한 서버리스**: 코드와 정적 자산 모두 사용자 바로 근처의 Cloudflare Workers 엣지 노드에서 밀리초 단위 지연으로 실행됩니다. +- **기본 갖춰진 엔터프라이즈급 내구성**: Cloudflare D1이 메모와 메타데이터를 담당하고, Cloudflare R2가 멀티 리전 복제로 미디어 첨부파일을 저장합니다. +- **AI 네이티브 제2의 뇌**: Agent Memory 허브에 CLI와 크로스 에이전트 스킬이 내장되어, AI 에이전트(Claude, Cursor, Codex, ChatGPT, ZCode)가 장기 선호도와 기억 범위를 읽고 갱신할 수 있습니다. MCP 엔드포인트도 함께 제공됩니다. +- **혼자서는 조용히, 함께라면 강력하게**: 기본값은 암호화된 비공개 1인용 안식처입니다. 팀 모드를 켜면 역할과 3단계 공개 범위를 갖춘 협업 공간으로 즉시 바뀝니다. +- **심플하되 단순하지 않게**: 인터페이스는 조용하고 모든 컨트롤은 존재 이유가 분명합니다 — 주의를 끄는 장식도, 빠진 기능도 없습니다. + +--- + +## ✨ 핵심 기능 + +### 1. 즉석 기록 & 영감을 주는 회고 +- **밀리초 만에 기록**: 카드형 타임라인, 태그, Markdown/GFM, 이미지·오디오 첨부 미리보기. +- **번개같은 검색**: SQLite FTS5 전문 인덱싱과 쿼리 연산자 지원 (`has:attachment`, `is:pinned`, `before:YYYY-MM-DD`, `after:YYYY-MM-DD`, `in:timeline|archive|trash`). +- **의미 기반 '찾기' (벡터 검색)**: Workers AI 임베딩과 Vectorize 파생 벡터 인덱스를 결합해 문맥에 맞는 회상을 제공하며, D1 권한을 재검증하고 필요 시 FTS5로 매끄럽게 대체합니다. +- **생각 활성화**: 내장 **데일리 리뷰**(과거의 오늘), **랜덤 워크**(태그·백링크 그래프를 탐험하며 엽서 요약 수집), 관련 노트 추천. +- **버전 이력**: 전체 버전 diff 비교와 클릭 한 번으로 과거 버전 복원. + +### 2. AI 장기 기억 (CLI + Skills) +- **Agent Memory**: `flaremo` CLI와 `flaremo-memory` 스킬을 동봉 — AI 에이전트가 공용 REST 기반을 통해 세션을 넘어 이어지는 장기 기억(선호도, 프로젝트 결정, 제약 조건, 교훈)을 기록·갱신합니다. +- **Human in the loop**: `/memory` 화면에서 AI가 기록한 기억을 검토하고, 확인·잠금·수정할 수 있습니다. +- **열린 생태계**: 권장 경로는 CLI + Skills이며, 기존 MCP 클라이언트를 위해 `/memory/mcp`(Streamable HTTP MCP)와 `/mcp` 엔드포인트도 제공합니다. + +### 3. 프로젝트 & 작업 +- **프로젝트로 작업 모으기**: 관련 메모와 할 일을 프로젝트로 묶어 관리합니다. 상태 열을 드래그로 옮기는 칸반 보드, 우선순위, 수동 정렬, 마감일을 지원합니다. +- **개인 전용, 되돌릴 수 있는 삭제**: 작업은 소유자 한 사람의 소유이며, 삭제 시 휴지통으로 이동해 복원하거나 기간이 지나면 자동으로 영구 삭제됩니다. + +### 4. 작업 관리 & 알림 +- **작업은 프로젝트 안에**: `/projects` 보드(상태 열 간 드래그), 우선순위, 수동 정렬, 마감일 덕분에 프로젝트 페이지가 일정 관리의 단일한 홈이 됩니다. +- **일정이 한눈에**: 탐색기 홈 화면이 미니 월간 캘린더와 기한 초과/오늘 알림을 함께 보여줘, 마감이 보드 뒤에 숨지 않습니다. +- **기한 초과 알림**: 기한이 지난 작업은 앱 내 알림을 띄우고, 브라우저 웹 푸시도 선택적으로 켤 수 있습니다. + +### 5. 팀 협업 & 3단계 공개 범위 +- **역할 거버넌스**: `owner`, `admin`, `member` 역할. 관리자는 1회용 활성화 링크로 멤버를 초대하고, 멤버가 직접 비밀번호를 설정합니다(관리자는 평문 자격 증명을 절대 다루지 않습니다). +- **3단계 공개 범위**: + - 🔒 **비공개**: 작성자 본인만 열람 가능. + - 👥 **팀 공개**: 활성 팀원에게 읽기 전용 공유. + - 🌐 **전체 공개**: 기한이 있는 공유 링크로 익명 읽기 전용 접근. +- **안전한 멤버 제외**: 멤버 제거 시 신뢰할 수 있는 백그라운드 정리가 실행되어 비공개 데이터는 완전히 삭제하고 팀 및 공개 메모는 보존합니다. +- **리더 시트**: 기한이 있는 읽기 전용 시트를 부여할 수 있습니다 — 게스트 독자, 강의 수강생, 고객 납품 등. 시트는 만료 시점에 자동으로 접근이 차단됩니다(자격 해석 시 fail-closed, 크론 불필요). 멤버 페이지에서 관리하거나, Personal Access Token으로 `PUT /api/app/admin/team/reader` 엔드포인트를 호출해 이메일로 개통할 수 있습니다(`docs/team-mode.md` 참조). + +### 6. 오프라인 우선 & PWA 경험 +- **설치형 PWA**: macOS, Windows, iOS, Android 홈 화면에 설치해 네이티브 앱과 같은 사용감을 제공합니다. +- **신뢰할 수 있는 오프라인 동기화**: 초안은 즉시 로컬에 저장됩니다. 오프라인 상태의 제출과 업로드는 큐에 쌓였다가 연결이 복구되면 자동으로 재생됩니다. +- **실시간 음성 기록**: `/capture` 페이지에서 실시간 스트리밍 음성 인식(ASR) 전사를 지원합니다. + +### 7. 강력한 Better Auth 애플리케이션 보안 +- **Better Auth 기반**: `HttpOnly`, `SameSite=Lax` 브라우저 쿠키 세션과, 스크립트·CLI·MCP용으로 폐기 가능한 `memos_pat_` Personal Access Token을 제공합니다. +- **엄격한 Origin 보호**: 상태 변경 요청에는 정확한 Origin 화이트리스트 검증을 강제합니다. Cloudflare Access는 선택적 외부 방어선으로 계속 사용할 수 있습니다. + +### 8. Memos 호환 & 매끄러운 이전 +- **Memos `/api/v1` 호환**: 핵심 Memos API 엔드포인트(기본 camelCase, 헤더로 legacy snake_case)와 OpenAPI 스키마를 제공합니다. +- **서드파티 앱 대응**: Moe Memos 같은 모바일 클라이언트에서 바로 연결할 수 있습니다. +- **양방향 가져오기/내보내기**: Memos / flomo에서 충돌 처리 전략과 함께 원클릭 가져오기, 전체 원본 내보내기 번들을 지원합니다. + +--- + +### 9. 플러그인 시스템: 카드도 플러그인 +- **기본 카드 5장**: Plain, Daily, Ticket, Postcard, 그리고 canvas로 그리는 Postmark 데모. +- **스토어와 큐레이션**: 설정에서 디렉터리 탐색, 원클릭 설치(SHA-256 검증), 활성화/비활성화, 순서 변경, 기본 카드 지정, 숨기기를 모두 처리합니다. 공식 디렉터리는 [flaremo.app/plugins](https://flaremo.app/plugins/registry.json)에 있습니다. +- **직접 업로드**: 관리자는 로컬 패키지를 설치할 수 있습니다 — 해당 인스턴스에만 존재하며 어디로도 전송되지 않습니다. +- **제작 도구**: `pnpm plugin:new`로 스캐폴딩하고, `pnpm plugin:check`는 **인스턴스가 설치 시 강제하는 것과 정확히 동일한 규칙**으로 검증하며, `pnpm plugins:build`로 패키징합니다. document 카드는 순수 JSON 레이아웃, sandbox 카드는 직접 작성한 HTML/CSS/JS를 실행합니다. 자세한 내용은 [플러그인 가이드](./docs/en/plugins.md)를 참조하세요. +- **기본이 안전**: 카드는 불투명 오리진 샌드박스에서 **네트워크 접근 없이** 실행됩니다. 커뮤니티/브랜드 팩은 관리자가 켜기 전까지 꺼져 있습니다. + +## 📊 Cloudflare 무료 제공량은 얼마나 넉넉한가? + +'무료'는 곧 '극심한 제한'이라고 여기는 경우가 많습니다. 하지만 텍스트 중심의 개인 지식 베이스에는 Cloudflare의 무료 할당량이 사실상 무한대입니다: + +| 리소스 | 무료 제공량 | 환산 용량 | 실제 사용 가능 기간 | +| :--- | :--- | :--- | :--- | +| **Cloudflare D1** | **5 GB 데이터베이스** | 텍스트 메모 약 **250만 개** | 매일 100개씩 작성해도 채우는 데 **68년** 소요 | +| **Cloudflare R2** | **10 GB 스토리지** | 사진 약 **5,000–10,000장** / 음성 **80시간** | **아웃바운드 요금 $0**; 공개 공유로도 대역폭 청구가 발생하지 않음 | +| **Cloudflare Workers** | 넉넉한 무료 요청 한도 | 전 세계 300개 이상 엣지 로케이션 | 콜드 부트 없는 전 세계 밀리초 응답 | + +--- + +## 🥊 비교: Cloudflare 네이티브 vs 홈 NAS vs 전통적 VPS + +| 기준 | Cloudflare 네이티브 (FlareMo) | 홈 NAS / 미니 PC | 전통적 VPS | +| :--- | :--- | :--- | :--- | +| **데이터 내구성** | **엔터프라이즈급 멀티 리전 복제**, 하드웨어 고장 위험 제로 | 단일 디스크 고장이나 정전 한 번으로 데이터 전체 손실 가능 | 수동 스냅샷·백업 루틴에 의존 | +| **유지보수** | **제로**: OS 패치도, Docker compose도, DB 관리도 없음 | OS 업데이트, Docker 관리, SMART 디스크 알림, 라우터 설정 | 커널 업그레이드, 보안 패치, 감시 데몬 관리 | +| **접속 지연** | **글로벌 엣지 CDN**, 어디서나 100ms 미만 응답 | DDNS / frp / Tailscale 터널 필요, 가정용 회선 업링크에 제약 | 단일 클라우드 리전에 의존, 국경 간 지연 큼 | +| **SSL & 도메인** | **자동 HTTPS** 및 커스텀 도메인 바인딩 | 수동 인증서 발급, 리버스 프록시 설정 | Nginx / Caddy 설정과 Let's Encrypt 갱신 관리 | +| **비용** | 무료 티어에서 **월 $0** | 높은 초기 하드웨어 비용 + 지속적인 전기료 | 매월/매년 청구되는 서버·대역폭 비용 | + +--- + +## 🚀 5분 만에 배포하기 + +### 방법 1: 원클릭 Cloudflare 배포 + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/realchendahuang/FlareMo) + +저장소를 내 GitHub 계정으로 복제하고 D1, R2, Queues, Vectorize를 자동으로 프로비저닝합니다. 최초 배포 후 `FLAREMO_PUBLIC_URL`과 시크릿을 설정하세요([docs/en/deploy.md](./docs/en/deploy.md#one-click-deploy-community-supported) 참조). 첫 시도에서 "Github API Limit Exceeded"가 표시되면 몇 분 기다렸다가 다시 시도하세요. + +### 방법 2: GitHub Action (셀프 호스팅 포크) + +포크에서 Actions의 **Deploy to Cloudflare**를 실행하면 리소스 프로비저닝, Worker 배포, 인증 시크릿 동기화가 한 번에 이루어집니다. push만으로는 배포되지 않습니다. [docs/en/github-action-deploy.md](./docs/en/github-action-deploy.md)를 참조하세요. + +### 방법 3: AI 에이전트로 배포 (권장) + +터미널 명령을 실행할 수 있는 AI 에이전트(Claude Code, Cursor Agent, Codex 등)에게 저장소와 함께 [docs/en/agent-deploy.md](./docs/en/agent-deploy.md)를 전달하세요: +> "docs/en/agent-deploy.md 문서에 따라 내 Cloudflare 계정에 FlareMo를 배포해 줘." + +--- + +### 방법 4: 3단계 수동 배포 + +#### 1. Cloudflare 리소스 생성 +```bash +pnpm exec wrangler whoami +pnpm exec wrangler d1 create flaremo +pnpm exec wrangler r2 bucket create flaremo-attachments +``` + +또는 `pnpm provision:remote`를 대신 실행하세요: 누락된 D1 / R2 / Queue / Vectorize 리소스를 생성하고 D1 `database_id`를 `wrangler.jsonc`에 대신 기록합니다. 멱등하게 동작하며 기존 리소스는 건너뜁니다. + +#### 2. 설정 및 시크릿 구성 +```bash +cp wrangler.jsonc.example wrangler.jsonc +``` +생성된 `database_id`를 채우고 `FLAREMO_PUBLIC_URL`을 프로덕션 도메인으로 설정하세요. 그다음 시크릿을 설정합니다: +```bash +pnpm exec wrangler secret put BETTER_AUTH_SECRET --config ./wrangler.jsonc +pnpm exec wrangler secret put FLAREMO_BOOTSTRAP_SECRET --config ./wrangler.jsonc +``` + +#### 3. 배포 +```bash +pnpm deploy:dry-run +pnpm deploy +``` + +(전체 `pnpm verify` 게이트는 유지관리자가 명시적으로 요청할 때만 실행됩니다.) +프로덕션 도메인의 `/setup`에 접속해 `FLAREMO_BOOTSTRAP_SECRET`을 입력하고 Owner 계정을 초기화하세요. + +상세 가이드: [배포 가이드](./docs/en/deploy.md) · [GitHub Action 배포](./docs/en/github-action-deploy.md) · [업데이트 가이드](./docs/en/update.md). + +--- + +## 🧱 아키텍처 & 기술 스택 + +```mermaid +flowchart LR + Browser["FlareMo Web UI (React 19 / PWA)"] --> Worker["Cloudflare Worker"] + Clients["Memos Clients / Scripts / MCP"] --> Worker + + Worker --> Auth["Better Auth (Session / PAT)"] + Worker --> D1["Cloudflare D1 (Memos / Relations / Settings)"] + Access["Cloudflare Access (Optional Outer Perimeter)"] -.-> Worker + Worker --> R2["Cloudflare R2 (Attachments & Exports)"] + Worker --> Assets["Workers Static Assets"] +``` + +- **런타임**: Cloudflare Workers +- **프론트엔드**: React 19, Vite, TanStack Router, Tailwind CSS 4, Radix UI +- **데이터베이스**: Cloudflare D1, Drizzle ORM +- **스토리지**: Cloudflare R2 +- **인증**: Better Auth (HttpOnly 쿠키 세션 + 폐기 가능한 `memos_pat_`) +- **AI & 검색**: Workers AI, Vectorize, SQLite FTS5 +- **플러그인**: 슬롯 기반 확장 플랫폼([표준](./docs/plugin-platform-standard.md), [가이드](./docs/en/plugins.md)); 패키지는 R2에 저장되며 샌드박스 카드는 네트워크 접근 없이 실행됩니다 + +--- + +## 🌟 Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=realchendahuang/FlareMo&type=Date)](https://star-history.com/#realchendahuang/FlareMo&Date) + +--- + +## 📄 라이선스 + +[GNU AGPL-3.0](./LICENSE) 라이선스에 따라 오픈소스로 공개됩니다. +Copyright (c) 2026 realchendahuang. diff --git a/README.md b/README.md index c4548233..b4b6396d 100644 --- a/README.md +++ b/README.md @@ -1,379 +1,219 @@ -# FlareMo +# FlareMo 🔥 -**一个免费账号就能 24 小时跑在云端的团队知识库。一个人用,是安静的私人笔记;一个团队用,是带角色权限的共享知识库。Cloudflare 原生部署,自带数据库和对象存储,私密 / 团队 / 公开三档可见性,应用层使用 Better Auth 原生登录,对外保留 Memos 兼容 API;Cloudflare Access 可以作为可选外层防线。** - -[![GitHub stars](https://img.shields.io/github/stars/realchendahuang/FlareMo?style=social)](https://github.com/realchendahuang/FlareMo) -[![license](https://img.shields.io/github/license/realchendahuang/FlareMo)](./LICENSE) -[![Powered by Cloudflare](https://img.shields.io/badge/powered%20by-Cloudflare-F38020?logo=cloudflare&logoColor=white)](https://www.cloudflare.com/) -[![Memos compatible](https://img.shields.io/badge/Memos-compatible-0466c1)](https://github.com/usememos/memos) +

+ Zero Servers · Zero Upkeep · 24/7 Global Edge Uptime · Absolute Data Ownership
+ For individuals: a quiet, focused thought capture space and second brain. For teams: a shared knowledge base with fine-grained roles. +

-[English](./README.en.md) +

+ English • + 简体中文 • + 日本語 • + Français • + Español • + 한국어 • + Русский • + العربية +

-

- FlareMo desktop timeline - FlareMo mobile timeline +

+ GitHub stars + License + Cloudflare Workers + Memos Compatible + Better Auth + Website

-截图展示的是当前已接上后端的时间线、编辑、筛选和移动端导航体验;未实现的能力(如微信输入)不会以占位入口的形式出现在界面里。 +
---- +| ☀️ Desktop · Light Theme | 🌙 Desktop · Dark Theme | 📱 Mobile · Responsive | +| :---: | :---: | :---: | +| FlareMo Desktop Light Mode | FlareMo Desktop Dark Mode | FlareMo Mobile Mode | -## 为什么做这个项目 +Actual live screenshots: seamless light/dark mode switching and full-featured mobile responsiveness. Every feature shown is wired to working backend capabilities. -Flomo 证明了「快速记录 + 安静时间线」这种轻量笔记体验是有价值的。但自部署这类系统通常意味着一台 VPS、一个 Postgres、一堆 Docker 容器、一份每周要维护的备份脚本,以及硬盘哪天坏了数据全没的风险。 +
-FlareMo 想回答另一个问题:**能不能只用一个免费 Cloudflare 账号,不买服务器、不装数据库、不写备份脚本,就拥有一个 24 小时在线、数据不会丢、可以自定义域名、还能被各种工具调用的知识库——一个人用是私人笔记,一个团队用是共享知识库?** +--- -答案是可以。Cloudflare 免费账号就能提供: +## 💡 Why FlareMo? -- **Cloudflare D1:5 GB 数据库** —— 用来存笔记、标签、关系、分享、设置。 -- **Cloudflare R2:10 GB 对象存储** —— 用来存附件、图片、导出包。 -- **Cloudflare Workers:免费请求额度,全球边缘节点** —— 代码和前端都在离你最近的地方跑。 -- **Better Auth 原生认证** —— 一次性设置用户名和密码,浏览器使用安全 cookie session,脚本和 Memos 客户端使用可撤销的 `memos_pat_` PAT;Cloudflare Access 可继续作为外层防线。 -- **Workers Static Assets** —— 前端和 API 由同一个 Worker 提供,一次部署全搞定。 +Tools like Flomo and Memos proved the immense value of low-friction memo capture and a distraction-free timeline. However, self-hosting a traditional note-taking setup typically means paying for a VPS, configuring Docker and PostgreSQL, scripting automated backups, and dreading disk or hardware failures. -整套系统跑在一个 Worker 上。你没有一个「服务器」要照看,只有一份代码和一个免费账号。 +FlareMo answers a simpler question: **Can you get a 24/7 online, resilient, globally accelerated knowledge base with zero server maintenance, using just a free Cloudflare account?** -团队协作也不需要为此升级到付费 SaaS 或多养一台服务器:同一份部署里,管理员在「团队管理」界面添加成员,笔记按「私密 / 团队可见 / 全网公开」三档可见性共享;成员被移出时,其私密数据被完整清理,团队与公开内容保留。所有数据——包括团队成员的数据——都只存在你自己的 Cloudflare 账号里。 +- **Truly Serverless**: Both code and static assets run on Cloudflare Workers edge nodes near you with millisecond latency. +- **Enterprise-grade durability out of the box**: Cloudflare D1 handles notes and metadata; Cloudflare R2 stores media attachments with multi-region replication. +- **AI-Native Second Brain**: Agent Memory hub ships a CLI and a cross-agent skill so AI agents (Claude, Cursor, Codex, ChatGPT, ZCode) can read and update your long-term preferences and memory scopes; MCP endpoints are also available. +- **Quiet for one, powerful for many**: Default is an encrypted, private single-user sanctuary. Enable team mode, and it instantly transforms into a collaborative workspace with roles and three-tier visibility. +- **Minimal, not simplistic**: The interface stays quiet and every control earns its place — nothing decorative shouting for attention, nothing useful missing. --- -## 这点免费额度到底够用多少 - -很多人对 5 GB 数据库 / 10 GB 对象存储没概念,觉得「免费」就是「不够用」。实际上对个人笔记这种写入量极低、纯文本为主的场景,免费额度是溢出的。 +## ✨ Key Features + +### 1. Instant Capture & Inspiring Review +- **Capture in milliseconds**: Card-style timeline, tags, Markdown/GFM, and previews for image and audio attachments. +- **Lightning-fast search**: SQLite FTS5 full-text indexing with query operators (`has:attachment`, `is:pinned`, `before:YYYY-MM-DD`, `after:YYYY-MM-DD`, `in:timeline|archive|trash`). +- **Semantic "Find" (Vector Search)**: Workers AI embeddings paired with Vectorize derived vector indexes for contextual recall; re-verifies permissions against D1 and seamlessly falls back to FTS5. +- **Thought activation**: Built-in **Daily Review** (on this day), **Random Walk** (wandering through tag and backlink graphs with postcard summaries), and related note recommendations. +- **Revision history**: Full version diffs and one-click historical restore. + +### 2. AI Long-term Memory (CLI + Skills) +- **Agent Memory**: Ships `flaremo` CLI and the `flaremo-memory` skill — agents record and update cross-session long-term memory (preferences, project decisions, constraints, lessons) through a shared REST base. +- **Human in the loop**: Review, verify, lock, or correct AI-recorded memories at `/memory`. +- **Open ecosystem**: CLI + Skills are the recommended path; `/memory/mcp` (Streamable HTTP MCP) and `/mcp` endpoints are available for existing MCP clients. + +### 3. Projects & Tasks +- **Group work under projects**: Organize notes and to-dos into projects, with a kanban board (drag between status columns), priorities, manual sort order, and due dates. +- **Personal by design, reversible deletion**: Tasks belong to a single owner; deleting moves them to a recycle bin until restored or automatically purged. + +### 4. Task Management & Reminders +- **Tasks live in Projects**: The `/projects` board (drag between status columns), priorities, manual sort order, and due dates make project pages the single home for scheduling work. +- **Time horizons at a glance**: The explorer home view pairs a mini month calendar with overdue/today reminders, so what's due never hides behind the board. +- **Overdue reminders**: Overdue tasks raise in-app notifications, with optional browser Web Push. + +### 5. Team Collaboration & 3-Tier Visibility +- **Role governance**: `owner`, `admin`, and `member` roles. Admins invite members via one-time activation links (members choose their own passwords; admins never handle plaintext credentials). +- **3-tier visibility**: + - 🔒 **Private**: Only author can view. + - 👥 **Team**: Shared read-only with active team members. + - 🌐 **Public**: Anonymous read-only via time-limited share links. +- **Safe offboarding**: Removing a member triggers reliable background cleanup that purges private data while preserving team and public notes. +- **Reader seats**: Grant a time-boxed read-only seat — guest readers, course cohorts, client delivery. Seats lapse automatically at their expiry (fail-closed at credential resolution, no cron needed). Manage them from the members page, or provision by email through `PUT /api/app/admin/team/reader` with a Personal Access Token (see `docs/team-mode.md`). + +### 6. Offline First & PWA Experience +- **Installable PWA**: Install to macOS, Windows, iOS, or Android home screen with native feel. +- **Reliable offline sync**: Drafts save locally instantly. Offline submissions and uploads queue up and replay automatically when connectivity is restored. +- **Live voice capture**: Access `/capture` for real-time streaming speech-to-text (ASR) transcription. + +### 7. Secure Better Auth Application Security +- **Better Auth powered**: HttpOnly, `SameSite=Lax` browser cookie sessions; revocable `memos_pat_` Personal Access Tokens for scripts, CLI, and MCP. +- **Strict Origin protection**: State-changing requests enforce exact origin whitelisting. Cloudflare Access remains available as an optional outer defensive perimeter. + +### 8. Memos Compatibility & Seamless Migration +- **Memos `/api/v1` compatibility**: Provides core Memos API endpoints (camelCase default, legacy snake_case via header) and OpenAPI schema. +- **Third-party apps ready**: Works directly with mobile clients like Moe Memos. +- **Bi-directional import & export**: One-click import from Memos / flomo with conflict strategies and full raw export bundles. -**5 GB D1 数据库:** +--- -- 一条普通笔记(含标签、时间戳、索引开销)算 2 KB,5 GB 可以存约 **250 万条笔记**。 -- 即使你每天写 100 条,也能写 **68 年**。 -- 实际上绝大多数人一辈子也写不到 5 GB 的纯文本笔记。 +### 9. Plugin System: Cards as Plugins +- **Five built-in cards**: Plain, Daily, Ticket, Postcard, plus a canvas-drawn Postmark demo. +- **Store and curation**: browse directories, one-click install (SHA-256 verified), enable/disable, reorder, set the default, hide — all in account settings. The official directory lives at [flaremo.app/plugins](https://flaremo.app/plugins/registry.json). +- **Upload your own**: admins can install a local package — it exists only on that instance and is never sent anywhere. +- **Authoring tools**: `pnpm plugin:new` scaffolds, `pnpm plugin:check` validates with the **exact rules instances enforce on install**, `pnpm plugins:build` packages. Document cards are pure JSON layouts; sandbox cards run your own HTML/CSS/JS. See the [plugin guide](./docs/en/plugins.md). +- **Safe by default**: cards run in an opaque-origin sandbox with **no network access**; community and brand packs stay off until an admin enables them. -**10 GB R2 对象存储:** +## 📊 How Generous Is Cloudflare's Free Tier? -- 一张手机压缩后约 1–2 MB,10 GB 约可存 **5000–10000 张图片**。 -- 或者约 **80 小时**的中等码率语音备忘录。 -- R2 的出口流量不收费,分享给别人看图也不会产生流量账单。 +Many assume "free" means "severely limited". For text-heavy personal knowledge bases, Cloudflare's free quota is virtually inexhaustible: -换句话说,免费额度不是一个「很快就会撞到」的天花板,而是一个「你大概率永远用不完」的容量。 +| Resource | Free Tier Quota | Equivalent Capacity | Practical Lifespan | +| :--- | :--- | :--- | :--- | +| **Cloudflare D1** | **5 GB database** | ~**2.5 Million** text memos | Writing 100 memos daily would take **68 years** to fill | +| **Cloudflare R2** | **10 GB storage** | ~**5,000–10,000** photos / **80 hours** of voice | **$0 egress fees**; public sharing won't trigger bandwidth bills | +| **Cloudflare Workers** | Generous free request limits | 300+ global edge locations | Millisecond latency worldwide without cold boots | --- -## 为什么放在 Cloudflare 上比放 NAS 更稳 - -自部署还有一个常被低估的成本:**数据的物理安全**。 - -- **NAS / 自建服务器**:数据在你自己家里的硬盘上。硬盘会坏,电源会跳,家里可能漏水、可能被盗、可能搬家时磕碰。哪怕你做 RAID,也只是延缓单盘故障,挡不住整台机器或整个房间出事。异地备份需要你另外搭一套。 -- **Cloudflare**:D1 和 R2 的数据由 Cloudflare 在企业级基础设施上持久化,自带冗余。你不用买第二块硬盘,不用写定时备份脚本,不用关心磁盘 SMART 报警。Cloudflare 不会因为你家停电而丢数据。 +## 🥊 Comparison: Cloudflare Native vs Home NAS vs Traditional VPS -这不代表你不用做导出——FlareMo 支持 Memos 格式导出包,重要数据本地留一份永远是好习惯。但日常的「会不会哪天醒来笔记全没了」这种焦虑,Cloudflare 帮你挡掉了。 - -除此之外,Cloudflare 还附带了自部署很难同时凑齐的几样东西: - -- **全球边缘网络**:你和朋友在不同国家,访问都走最近的节点。 -- **免费 HTTPS 和自定义域名**:绑个域名就行,证书自动续。 -- **不用打洞**:不用 frp、不用 Tailscale、不用公网 IP,分享链接直接发给别人就能开。 -- **零运维**:没有系统要打补丁,没有数据库要升级,没有进程要看门狗。 +| Dimension | Cloudflare Native (FlareMo) | Home NAS / Mini PC | Traditional VPS | +| :--- | :--- | :--- | :--- | +| **Data Durability** | **Enterprise multi-region replication**, zero hardware failure risk | Single drive failure or power outage can cause total data loss | Dependent on manual snapshot & backup routines | +| **Maintenance** | **Zero**: No OS patching, no Docker compose, no DB maintenance | OS updates, Docker upkeep, SMART disk alerts, router configs | Kernel upgrades, security patches, watchdog daemons | +| **Access Latency** | **Global edge CDN**, sub-100ms response anywhere | Requires DDNS / frp / Tailscale tunnels, constrained by home uplink | Dependent on single cloud region; high cross-border latency | +| **SSL & Domains** | **Automated HTTPS** & custom domain bindings | Manual certificate issuance, reverse proxy configuration | Nginx / Caddy config & Let's Encrypt renewal maintenance | +| **Financial Cost** | **$0 / month** on free tier | High upfront hardware cost + ongoing electricity | Ongoing monthly / annual server & bandwidth bills | --- -## 现在能做什么 - -- 快速记录笔记,支持标签和附件。 -- 时间线、归档、回收站。 -- D1 FTS5 全文搜索、标签筛选、活动热力图;默认搜索时间线与归档,搜索支持 `has:attachment`、`is:pinned`、`before:YYYY-MM-DD`、`after:YYYY-MM-DD` 和 `in:timeline|archive|trash`。 -- 语义搜索(「找一找」):Workers AI embedding + Vectorize 派生索引,命中后回 D1 复查权限;provider 或索引缺失时自动降级回 FTS5 关键词搜索,边界见 [docs/semantic-search.md](./docs/semantic-search.md)。 -- 每日回顾(`/review/daily` 那年今日)、随机漫步(`/review/walk` 标签/引用游走 + 明信片总结)和 memo 详情页的相关笔记。 -- 可安装的 PWA;新建笔记草稿自动保存在本机,离线提交(包括附件)进入本机待同步队列,重新联网后按顺序提交。 -- Markdown/GFM、图片与音频附件预览。 -- 记录详情、引用关系、反向链接和历史版本恢复。 -- 可撤销的公开分享链接。 -- 团队模式:`owner` / `admin` / `member` 角色与成员管理。管理员在「团队管理」界面添加成员(姓名 + 邮箱,服务端签发一次性激活链接,成员自设密码,管理员不经手也不可知晓密码)、设置或取消管理员、移出成员并触发可重试的数据清理(私密内容删除,团队与公开内容保留)。 -- 三档可见性:私密(仅作者)、团队可见(有效成员只读)、全网公开(匿名只读);Web、Memos 兼容 API、MCP、附件、搜索与 SSE 共用同一权限矩阵。 -- 支持冲突策略的 Memos 数据导入导出。 -- Memos current camelCase / protobuf-JSON 风格的 `/api/v1` memo、attachment、relation、share、social、auth facade 和 PAT 资源子集;Connect JSON/protobuf/gRPC-Web 还覆盖多用户 UserService 的 webhook CRUD/signing-secret 与 notification list/update/delete(含 comment/mention payload);旧 snake_case wire 通过显式 header 保留。 -- OpenAPI 输出。 -- MCP 端点。 -- Agent Memory:AI 长期记忆中枢,Agent 通过 `/memory/mcp` 读写跨 session 的长期记忆(偏好、决策、约束、教训),`/memory` 界面可查看、确认、锁定、纠正。 -- 中英文界面。 - -前端只保留当前已经接上能力的入口。像微信输入这类还没实现的能力,不会挂在界面里占位置。 +## 🚀 5-Minute Quick Deployment ---- +### Method 1: One-click Deploy to Cloudflare -## 部署:手动或让 Agent 替你做 +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/realchendahuang/FlareMo) -FlareMo 的部署被刻意做得很轻。仓库不再跟踪 `wrangler.jsonc`,部署是手动操作,没有一键按钮或自动部署。两种方式,挑一种就行。 +Clones the repository into your GitHub account and provisions D1, R2, Queues, and Vectorize automatically. After the initial deploy, set `FLAREMO_PUBLIC_URL` and secrets (see [docs/en/deploy.md](./docs/en/deploy.md#one-click-deploy-community-supported)). If the first attempt reports "Github API Limit Exceeded", wait a few minutes and retry. -**方式一:让 AI Agent 替你部署** +### Method 2: GitHub Action (self-hosted fork) -仓库里带了一份 [docs/agent-deploy.md](./docs/agent-deploy.md),是写给 Codex / Claude Code / Cursor 这类 Agent 用的部署 runbook。把仓库交给一个能跑命令的 Agent,它就能复制 `wrangler.jsonc.example`、创建 D1 / R2 资源、填写 `database_id`、跑迁移、部署。你不用记命令,Agent 自己按步骤来。 +On your fork, run **Deploy to Cloudflare** from Actions to provision resources, publish the Worker, and sync auth secrets. Pushes do not publish. See [docs/en/github-action-deploy.md](./docs/en/github-action-deploy.md). -需要让 Agent、Telegram 或其他 IM 渠道直接写入笔记时,参考 [Agent 与 IM 渠道写入](./docs/agent-ingestion.md)。仓库提供一个经过测试的独立 Telegram Worker 示例,不会把渠道密钥或平台逻辑塞进 FlareMo 主 Worker。 +### Method 3: Deploy with an AI Agent (Recommended) -需要让 Agent 读写跨 session 的长期记忆(用户偏好、项目决策、约束、教训)时,参考 [Agent Memory](./docs/agent-memory.md):统一 `/memory/mcp` 端点 + 六个工具,记忆归用户所有、可随时查看和纠正。 +Give the repository to an agent capable of executing terminal commands (e.g. Claude Code, Cursor Agent, Codex) along with [docs/en/agent-deploy.md](./docs/en/agent-deploy.md): +> "Please deploy FlareMo to my Cloudflare account following docs/en/agent-deploy.md." -**方式二:手动部署**(想自己一步步来的话)先创建资源: +--- + +### Method 4: Manual 3-Step Deployment +#### 1. Create Cloudflare Resources ```bash +pnpm exec wrangler whoami pnpm exec wrangler d1 create flaremo pnpm exec wrangler r2 bucket create flaremo-attachments ``` -把 `wrangler.jsonc.example` 复制为 `wrangler.jsonc`(仓库不跟踪 `wrangler.jsonc` 本身),填入 D1 输出的 `database_id`,并把 `FLAREMO_PUBLIC_URL` 设为你的公开访问域名,再执行: +Or run `pnpm provision:remote` instead: it creates the missing D1 / R2 / Queue / Vectorize resources and writes the D1 `database_id` into `wrangler.jsonc` for you. It is idempotent — existing resources are skipped. +#### 2. Configure Settings & Secrets ```bash -pnpm verify -pnpm deploy:dry-run -pnpm deploy +cp wrangler.jsonc.example wrangler.jsonc ``` - -完整部署说明见 [docs/deploy.md](./docs/deploy.md),版本更新见 [docs/update.md](./docs/update.md)。 - -**部署前检查清单** - -- Wrangler 已登录目标 Cloudflare 账号:`pnpm exec wrangler whoami`。 -- `wrangler.jsonc` 里的 D1 binding 是 `DB`,并已填入目标 D1 的 `database_id`。 -- `wrangler.jsonc` 里的 R2 binding 是 `ATTACHMENTS`,目标 bucket 已创建。 -- `pnpm deploy` 会先应用尚未执行的远端 D1 migrations,再发布 Worker。 -- `wrangler.jsonc` 已设置生产 `FLAREMO_PUBLIC_URL`,并通过 Wrangler secret 配置 Better Auth secrets。 -- 已完成一次性 owner bootstrap、原生登录和 PAT 创建验证;如果启用 Cloudflare Access,也已验证外层 policy 与应用层认证同时通过。 -- Cloudflare Access application(可选)已规划好人类访问、Service Token 和公开分享 bypass。 -- 发布前已跑:`pnpm verify` 和 `pnpm deploy:dry-run`。 - ---- - -## 登录:Better Auth 原生认证,Access 可选 - -FlareMo 的应用层认证由 Better Auth 提供。第一次部署时由部署者在生产 HTTPS 的 `/setup` 页面手动输入一次性 bootstrap secret、显示名、邮箱和密码,创建唯一初始 owner;成功后公共 signup 默认关闭。团队协作的主路径是管理员在「团队管理」界面添加成员:服务端签发一次性激活链接,成员自行设置密码;`owner` 也可以在后台开启开放注册(兼容路径,默认关闭),开启后任何人都能通过 `/register` 页或 Memos 兼容客户端的 `signup` 创建普通成员账户。`FLAREMO_SINGLE_USER_EMAIL` 和 `FLAREMO_SINGLE_USER_NAME` 只是既有 `users/owner` domain metadata 的 legacy 变量,不是登录凭据或 bootstrap 输入。团队角色、可见性权限和成员移除语义见 [docs/team-mode.md](./docs/team-mode.md)。 - -- Web 端使用**邮箱 + 密码**登录(`/login`);注册、管理员建号、初始化都以邮箱为登录凭证。Memos 兼容客户端仍使用**用户名 + 密码**登录——用户名由邮箱自动生成(可复制、可在账户页修改),协议上无法用邮箱登录。 - -- 浏览器登录后使用 `HttpOnly`、`SameSite=Lax` cookie session。 -- 脚本、Memos-compatible 客户端和 MCP 使用账户创建的 `memos_pat_` Personal Access Token。 -- PAT 只在创建响应中显示一次,可以列出元数据并撤销;PAT 不能进入账户管理接口。 -- cookie session 的 `POST`、`PATCH`、`DELETE` 等状态变更必须带 `Origin`,并精确匹配 `FLAREMO_PUBLIC_URL` 或 `FLAREMO_TRUSTED_ORIGINS`,否则返回 `403`;PAT 请求可以无 Origin,但如果携带 Origin 也必须匹配同一 allowlist,否则返回 `403`。不使用 wildcard、`Referer` 或 Access headers 替代 Origin。 -- Cloudflare Access 是可选外层。启用时,Access Service Token 只通过外层 policy,仍必须同时提供 Better Auth cookie 或 PAT。 -- 当前没有邮件 provider,因此不依赖邮件验证或邮件找回密码。成员忘记密码时,owner 在后台生成一次性重置链接(`/reset` 页自行设密,管理员不接触明文);owner 自己忘记密码时,用部署时配置的 `FLAREMO_RECOVERY_SECRET` 走 `/recover` 页面恢复。已知当前密码时也可在账户页修改。 -- 公开分享仍使用 FlareMo share token、过期时间和 memo 状态校验,不把公开分享混入私有登录。 - -生产部署前在 `wrangler.jsonc` 填入不带 path/query/hash 的 `FLAREMO_PUBLIC_URL`,并交互式配置 secrets: - +Fill in the generated `database_id` and set `FLAREMO_PUBLIC_URL` to your production domain. Then set secrets: ```bash pnpm exec wrangler secret put BETTER_AUTH_SECRET --config ./wrangler.jsonc pnpm exec wrangler secret put FLAREMO_BOOTSTRAP_SECRET --config ./wrangler.jsonc ``` -不要把真实 secret、初始密码、cookie 或 PAT 写进 `wrangler.jsonc`、文档、Git、日志或聊天。完整 setup、Access 迁移和恢复说明见 [部署文档](./docs/deploy.md)。 - -原生 PAT 访问示例(`FLAREMO_MEMOS_PAT` 只应来自本地安全配置): - -```bash -curl "$FLAREMO_URL/api/v1/memos" \ - -H "Authorization: Bearer $FLAREMO_MEMOS_PAT" -``` - -如果生产仍启用 Access,再附加 Access headers;仅有 Access Service Token 不足以访问私有业务数据: - +#### 3. Deploy ```bash -curl "$FLAREMO_URL/api/v1/memos" \ - -H "CF-Access-Client-Id: $FLAREMO_ACCESS_CLIENT_ID" \ - -H "CF-Access-Client-Secret: $FLAREMO_ACCESS_CLIENT_SECRET" \ - -H "Authorization: Bearer $FLAREMO_MEMOS_PAT" -``` - -旧版 MCP 访问示例(保留给已有 FlareMo 客户端): - -```bash -curl "$FLAREMO_URL/api/v1/mcp" \ - -H "content-type: application/json" \ - -H "CF-Access-Client-Id: $FLAREMO_ACCESS_CLIENT_ID" \ - -H "CF-Access-Client-Secret: $FLAREMO_ACCESS_CLIENT_SECRET" \ - -H "Authorization: Bearer $FLAREMO_MEMOS_PAT" \ - --data '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' -``` - -current Memos 风格的无状态 Streamable HTTP MCP 使用根路径 `/mcp`,支持 `initialize`、`notifications/initialized`、`tools/list` 和 `tools/call`。它仍是 FlareMo 的工具子集,不承诺 SSE、有状态 MCP session 或完整 Memos MCP method surface: - -```bash -curl "$FLAREMO_URL/mcp" \ - -H "content-type: application/json" \ - -H "accept: application/json, text/event-stream" \ - -H "Authorization: Bearer $FLAREMO_MEMOS_PAT" \ - --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26"}}' +pnpm deploy:dry-run +pnpm deploy ``` -建议只 bypass 的公开路径(如果启用 Access): - -- `/share/*` -- `/api/public/shares/*` -- `/assets/*` +(The full `pnpm verify` gate runs only when the maintainer explicitly asks for it.) +Visit your production domain at `/setup` and enter the `FLAREMO_BOOTSTRAP_SECRET` to initialize your Owner account. -分享内容仍由 FlareMo 的 share token、过期时间和 memo 状态校验。当前 `/api/v1` 默认是 current camelCase wire,`/mcp` 是无状态 Streamable HTTP MCP 子集;旧 snake_case API 可通过 `X-FlareMo-Wire: legacy` 或 legacy vendor `Accept` 显式选择。Better Auth-backed auth facade 和 PAT 资源已提供,但 `accessToken` 是 opaque session-backed token,不是 Memos 原生 JWT。当前已有 memo/social 的有限子集,以及 UserService webhook CRUD/signing-secret、notification list/update/delete、comment/mention notification payload 和四类 memo 事件的有界异步 webhook outbox 投递/重试;完整 Memos Server parity、完整 CEL/Connect/SSE、comments/reactions/shortcuts 的完整上游 service/wire parity、完整多用户 notification ACL、webhook 的完整上游事件语义/egress SSRF 防护,以及第三方客户端逐一实测仍未完成,详见 [兼容矩阵](./docs/memos-compatibility.md) 和 [生态实测矩阵](./docs/memos-ecosystem.md)。 +Detailed guides: [Deployment Guide](./docs/en/deploy.md) · [GitHub Action deploy](./docs/en/github-action-deploy.md) · [Update Guide](./docs/en/update.md). --- -## 技术栈 - -- Runtime: Cloudflare Workers -- Web: React, Vite, Tailwind CSS, shadcn/radix primitives -- API: Hono-style Worker routes, Zod contracts, OpenAPI -- Database: Cloudflare D1, Drizzle -- Object storage: Cloudflare R2 -- Auth boundary: Better Auth; optional Cloudflare Access outer layer -- Package manager: pnpm - -D1 是笔记、用户、标签、分享、关系等业务数据的事实源。R2 只放附件、导出包和对象文件。KV、Vectorize、Workers AI、Queues/Cron 只有在对应功能真的进入实现时才接入,不拿来替代 D1。 - -## 架构 +## 🧱 Architecture & Tech Stack ```mermaid flowchart LR - Browser["FlareMo Web UI"] --> Worker["Cloudflare Worker"] - Clients["Memos-compatible clients / scripts / MCP"] --> Worker + Browser["FlareMo Web UI (React 19 / PWA)"] --> Worker["Cloudflare Worker"] + Clients["Memos Clients / Scripts / MCP"] --> Worker - Worker --> Auth["Better Auth: sessions, accounts, PATs"] - Worker --> D1["D1: memos, users, relations, shares, settings"] - Access["Cloudflare Access (optional)"] -. outer policy .-> Worker - Worker --> R2["R2: attachments and exports"] + Worker --> Auth["Better Auth (Session / PAT)"] + Worker --> D1["Cloudflare D1 (Memos / Relations / Settings)"] + Access["Cloudflare Access (Optional Outer Perimeter)"] -.-> Worker + Worker --> R2["Cloudflare R2 (Attachments & Exports)"] Worker --> Assets["Workers Static Assets"] ``` -一个 Worker 同时服务 API 和前端静态资源。D1 保存权威数据,R2 保存附件。Memos 兼容层是 adapter,不是把原版 Memos 服务端搬到 Workers 上跑。 - ---- - -## 定位:AI native 的个人知识管理 - -FlareMo 要做的是 **AI native 的个人知识管理**——一个人用是安静的私人笔记,一个团队用是共享的知识库,并且把语义检索、AI 记忆、Agent 读写这些能力做成产品的原生部分。它和 Memos 的目标不一样,所以**不以「完全兼容」为目标**。 - -Memos 是 FlareMo 选定的生态底座,不是要复刻的对象: - -- **能复用的就复用**:领域模型、资源命名、`/api/v1` 协议、OpenAPI、导入导出和 MCP 方向,以及围绕它们生长的第三方客户端与脚本生态。 -- **上游没有的,自己扩展**:Agent Memory、语义检索与「找一找」、项目与任务、音频文稿阅读等,都是 FlareMo 原生能力,不受上游形态约束。 -- **上游有的,也按需兼容**:只在我们需要、且语义说得通的时候接。`AIService.Transcribe` 这类上游接口在 FlareMo 明确返回 `501`,因为 AI 能力是 FlareMo 自己的赛道。 - -**未来与上游分叉是预期结果,不是意外。** 兼容是「借一个成熟底座、少走弯路」的手段,不是产品目标本身;FlareMo 的方向由自己的需求定义。 - -兼容面有一条工程纪律:`/api/v1/*` 的**既有字段与语义是第三方客户端的契约**,只做加法、不改形状——新增能力优先落在 FlareMo 原生面 `/api/app/*`,或写入可自由扩展的 memo payload。这样既能持续复用 Memos 生态,也不妨碍 FlareMo 往前长。详见 [docs/architecture-notes.md](./docs/architecture-notes.md) 的兼容策略章节。 - ---- - -## Memos 兼容面 - -FlareMo 保留 Memos 风格的核心实体,目标是复用 Memos 的客户端、脚本、导入导出和周边工具,而不是把原版 Memos 的 Go server 搬过来。 - -保留实体:`users/{id}`、`memos/{id}`、`attachments/{id}`、memo payload / property、relations、shares、settings。 - -当前公开 API 子集: - -- `POST /api/v1/memos` -- `GET /api/v1/memos` -- `GET /api/v1/{name=memos/*}` -- `PATCH /api/v1/{memo.name=memos/*}` -- `DELETE /api/v1/{name=memos/*}` -- `GET /api/v1/{name=memos/*}/attachments` -- `PATCH /api/v1/{name=memos/*}/attachments` -- `GET /api/v1/{name=memos/*}/relations` -- `PATCH /api/v1/{name=memos/*}/relations` -- `GET /api/v1/memos/{id}/relation-context` -- `GET /api/v1/memos/{id}/context` -- `GET /api/v1/memos/{id}/revisions` -- `POST /api/v1/memos/{id}/revisions/restore` -- `GET /api/v1/{parent=memos/*}/shares` -- `POST /api/v1/{parent=memos/*}/shares` -- `GET /api/v1/shares/{share_id}` -- `DELETE /api/v1/shares/{share_id}` -- `POST /api/v1/attachments` -- `GET /api/v1/attachments` -- `GET /api/v1/{name=attachments/*}` -- `GET /api/v1/{name=attachments/*}/blob` -- `DELETE /api/v1/{name=attachments/*}` -- `GET /api/v1/export` -- `POST /api/v1/import` -- `GET /openapi.json` -- `POST /api/v1/mcp` - -内部服务不复制原版 Memos 的多数据库抽象、本地文件假设、后台 runner、SSE、完整社交功能和实例管理后台。Memos 兼容范围见 [docs/memos-compatibility.md](./docs/memos-compatibility.md),第三方客户端和工具的实测矩阵见 [docs/memos-ecosystem.md](./docs/memos-ecosystem.md)。 +- **Runtime**: Cloudflare Workers +- **Frontend**: React 19, Vite, TanStack Router, Tailwind CSS 4, Radix UI +- **Database**: Cloudflare D1, Drizzle ORM +- **Storage**: Cloudflare R2 +- **Auth**: Better Auth (HttpOnly cookie session + revocable `memos_pat_`) +- **AI & Search**: Workers AI, Vectorize, SQLite FTS5 +- **Plugins**: slot-based extension platform ([standard](./docs/plugin-platform-standard.md), [guide](./docs/en/plugins.md)); packages live in R2, sandboxed cards run without network access --- -## 本地运行 - -```bash -pnpm install -pnpm migrate:local -pnpm dev -``` - -本地默认地址:`http://localhost:8787` - -`pnpm dev` 会先构建前端,再用 Wrangler 启动 Worker,本地 D1/R2 使用 Wrangler 的本地模拟。 - ---- - -## 项目状态 - -FlareMo 当前已经具备: - -- 可部署的 Cloudflare Worker + Workers Static Assets 一体应用。 -- D1 + Drizzle schema 和 migrations。 -- R2 附件。 -- Memos 兼容 API 子集、导入导出、OpenAPI 和 MCP。 -- Flomo 风格的快速记录和时间线 UI。 -- Better Auth 原生 cookie session、一次性 owner bootstrap 和可撤销 `memos_pat_` PAT。 -- 团队模式:owner/admin/member 角色、团队管理界面、三档可见性权限矩阵和可重试的成员移除清理。 -- Cloudflare Access 可选外层防线,以及公开分享 bypass 的边界说明。 -- Agent 部署 runbook、发版规则、兼容矩阵和开源协作文件。 - -后续方向见 [ROADMAP.md](./ROADMAP.md)。语义搜索的实现边界见 [docs/semantic-search.md](./docs/semantic-search.md)。 - -## 工程化 - -仓库带一个瘦 CI(format / lint / typecheck / 单元测试,约 3 分钟)作为兕底与外部 PR 门禁。发布前由维护者在本地执行完整门禁: - -```bash -pnpm verify -pnpm deploy:dry-run -``` - -自部署仓库自带一个最小权限的更新 workflow(`flaremo-update.yml`)。它只同步正式 Release 并创建升级 PR;合并后由该仓库连接的 Cloudflare Workers Builds 负责部署,不需要 Cloudflare API Token。配置方式见 [docs/update.md](./docs/update.md)。 - -常用维护命令: - -```bash -pnpm format:check -pnpm persistence:check -pnpm screenshots -pnpm backup:drill -pnpm release vX.Y.Z -``` - -`pnpm verify` 会先校验 D1 持久化清单完整性,再跑类型检查、Vitest、生产构建和 Playwright E2E。Memos 兼容面有独立的 Worker contract test,覆盖 DTO shape、附件导入导出和 OpenAPI 路径。截图由 `pnpm screenshots` 从本地 Worker 实例生成,README 里的图片不是设计稿。 - -发版规则见 [docs/release.md](./docs/release.md)。维护手册见 [docs/maintenance.md](./docs/maintenance.md)。贡献说明见 [CONTRIBUTING.md](./CONTRIBUTING.md)。支持入口见 [SUPPORT.md](./SUPPORT.md)。安全策略见 [SECURITY.md](./SECURITY.md)。社区行为准则见 [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)。 - ---- - -## 参考项目 - -- [usememos/memos](https://github.com/usememos/memos):数据模型、资源命名和兼容 API 参考。 -- [blinkospace/blinko](https://github.com/blinkospace/blinko):搜索、附件和编辑体验参考。 -- [XuYouo/MeowNocode](https://github.com/XuYouo/MeowNocode):Cloudflare D1 轻量应用参考。 - -## Star - -喜欢这个项目,可以点个 Star,方便跟进更新。 +## 🌟 Star History [![Star History Chart](https://api.star-history.com/svg?repos=realchendahuang/FlareMo&type=Date)](https://star-history.com/#realchendahuang/FlareMo&Date) -## License - -FlareMo 以 [GNU AGPL-3.0](./LICENSE)(AGPL-3.0-only)授权开源。 - -- 自部署、修改和再分发按 AGPL-3.0 条款执行;以网络服务形式提供修改版本时,需要向该服务的使用者公开对应源码。 -- Copyright (c) 2026 realchendahuang。版权持有者可以在 AGPL-3.0 之外,为 FlareMo 托管服务使用双许可。 -- "FlareMo" 名称与标识不属于 AGPL-3.0 授权范围;fork 与衍生项目不得使用 FlareMo 品牌进行推广或暗示官方背书。 +--- -### 主动语音记录(Capture) +## 📄 License -登录后进入 `/capture`,点击开始,在前台实时识别语音;停止后编辑文字并保存为普通记录,默认仅自己可见并带 `voice` 标签。支持腾讯云或 DashScope 实时 ASR;未配置时不显示导航入口。不保存原始音频,不承诺后台或锁屏录音。配置和真机验收见 [Voice Capture](docs/voice-capture.md)。 +Open-sourced under the [GNU AGPL-3.0](./LICENSE) license. +Copyright (c) 2026 realchendahuang. diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 00000000..a6d85a97 --- /dev/null +++ b/README.ru.md @@ -0,0 +1,219 @@ +# FlareMo 🔥 + +

+ Ноль серверов · Ноль затрат на обслуживание · Глобальная доступность 24/7 · Полный контроль над данными
+ Для личного использования — тихое пространство заметок и второй мозг с ИИ; для команд — общая база знаний с гибким разграничением прав. +

+ +

+ English • + 简体中文 • + 日本語 • + Français • + Español • + 한국어 • + Русский • + العربية +

+ +

+ GitHub stars + License + Cloudflare Workers + Memos Compatible + Better Auth + Website +

+ +
+ +| ☀️ Десктоп · Светлая тема | 🌙 Десктоп · Тёмная тема | 📱 Мобильный интерфейс | +| :---: | :---: | :---: | +| Интерфейс FlareMo в светлой теме | Интерфейс FlareMo в тёмной теме | Мобильный интерфейс FlareMo | + +Реальные снимки работающего приложения: плавное переключение тем и полная мобильная адаптивность. Все отображаемые функции полностью подключены к бэкенду. + +
+ +--- + +## 💡 Почему FlareMo? + +Сервисы вроде Flomo и Memos доказали ценность быстрой фиксации мыслей и ленты заметок без отвлекающих факторов. Однако самостоятельный хостинг таких решений обычно требует аренды VPS, настройки Docker и PostgreSQL, написания скриптов бэкапа и постоянного страха перед поломкой жесткого диска. + +FlareMo отвечает на более простой вопрос: **Можно ли получить отказоустойчивую, глобально ускоренную базу знаний, доступную 24/7 без обслуживания серверов, используя лишь бесплатный аккаунт Cloudflare?** + +- **Истинный Serverless**: И код, и статические ресурсы исполняются на пограничных узлах Cloudflare Workers рядом с вами, с откликом в считанные миллисекунды. +- **Корпоративная надежность из коробки**: Cloudflare D1 хранит заметки и метаданные, а Cloudflare R2 с мультирегиональной репликацией надежно держит медиавложения. +- **AI-Native второй мозг**: хаб «Agent Memory» поставляется с CLI и кросс-агентным скиллом, позволяя ИИ-агентам (Claude, Cursor, Codex, ChatGPT, ZCode) читать и обновлять ваши долгосрочные предпочтения и области памяти; эндпоинты MCP также доступны. +- **Тихо для одного, мощно для многих**: По умолчанию это зашифрованное приватное пространство для одного пользователя. Включите командный режим — и оно мгновенно превращается в совместное рабочее пространство с ролями и тремя уровнями видимости. +- **Минимализм, не примитивизм**: Интерфейс остается тихим, и каждый элемент управления заслуживает своего места — ничего кричащего и лишнего, ничего полезного и недостающего. + +--- + +## ✨ Ключевые возможности + +### 1. Мгновенная фиксация и вдохновляющие обзоры +- **Запись за миллисекунды**: Хронологическая лента в виде карточек, теги, Markdown/GFM, предпросмотр изображений и аудиовложений. +- **Молниеносный поиск**: Полнотекстовая индексация SQLite FTS5 с операторами запросов (`has:attachment`, `is:pinned`, `before:YYYY-MM-DD`, `after:YYYY-MM-DD`, `in:timeline|archive|trash`). +- **Семантический «Поиск» (векторный)**: Эмбеддинги Workers AI в паре с производными векторными индексами Vectorize для поиска по смыслу; права повторно проверяются по D1, а при необходимости поиск бесшовно откатывается к FTS5. +- **Активация мыслей**: Встроенные **Ежедневный обзор** («В этот день»), **Случайная прогулка** (странствие по графу тегов и обратных ссылок со сводками-открытками) и рекомендации похожих заметок. +- **История версий**: Наглядное сравнение изменений и восстановление любой ревизии в один клик. + +### 2. Долгосрочная память ИИ (CLI + Skills) +- **Agent Memory**: Поставляются CLI `flaremo` и скилл `flaremo-memory` — ИИ-агенты записывают и обновляют долговременную память между сессиями (предпочтения, проектные решения, ограничения, уроки) через общую REST-основу. +- **Контроль человеком**: На странице `/memory` вы можете просматривать, подтверждать, закреплять или исправлять записанные ИИ воспоминания. +- **Открытая экосистема**: Рекомендуемый путь — CLI + Skills; эндпоинты `/memory/mcp` (Streamable HTTP MCP) и `/mcp` остаются доступными для существующих MCP-клиентов. + +### 3. Проекты и задачи +- **Группируйте работу по проектам**: Заметки и дела собираются в проекты: канбан-доска (перетаскивание между колонками статусов), приоритеты, ручной порядок сортировки и сроки. +- **Лично по умолчанию, удаление обратимо**: Задачи принадлежат одному владельцу; удаление мягкое — записи попадают в корзину до восстановления или автоматической очистки. + +### 4. Управление задачами и напоминания +- **Задачи живут в проектах**: Доска `/projects` (перетаскивание между колонками статусов), приоритеты, ручной порядок сортировки и сроки делают страницы проектов единым домом для планирования работы. +- **Горизонты времени одним взглядом**: Главный экран обозревателя сочетает мини-календарь на месяц с напоминаниями о просроченных и сегодняшних задачах — сроки больше не прячутся за доской. +- **Напоминания о просрочке**: Просроченные задачи порождают уведомления в приложении, с опциональными браузерными Web Push-уведомлениями. + +### 5. Командная работа и 3 уровня видимости +- **Ролевое управление**: Роли `owner`, `admin` и `member`. Администраторы приглашают участников через одноразовые ссылки активации (участники сами задают пароли; администраторы никогда не видят учетные данные в открытом виде). +- **3 уровня видимости**: + - 🔒 **Приватно**: Видно только автору. + - 👥 **Команда**: Общий доступ только для чтения активным участникам команды. + - 🌐 **Публично**: Анонимный доступ только для чтения по временным ссылкам. +- **Безопасное удаление участника**: При удалении пользователя запускается надежная фоновая очистка: личные данные физически удаляются, а командные и публичные заметки сохраняются. +- **Места читателей**: Выдайте место с ограниченным сроком и доступом только для чтения — гостевые читатели, слушатели курсов, сдача проекта клиентам. По истечении срока доступ закрывается автоматически (fail-closed при разборе учетных данных, без cron). Управляйте местами на странице участников или выдавайте их по email через `PUT /api/app/admin/team/reader` с персональным токеном доступа (см. `docs/team-mode.md`). + +### 6. Офлайн-первым и возможности PWA +- **Устанавливаемое PWA**: Установка на macOS, Windows, iOS или Android с ощущением нативного приложения. +- **Надежная офлайн-синхронизация**: Черновики мгновенно сохраняются локально. Созданные офлайн заметки и загрузки встают в очередь и автоматически повторяются при восстановлении связи. +- **Живой голосовой ввод**: Страница `/capture` с потоковым распознаванием речи в текст (ASR) в реальном времени. + +### 7. Безопасность приложения на базе Better Auth +- **На движке Better Auth**: Cookie-сессии браузера с `HttpOnly` и `SameSite=Lax`; отзываемые персональные токены доступа `memos_pat_` для скриптов, CLI и MCP. +- **Строгая защита Origin**: Все изменяющие состояние запросы требуют точного совпадения с белым списком origin. Cloudflare Access остается доступным как опциональный внешний периметр обороны. + +### 8. Совместимость с Memos и бесшовная миграция +- **Совместимость с Memos `/api/v1`**: Поддержка ключевых эндпоинтов Memos API (по умолчанию camelCase, legacy snake_case через заголовок) и OpenAPI-схемы. +- **Готовность к сторонним приложениям**: Прямая работа с мобильными клиентами вроде Moe Memos. +- **Двусторонний импорт и экспорт**: Импорт из Memos / flomo в один клик со стратегиями разрешения конфликтов и полными экспортными пакетами исходных данных. + +--- + +### 9. Система плагинов: карточки как плагины +- **Пять встроенных карточек**: Простая, Дневная, Билет, Открытка и демонстрационный Штемпель, нарисованный на canvas. +- **Магазин и курирование**: просмотр каталогов, установка в один клик (с проверкой SHA-256), включение/выключение, изменение порядка, карточка по умолчанию, скрытие — всё в настройках аккаунта. Официальный каталог: [flaremo.app/plugins](https://flaremo.app/plugins/registry.json). +- **Свои пакеты**: администратор может установить локальный пакет — он существует только в этом инстансе и никуда не отправляется. +- **Инструменты автора**: `pnpm plugin:new` создает каркас, `pnpm plugin:check` проверяет по **тем же правилам, которые инстансы применяют при установке**, `pnpm plugins:build` собирает пакет. Document-карточки — чистый JSON-макет; sandbox-карточки исполняют ваш собственный HTML/CSS/JS. Подробнее: [руководство по плагинам](./docs/en/plugins.md). +- **Безопасно по умолчанию**: карточки работают в песочнице с непрозрачным origin и **без доступа к сети**; пакеты сообщества и брендов выключены, пока администратор их не включит. + +## 📊 Насколько щедр бесплатный тариф Cloudflare? + +Многие считают, что «бесплатно» означает «урезано до предела». Для текстовых персональных баз знаний бесплатные квоты Cloudflare практически неисчерпаемы: + +| Ресурс | Бесплатная квота | Эквивалентный объем | Срок реального использования | +| :--- | :--- | :--- | :--- | +| **Cloudflare D1** | **База данных 5 ГБ** | Около **2,5 млн** текстовых заметок | При 100 заметках в день заполнится лишь через **68 лет** | +| **Cloudflare R2** | **Хранилище 10 ГБ** | Около **5 000–10 000 фото** / **80 ч** голосовых записей | **$0 за исходящий трафик**; публичный шеринг не приведет к счетам за трафик | +| **Cloudflare Workers** | Щедрые лимиты бесплатных запросов | 300+ пограничных точек по всему миру | Миллисекундный отклик в любой точке мира без холодных стартов | + +--- + +## 🥊 Сравнение: Cloudflare Native против домашнего NAS и классического VPS + +| Критерий | Cloudflare Native (FlareMo) | Домашний NAS / мини-ПК | Классический VPS | +| :--- | :--- | :--- | :--- | +| **Сохранность данных** | **Корпоративная мультирегиональная репликация**, нулевой риск потери из-за железа | Отказ одного диска или отключение питания могут уничтожить все данные | Зависит от ручных снапшотов и процедур бэкапа | +| **Обслуживание** | **Нулевое**: без патчей ОС, без Docker compose, без обслуживания БД | Обновления ОС, уход за Docker, SMART-алерты дисков, настройка роутера | Обновления ядра, патчи безопасности, watchdog-демоны | +| **Задержка доступа** | **Глобальный пограничный CDN**, отклик меньше 100 мс откуда угодно | Нужны DDNS / frp / туннели Tailscale, ограничено домашним аплинком | Привязка к одному региону облака; высокая межграничная задержка | +| **SSL и домены** | **Автоматический HTTPS** и привязка собственных доменов | Ручная выдача сертификатов, настройка обратного прокси | Настройка Nginx / Caddy и продление Let's Encrypt | +| **Финансовые затраты** | **$0 / месяц** на бесплатном тарифе | Высокие разовые затраты на железо + электричество | Постоянные ежемесячные или годовые счета за сервер и трафик | + +--- + +## 🚀 Быстрое развертывание за 5 минут + +### Способ 1: Развертывание в один клик + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/realchendahuang/FlareMo) + +Клонирует репозиторий в ваш аккаунт GitHub и автоматически создает D1, R2, Queues и Vectorize. После первоначального развертывания задайте `FLAREMO_PUBLIC_URL` и секреты (см. [docs/en/deploy.md](./docs/en/deploy.md#one-click-deploy-community-supported)). Если первая попытка сообщает «Github API Limit Exceeded», подождите несколько минут и повторите. + +### Способ 2: GitHub Action (собственный форк) + +В своем форке запустите **Deploy to Cloudflare** во вкладке Actions, чтобы создать ресурсы, опубликовать Worker и синхронизировать секреты аутентификации. Обычные push ничего не публикуют. См. [docs/en/github-action-deploy.md](./docs/en/github-action-deploy.md). + +### Способ 3: Развертывание с ИИ-агентом (Рекомендуется) + +Передайте репозиторий агенту, умеющему выполнять команды в терминале (например, Claude Code, Cursor Agent, Codex), вместе с [docs/en/agent-deploy.md](./docs/en/agent-deploy.md): +> «Пожалуйста, разверните FlareMo в моем аккаунте Cloudflare согласно docs/en/agent-deploy.md». + +--- + +### Способ 4: Ручное развертывание в 3 шага + +#### 1. Создание ресурсов Cloudflare +```bash +pnpm exec wrangler whoami +pnpm exec wrangler d1 create flaremo +pnpm exec wrangler r2 bucket create flaremo-attachments +``` + +Или запустите вместо этого `pnpm provision:remote`: он создаст недостающие ресурсы D1 / R2 / Queue / Vectorize и сам впишет `database_id` D1 в `wrangler.jsonc`. Операция идемпотентна — существующие ресурсы пропускаются. + +#### 2. Настройка конфигурации и секретов +```bash +cp wrangler.jsonc.example wrangler.jsonc +``` +Укажите сгенерированный `database_id` и задайте `FLAREMO_PUBLIC_URL` равным вашему продакшен-домену. Затем установите секреты: +```bash +pnpm exec wrangler secret put BETTER_AUTH_SECRET --config ./wrangler.jsonc +pnpm exec wrangler secret put FLAREMO_BOOTSTRAP_SECRET --config ./wrangler.jsonc +``` + +#### 3. Публикация +```bash +pnpm deploy:dry-run +pnpm deploy +``` + +(Полный гейт `pnpm verify` выполняется только по явному запросу мейнтейнера.) +Откройте `/setup` на вашем продакшен-домене и введите `FLAREMO_BOOTSTRAP_SECRET`, чтобы инициализировать учетную запись владельца. + +Подробные руководства: [Руководство по развертыванию](./docs/en/deploy.md) · [Развертывание через GitHub Action](./docs/en/github-action-deploy.md) · [Руководство по обновлению](./docs/en/update.md). + +--- + +## 🧱 Архитектура и технологический стек + +```mermaid +flowchart LR + Browser["FlareMo Web UI (React 19 / PWA)"] --> Worker["Cloudflare Worker"] + Clients["Memos Clients / Scripts / MCP"] --> Worker + + Worker --> Auth["Better Auth (Session / PAT)"] + Worker --> D1["Cloudflare D1 (Memos / Relations / Settings)"] + Access["Cloudflare Access (Optional Outer Perimeter)"] -.-> Worker + Worker --> R2["Cloudflare R2 (Attachments & Exports)"] + Worker --> Assets["Workers Static Assets"] +``` + +- **Рантайм**: Cloudflare Workers +- **Фронтенд**: React 19, Vite, TanStack Router, Tailwind CSS 4, Radix UI +- **База данных**: Cloudflare D1, Drizzle ORM +- **Хранилище**: Cloudflare R2 +- **Аутентификация**: Better Auth (cookie-сессия HttpOnly + отзываемый `memos_pat_`) +- **ИИ и поиск**: Workers AI, Vectorize, SQLite FTS5 +- **Плагины**: расширяемая платформа на основе слотов ([стандарт](./docs/plugin-platform-standard.md), [руководство](./docs/en/plugins.md)); пакеты хранятся в R2, карточки в песочнице работают без доступа к сети + +--- + +## 🌟 Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=realchendahuang/FlareMo&type=Date)](https://star-history.com/#realchendahuang/FlareMo&Date) + +--- + +## 📄 Лицензия + +Проект распространяется по лицензии [GNU AGPL-3.0](./LICENSE). +Copyright (c) 2026 realchendahuang. diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 00000000..15960876 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,225 @@ +# FlareMo 🔥 + +

+ 0 服务器 · 0 维护成本 · 24 小时全球边缘在线 · 数据绝对自主掌控
+ 一个人用,是安静专注的私人灵感速记与第二大脑;一个团队用,是具备多级权限的共享知识库。 +

+ +

+ English • + 简体中文 • + 日本語 • + Français • + Español • + 한국어 • + Русский • + العربية +

+ +

+ GitHub stars + License + Cloudflare Workers + Memos Compatible + Better Auth + Website +

+ +
+ +| ☀️ 桌面端 · 浅色明亮 | 🌙 桌面端 · 深色沉浸 | 📱 移动端 · 极简随行 | +| :---: | :---: | :---: | +| FlareMo 桌面端浅色界面 | FlareMo 桌面端深色界面 | FlareMo 移动端界面 | + +真实运行界面:支持深浅主题无缝切换与全功能移动端自适应。所有展示功能均已稳定接入后端,不放未实现的占位按钮。 + +
+ +--- + +## 💡 为什么选择 FlareMo + +Flomo 和 Memos 证明了「快速捕捉 + 流式时间线」这种无压力记录体验的巨大价值。但自部署传统的笔记系统通常意味着:你需要一台按月付费的 VPS、安装并配置 Docker / Postgres、编写定时备份脚本,还要时刻提防硬盘损坏或硬件故障导致的数据损失。 + +FlareMo 回答了一个更简单的问题:**仅凭一个免费的 Cloudflare 账号、零服务器维护,你能拥有一个 7×24 小时在线、有韧性、全球加速的知识库吗?** + +- **真正的 Serverless**:代码与静态前端直接部署在离你最近的 Cloudflare Workers 边缘节点,毫秒级响应。 +- **开箱即用的企业级持久化**:Cloudflare D1 承载笔记与元数据,Cloudflare R2 存储媒体附件,自带多地域副本。 +- **AI 原生第二大脑**:Agent Memory 记忆中枢自带 CLI 与跨 Agent Skill,AI Agent(Claude、Cursor、Codex、ChatGPT、ZCode)可以读取并更新你的长期偏好与记忆范围;同时保留 MCP 端点。 +- **一人安静,多人强大**:默认是私密加密的单人 sanctuary;开启团队模式后,即刻变身为具备角色与三级可见性的协作工作台。 +- **简约不简单**:界面安静,每个控件都各有其位——没有抢眼装饰,也没有缺失的功能。 + +--- + +## ✨ 核心特性 + +### 1. 极速速记与沉浸式回顾 +- **毫秒级捕捉**:卡片式时间线,支持标签、Markdown/GFM,以及图片与音频附件预览。 +- **极速搜索**:SQLite FTS5 全文索引,支持查询操作符(`has:attachment`、`is:pinned`、`before:YYYY-MM-DD`、`after:YYYY-MM-DD`、`in:timeline|archive|trash`)。 +- **语义「找一找」(向量搜索)**:Workers AI embedding 搭配 Vectorize 派生向量索引,按上下文语义召回;结果会对照 D1 二次校验权限,并无缝降级到 FTS5。 +- **灵感激活**:内置**每日回顾**(那年今日)、**随机漫步**(在标签与反向链接图中游走,附明信片式总结)和相关笔记推荐。 +- **版本回溯**:完整的版本差异对比,一键还原历史版本。 + +### 2. AI 长期记忆(CLI + Skills) +- **Agent Memory**:自带 `flaremo` CLI 与 `flaremo-memory` Skill——AI Agent 通过同一 REST 基底记录并更新跨会话的长期记忆(偏好、项目决策、约束、教训)。 +- **人机协同**:在 `/memory` 界面审阅、确认、锁定或纠正 AI 沉淀的记忆。 +- **开放生态**:推荐路径是 CLI + Skills;同时提供 `/memory/mcp`(Streamable HTTP MCP)与 `/mcp` 端点,兼容既有 MCP 客户端。 + +### 3. 项目与任务 +- **按项目归拢工作**:把笔记与待办收进项目统一管理,看板支持跨状态列拖拽,配优先级、手动排序与截止日。 +- **个人私有、可撤销删除**:任务为单人私有资源;删除先进回收站,可随时还原,到期后自动清理。 + +### 4. 任务管理与提醒 +- **任务住在项目里**:`/projects` 看板(跨状态列拖拽)、优先级、手动排序与截止日,让项目页成为排期工作的唯一归宿。 +- **时间维度一目了然**:explorer 首页将迷你月历与逾期/今日提醒并排呈现,到期事项不会藏在看板后面。 +- **逾期提醒**:逾期任务触发站内通知,并可选开启浏览器 Web Push。 + +### 5. 团队协作与三级可见性 +- **角色治理**:`owner`、`admin`、`member` 三种角色。管理员通过一次性激活链接邀请成员(成员自主设置密码;管理员经手不到任何明文凭据)。 +- **三档可见性**: + - 🔒 **私密**:仅作者本人可见。 + - 👥 **团队可见**:对有效团队成员只读共享。 + - 🌐 **全网公开**:通过限时分享链接匿名只读访问。 +- **安全移除**:移除成员会触发可靠的后台清理,清除其私有数据,同时保留团队与公开笔记。 +- **读者席位**:可发放带有效期的只读席位(客座读者、课程学员、客户交付)。席位到期自动失效(凭证解析时 fail-closed,无需定时任务);可在成员页管理,也可用个人访问令牌(PAT)调用 `PUT /api/app/admin/team/reader` 按邮箱开通(见 `docs/team-mode.md`)。 + +### 6. 离线优先与 PWA 体验 +- **可安装 PWA**:一键安装到 macOS、Windows、iOS 或 Android 主屏幕,原生 App 级体验。 +- **可靠的离线同步**:草稿即刻本地保存。离线提交与上传进入待同步队列,恢复网络后自动重放。 +- **实时语音捕捉**:访问 `/capture` 即可获得实时流式语音转文字(ASR)。 + +### 7. 坚固的 Better Auth 应用安全 +- **Better Auth 驱动**:浏览器使用 HttpOnly、`SameSite=Lax` Cookie 会话;脚本、CLI 与 MCP 使用可撤销的 `memos_pat_` 个人访问令牌。 +- **严格的 Origin 防护**:状态变更请求强制精确校验 Origin 白名单。Cloudflare Access 仍可作为可选的外层防线。 + +### 8. Memos 兼容与无缝迁移 +- **Memos `/api/v1` 兼容**:提供核心 Memos API 端点(默认 camelCase,经请求头可切换 legacy snake_case)与 OpenAPI 契约。 +- **第三方客户端开箱即用**:直接兼容 Moe Memos 等移动端客户端。 +- **双向导入导出**:从 Memos / flomo 一键导入(带冲突处理策略),并支持全量原始数据导出包。 + +--- + +### 9. 插件系统:卡片即插件 +- **五张内置卡片**:素白、日签、票根、明信片,以及自绘 canvas 的邮戳演示卡。 +- **商店与管理**:浏览目录、一键安装(SHA-256 校验)、启用/停用、排序、设默认、隐藏——全部在账户设置里完成。官方目录见 [flaremo.app/plugins](https://flaremo.app/plugins/registry.json)。 +- **可上传**:管理员可安装本地插件包——它只存在于该实例上,永不外传。 +- **可创作**:`pnpm plugin:new` 生成脚手架,`pnpm plugin:check` 用与实例安装时**完全相同的规则**校验,`pnpm plugins:build` 出包。document 卡是纯 JSON 排版,sandbox 卡写你自己的 HTML/CSS/JS。详见[插件指南](./docs/plugins.md)。 +- **默认安全**:卡片跑在不透明源沙箱里,**无任何网络访问**;社区与品牌插件包默认关闭,管理员显式启用才可用。 + +## 📊 Cloudflare 免费额度到底有多慷慨? + +很多人以为「免费」等于「严重缩水」。对以文字为主的个人知识库来说,Cloudflare 的免费配额几乎取之不尽: + +| 资源 | 免费配额 | 对应容量 | 实际使用寿命 | +| :--- | :--- | :--- | :--- | +| **Cloudflare D1** | **5 GB 数据库** | 约 **250 万条** 纯文本笔记 | 每天写 100 条也要 **68 年** 才能写满 | +| **Cloudflare R2** | **10 GB 存储** | 约 **5,000–10,000 张** 照片 / **80 小时** 语音 | **0 出口流量费用**;公开分享不会触发带宽账单 | +| **Cloudflare Workers** | 慷慨的免费请求额度 | 全球 300+ 边缘节点 | 全球毫秒级响应,没有冷启动 | + +--- + +## 🥊 对比:Cloudflare 原生 vs 家用 NAS vs 传统 VPS + +| 维度 | Cloudflare 原生(FlareMo) | 家用 NAS / 小主机 | 传统 VPS | +| :--- | :--- | :--- | :--- | +| **数据持久性** | **企业级多地域副本**,零硬件故障风险 | 单盘损坏或断电即可导致全量数据损失 | 依赖手动快照与备份例程 | +| **维护成本** | **零**:无系统补丁、无 Docker compose、无数据库维护 | 系统更新、Docker 维护、SMART 磁盘告警、路由器配置 | 内核升级、安全补丁、看门狗守护进程 | +| **访问延迟** | **全球边缘 CDN**,任意位置均低于 100ms | 需自建 DDNS / frp / Tailscale 隧道,受家庭上行带宽制约 | 依赖单一云地域,跨境延迟高 | +| **SSL 与域名** | **自动 HTTPS** 与自定义域名绑定 | 手动签发证书、配置反向代理 | Nginx / Caddy 配置与 Let's Encrypt 续期维护 | +| **资金成本** | 免费额度内 **0 元/月** | 硬件前期投入高 + 持续电费 | 持续支付按月/按年的服务器与带宽账单 | + +--- + +## 🚀 5 分钟极速部署 + +### 方式一:一键部署到 Cloudflare + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/realchendahuang/FlareMo) + +按钮流程会把仓库克隆到你的 GitHub 账号,并自动创建 D1、R2、Queues 与 Vectorize 资源。首次部署后,请按 [docs/deploy.md](./docs/deploy.md#一键部署社区支持) 设置 `FLAREMO_PUBLIC_URL` 与密钥。若首次尝试报 "Github API Limit Exceeded",等待几分钟重试即可。 + +### 方式二:GitHub Action(自托管 fork) + +在自己的 fork 上,于 Actions 里手动运行 **Deploy to Cloudflare**:创建 Cloudflare 资源、发布 Worker、同步认证密钥。push 不会自动发布。步骤见 [docs/github-action-deploy.md](./docs/github-action-deploy.md)。 + +### 方式三:让 AI Agent 替你部署(推荐) + +把仓库交给一个能执行终端命令的 AI Agent(如 Claude Code、Cursor Agent、Codex),并附上 [docs/agent-deploy.md](./docs/agent-deploy.md): +> "请按照 docs/agent-deploy.md 的指引,帮我把 FlareMo 部署到我的 Cloudflare 账号。" + +--- + +### 方式四:手动 3 步部署 + +#### 1. 创建 Cloudflare 资源 +```bash +pnpm exec wrangler whoami +pnpm exec wrangler d1 create flaremo +pnpm exec wrangler r2 bucket create flaremo-attachments +``` + +也可以改用 `pnpm provision:remote`:自动创建缺失的 D1 / R2 / Queue / Vectorize 资源,并把 D1 的 `database_id` 写入 `wrangler.jsonc`。它是幂等的——已存在的资源会跳过。 + +#### 2. 配置设置与密钥 +```bash +cp wrangler.jsonc.example wrangler.jsonc +``` +填入生成的 `database_id`,并将 `FLAREMO_PUBLIC_URL` 设置为你的生产域名。然后写入密钥: +```bash +pnpm exec wrangler secret put BETTER_AUTH_SECRET --config ./wrangler.jsonc +pnpm exec wrangler secret put FLAREMO_BOOTSTRAP_SECRET --config ./wrangler.jsonc +``` + +#### 3. 部署 +```bash +pnpm deploy:dry-run +pnpm deploy +``` + +(完整 `pnpm verify` 门禁只在维护者明确要求时运行。) +访问你的生产域名下的 `/setup`,输入 `FLAREMO_BOOTSTRAP_SECRET` 即可初始化 Owner 账号。 + +详细指南:[部署指南](./docs/deploy.md) · [GitHub Action 部署](./docs/github-action-deploy.md) · [更新指南](./docs/update.md)。 + +--- + +## 🧱 架构与技术栈 + +```mermaid +flowchart LR + Browser["FlareMo Web UI (React 19 / PWA)"] --> Worker["Cloudflare Worker"] + Clients["Memos Clients / Scripts / MCP"] --> Worker + + Worker --> Auth["Better Auth (Session / PAT)"] + Worker --> D1["Cloudflare D1 (Memos / Relations / Settings)"] + Access["Cloudflare Access (Optional Outer Perimeter)"] -.-> Worker + Worker --> R2["Cloudflare R2 (Attachments & Exports)"] + Worker --> Assets["Workers Static Assets"] +``` + +- **运行时**:Cloudflare Workers +- **前端**:React 19, Vite, TanStack Router, Tailwind CSS 4, Radix UI +- **数据库**:Cloudflare D1, Drizzle ORM +- **存储**:Cloudflare R2 +- **认证**:Better Auth(HttpOnly Cookie 会话 + 可撤销 `memos_pat_`) +- **AI 与搜索**:Workers AI, Vectorize, SQLite FTS5 +- **插件**:槽位式扩展平台([规范](./docs/plugin-platform-standard.md)、[指南](./docs/plugins.md));插件包存于 R2,沙箱卡片无网络访问 + +--- + +## 🌟 Star History + +如果你觉得 FlareMo 对你有帮助,欢迎点亮右上角的 Star ⭐️,你的支持是项目持续迭代的最大动力! + +

+ + Star History Chart + +

+ +--- + +## 📄 开源许可证 + +本项目基于 [GNU AGPL-3.0](./LICENSE) 协议开源。允许自托管部署、修改与学习使用;若以网络服务(SaaS)形式对外提供修改版本,须按 AGPL-3.0 条款开源修改后的源代码。 +Copyright (c) 2026 realchendahuang. diff --git a/ROADMAP.md b/ROADMAP.md index d8640049..c99e218e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,6 +4,7 @@ ## 产品主线 +- 设计原则:简约不简单,克制不放肆——不为了炫技加装饰,也不为了省事砍掉该有的能力(视觉与交互细节见 `docs/design-system.md`)。 - 快速记录:打开即写、低干扰输入、可靠草稿。 - 安静时间线:搜索、标签、归档、回收站、活动热力图。 - Memos 兼容:核心 `/api/v1` 子集稳定,当前包含有限 social、UserService webhook/notification 资源生命周期,以及四类 memo 事件的有界异步 webhook outbox 投递/重试;完整 Memos Server parity、完整上游 webhook 事件语义和完整多用户 ACL 仍未完成。导入导出保持可靠。 @@ -26,7 +27,7 @@ - 扩大真实 Memos 客户端兼容矩阵,并补每个已验证客户端的配置示例。 - Agent Memory 后续:自动固化(会话/工作完成后 LLM 提炼)。 -- 回顾触达渠道:✅ 站内通知已落地(cron 幂等写入 `daily_review` 收件箱,铃铛直达 `/review/daily`);Web Push 仍为后续;AI 洞察类派生能力(Workers AI 或外部模型)仍为后续。 +- 回顾触达渠道:✅ 站内通知已落地(cron 幂等写入 `daily_review` 收件箱,铃铛直达 `/review/daily`);✅ Web Push 已落地(VAPID 订阅 + 回顾推送,见 docs/deploy.md 配置小节);AI 洞察类派生能力(Workers AI 或外部模型)仍为后续。 - 增加附件生命周期观测面:清理计数、缺失对象报告和可控重试。 - 扩大浏览器 E2E:Markdown、历史恢复、反向链接、分享撤销和附件预览。 diff --git a/SECURITY.md b/SECURITY.md index 474e0125..1b70d17b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -29,6 +29,7 @@ - Cloudflare Access 是可选的外层防线,生产迁移期建议保留。启用 Access 时,客户端必须同时通过 Access policy 和 FlareMo 应用层认证;Access Service Token 本身不等于 FlareMo 用户 session,也不能单独访问私有 API。 - 公开分享路径可以 bypass Access,但分享内容仍由 FlareMo 的 share token、过期时间和 memo 状态校验。公开分享不接受浏览器 session 或 PAT 作为分享授权的替代物。 - `BETTER_AUTH_SECRET`、`FLAREMO_BOOTSTRAP_SECRET` 和可选的 `FLAREMO_RECOVERY_SECRET` 不得放入 `wrangler.jsonc`、`.dev.vars.example` 的真实值、Git、issue、PR、日志或聊天;生产环境应使用 `wrangler secret put` 或 Cloudflare 控制台配置。 +- 凭据端点(sign-in / sign-up / 忘记密码 / 重置密码)的边缘节流是资源加固而非正确性门禁:限流走可选的 Cloudflare rate-limiting 绑定(`RATE_LIMITER`),绑定缺失或绑定调用失败时一律 fail-open 放行。未配置该绑定的部署(如免费档或未加绑定的自托管实例)没有这层节流,防暴力破解完全依赖 Better Auth 自身的密码强度与认证逻辑;部署者如需该层防护,应在 wrangler 配置中绑定 rate limiting 并按官方文档设置限额。 ### Origin 与凭据类型 diff --git a/apps/site/AGENTS.md b/apps/site/AGENTS.md index cd5aba99..0e067757 100644 --- a/apps/site/AGENTS.md +++ b/apps/site/AGENTS.md @@ -27,11 +27,11 @@ pnpm --filter @flaremo/site build ``` -确保新 slug 出现在 `src/lib/route-meta.ts` 的 `getDocRoutes()`(中文 15 篇 + 英文 4 篇),以及 `scripts/build.mjs` 的 `getAllPaths()`。英文版仅 4 篇已译文档;其余加 `fallbackFromZh: true` 标志,UI 顶部显示 "English coming soon"。 +确保新 slug 出现在 `src/lib/docs-source.generated.ts`(`?raw` import + `ZH_DOCS`/`EN_DOCS` 条目),以及 `scripts/build.mjs` 顶部的 `DOC_SLUGS`(`getAllPaths()` 按它展开预渲染路径)。英文条目加 `fallbackFromZh: true` 时回退中文正文,详情页顶部显示"翻译待补"提示条(`src/pages/docs-detail-page.tsx`)。 ## SEO -- 每个路由的 SEO(title / description / og / twitter / canonical / hreflang / JSON-LD)在 `src/lib/route-meta.ts` 注册。 +- 每个路由的 SEO meta(title / description / og / twitter / canonical / hreflang / JSON-LD)在 `src/content/static-page-meta.ts` 的 `STATIC_PAGE_META` 注册;`src/ssr-render.tsx` 按 pathname 取 meta 交给 `src/lib/html-shell.ts` 的 `buildSeoForPath`(包装 `src/lib/seo.ts` 的 `buildSeoHead`)生成 `` 内容。 - `src/lib/html-shell.ts` 的 `renderHtmlShell` 生成完整 ``;`src/ssr-render.tsx` 用 `createMemoryHistory` + `router.load()` + `renderToString` 渲染 body。 - `scripts/build.mjs` 构建时自动产出 `sitemap.xml` 与 `robots.txt`(后者在 `public/`)。 diff --git a/apps/site/components.json b/apps/site/components.json new file mode 100644 index 00000000..665ff150 --- /dev/null +++ b/apps/site/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles/tokens.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/apps/site/index.html b/apps/site/index.html index 23c8b171..c92da8cd 100644 --- a/apps/site/index.html +++ b/apps/site/index.html @@ -8,6 +8,7 @@ FlareMo +
diff --git a/apps/site/package.json b/apps/site/package.json index 673e3821..05c6fff7 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -2,37 +2,41 @@ "name": "@flaremo/site", "license": "AGPL-3.0-only", "private": true, - "version": "0.20.0", + "version": "0.22.0", "type": "module", "scripts": { "dev": "vite", "build": "node ./scripts/build.mjs", "check": "tsc --noEmit -p tsconfig.app.json --pretty false && tsc --noEmit -p tsconfig.node.json --pretty false", - "format": "prettier --write \"**/*.{ts,tsx,css,md}\"", + "typecheck": "tsc --noEmit -p tsconfig.app.json --pretty false && tsc --noEmit -p tsconfig.node.json --pretty false", "preview": "vite preview", "deploy": "pnpm build && wrangler deploy --config ./wrangler.jsonc", - "deploy:dry-run": "pnpm build && wrangler deploy --config ./wrangler.jsonc --dry-run --outdir=dist" + "deploy:dry-run": "pnpm build && wrangler deploy --config ./wrangler.jsonc --dry-run --outdir=dist", + "test": "vitest run --config ../../vitest.config.ts --passWithNoTests" }, "dependencies": { + "@base-ui/react": "~1.8.0", "@fontsource-variable/geist": "^5.2.9", + "@fontsource-variable/noto-sans-arabic": "^5.3.0", "@tailwindcss/vite": "^4", "@tanstack/react-router": "^1.140.0", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^1.21.0", + "motion": "^13.2.0", "react": "^19.2.6", "react-dom": "^19.2.6", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.6.0", - "tailwindcss": "^4" + "tailwindcss": "^4", + "tw-animate-css": "^1.4.0" }, "devDependencies": { "@types/node": "^24", "@types/react": "^19", "@types/react-dom": "^19", "@vitejs/plugin-react": "^6", - "prettier": "^3.8.3", - "prettier-plugin-tailwindcss": "^0.8.0", "typescript": "^5.9.3", "vite": "^8", "wrangler": "^4.114.0" diff --git a/apps/site/public/docs-assets/flaremo-desktop-dark.png b/apps/site/public/docs-assets/flaremo-desktop-dark.png new file mode 100644 index 00000000..f0f84362 Binary files /dev/null and b/apps/site/public/docs-assets/flaremo-desktop-dark.png differ diff --git a/apps/site/public/docs-assets/flaremo-desktop-light.png b/apps/site/public/docs-assets/flaremo-desktop-light.png new file mode 100644 index 00000000..717cbff6 Binary files /dev/null and b/apps/site/public/docs-assets/flaremo-desktop-light.png differ diff --git a/apps/site/public/docs-assets/flaremo-desktop.png b/apps/site/public/docs-assets/flaremo-desktop.png index cfae3a37..717cbff6 100644 Binary files a/apps/site/public/docs-assets/flaremo-desktop.png and b/apps/site/public/docs-assets/flaremo-desktop.png differ diff --git a/apps/site/public/docs-assets/flaremo-mobile.png b/apps/site/public/docs-assets/flaremo-mobile.png index 53947f66..a8cd985d 100644 Binary files a/apps/site/public/docs-assets/flaremo-mobile.png and b/apps/site/public/docs-assets/flaremo-mobile.png differ diff --git a/apps/site/public/showcase/card-daily.png b/apps/site/public/showcase/card-daily.png new file mode 100644 index 00000000..9f10a671 Binary files /dev/null and b/apps/site/public/showcase/card-daily.png differ diff --git a/apps/site/public/showcase/card-plain.png b/apps/site/public/showcase/card-plain.png new file mode 100644 index 00000000..aceca9f5 Binary files /dev/null and b/apps/site/public/showcase/card-plain.png differ diff --git a/apps/site/public/showcase/card-postcard.png b/apps/site/public/showcase/card-postcard.png new file mode 100644 index 00000000..3da832f9 Binary files /dev/null and b/apps/site/public/showcase/card-postcard.png differ diff --git a/apps/site/public/showcase/card-stamp.png b/apps/site/public/showcase/card-stamp.png new file mode 100644 index 00000000..17b069f0 Binary files /dev/null and b/apps/site/public/showcase/card-stamp.png differ diff --git a/apps/site/public/showcase/card-ticket.png b/apps/site/public/showcase/card-ticket.png new file mode 100644 index 00000000..d4d3b6d3 Binary files /dev/null and b/apps/site/public/showcase/card-ticket.png differ diff --git a/apps/site/scripts/build.mjs b/apps/site/scripts/build.mjs index 9f2ebdfd..41ae67fd 100644 --- a/apps/site/scripts/build.mjs +++ b/apps/site/scripts/build.mjs @@ -11,7 +11,7 @@ */ import { fileURLToPath } from "node:url"; import path from "node:path"; -import { mkdir, writeFile, cp, rm, readFile } from "node:fs/promises"; +import { mkdir, writeFile, cp, rm, readFile, access } from "node:fs/promises"; import { createServer } from "vite"; import { build } from "vite"; @@ -20,34 +20,44 @@ const root = path.resolve(dirname, ".."); const outDir = path.join(root, "dist", "site"); const manifestPath = path.join(outDir, ".vite", "manifest.json"); +const SUPPORTED_LOCALES = ["en", "zh", "ja", "fr", "es", "ko", "ru", "ar"]; + +const DOC_SLUGS = [ + "agent-deploy", + "agent-ingestion", + "agent-memory", + "architecture-notes", + "deploy", + "design-system", + "maintenance", + "memos-compatibility", + "memos-ecosystem", + "plugins", + "product-requirements", + "release", + "semantic-search", + "team-mode", + "tech-stack", + "update", +]; + /** All paths that should be prerendered (with doc slugs expanded). */ function getAllPaths() { - const zhDocs = [ - "agent-deploy", - "agent-ingestion", - "agent-memory", - "architecture-notes", - "deploy", - "design-system", - "maintenance", - "memos-compatibility", - "memos-ecosystem", - "product-requirements", - "release", - "semantic-search", - "tech-stack", - "update", - ]; - const enDocs = ["deploy", "agent-deploy", "memos-compatibility", "update"]; - - return [ + const paths = [ "/", - "/en", "/docs", - "/en/docs", - ...zhDocs.map((slug) => `/docs/${slug}`), - ...enDocs.map((slug) => `/en/docs/${slug}`), + ...DOC_SLUGS.map((slug) => `/docs/${slug}`), ]; + + for (const loc of SUPPORTED_LOCALES) { + paths.push(`/${loc}`); + paths.push(`/${loc}/docs`); + for (const slug of DOC_SLUGS) { + paths.push(`/${loc}/docs/${slug}`); + } + } + + return paths; } function outputPathFor(routePath) { @@ -105,6 +115,30 @@ async function main() { const publicDir = path.join(root, "public"); await cp(publicDir, outDir, { recursive: true }); + // Mirror the plugin store (registry.json + artifacts) so + // https:///plugins/registry.json is the default directory source + // for every instance. Generated by `pnpm plugins:build` beforehand. + const storeDir = path.resolve(root, "..", "..", "plugins", "dist", "store"); + const registrySrc = path.resolve(root, "..", "..", "plugins", "registry.json"); + const exists = async (target) => { + try { + await access(target); + return true; + } catch { + return false; + } + }; + if (await exists(storeDir)) { + const pluginsOut = path.join(outDir, "plugins"); + await cp(storeDir, pluginsOut, { recursive: true }); + await cp(registrySrc, path.join(pluginsOut, "registry.json")); + console.log("[site] mirrored plugin store → /plugins/"); + } else { + console.warn( + "[site] plugins/dist/store missing — run `pnpm plugins:build` first to publish the store", + ); + } + // Emit sitemap.xml const sitemap = buildSitemap(paths); await writeFile(path.join(outDir, "sitemap.xml"), sitemap, "utf8"); @@ -127,14 +161,62 @@ function injectAssets(html, entryJs, entryCss) { } function buildSitemap(paths) { + const LOCALES = [ + { code: "en", prefix: "" }, + { code: "zh-CN", prefix: "/zh" }, + { code: "ja", prefix: "/ja" }, + { code: "fr", prefix: "/fr" }, + { code: "es", prefix: "/es" }, + { code: "ko", prefix: "/ko" }, + { code: "ru", prefix: "/ru" }, + { code: "ar", prefix: "/ar" }, + ]; + + const cleanPath = (p) => { + let s = p.startsWith("/") ? p : `/${p}`; + for (const prefix of [ + "/zh-CN", + "/en", + "/zh", + "/ja", + "/fr", + "/es", + "/ko", + "/ru", + "/ar", + ]) { + if (s === prefix) return "/"; + if (s.startsWith(`${prefix}/`)) return s.slice(prefix.length) || "/"; + } + return s; + }; + + const getUrl = (clean, loc) => { + if (clean === "/") { + return loc.prefix ? `https://flaremo.app${loc.prefix}/` : "https://flaremo.app/"; + } + return `https://flaremo.app${loc.prefix}${clean}`; + }; + const urls = paths .map((p) => { const loc = p === "/" ? "https://flaremo.app/" : `https://flaremo.app${p}`; - return ` ${loc}`; + const base = cleanPath(p); + const alternates = LOCALES.map( + (l) => ` ` + ).join("\n"); + const xDefault = ` `; + + return ` + ${loc} +${alternates} +${xDefault} + `; }) .join("\n"); + return ` - + ${urls} `; diff --git a/apps/site/src/components/card-gallery-section.tsx b/apps/site/src/components/card-gallery-section.tsx new file mode 100644 index 00000000..60601cff --- /dev/null +++ b/apps/site/src/components/card-gallery-section.tsx @@ -0,0 +1,65 @@ +import { ArrowRight } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + CARD_IMAGES, + getCardGalleryContent, +} from "@/content/card-gallery-i18n"; +import { getLocalizedPath, type SupportedLocale } from "@/lib/seo"; + +/** + * Share-card gallery: the five built-in cards at their real 340×420 aspect, + * captioned, with one line explaining the plugin model and a link into the + * plugin docs. Previews are static PNGs captured from the running app, so the + * section stays honest about what the cards actually look like. + */ +export function CardGallerySection({ locale }: { locale: SupportedLocale }) { + const content = getCardGalleryContent(locale); + + return ( +
+
+
+ {content.badge} +

+ {content.heading} +

+

+ {content.subtitle} +

+
+ +
+ +
+ {content.cards.map((card, index) => ( +
+
+ {card.name} +
+
+

{card.name}

+

+ {card.caption} +

+
+
+ ))} +
+
+ ); +} diff --git a/apps/site/src/components/interactive-showcase.tsx b/apps/site/src/components/interactive-showcase.tsx new file mode 100644 index 00000000..ba10d59f --- /dev/null +++ b/apps/site/src/components/interactive-showcase.tsx @@ -0,0 +1,179 @@ +import { useEffect, useState } from "react"; +import { SHOWCASE_I18N, type ShowcaseContent } from "@/content/showcase-i18n"; +import type { SupportedLocale } from "@/lib/seo"; +import { DesktopDevice } from "./showcase/desktop-device"; +import { + createInitialMemos, + type Memo, + type ShowcaseMenu, +} from "./showcase/fixtures"; +import { MobileDevice } from "./showcase/mobile-device"; + +export function InteractiveShowcase({ + locale = "zh", +}: { + locale?: SupportedLocale; +}) { + const showcase: ShowcaseContent = SHOWCASE_I18N[locale] || SHOWCASE_I18N.zh; + + const [memos, setMemos] = useState(() => + createInitialMemos(showcase), + ); + + // 当外部语言切换时,重置并切换默认展示内容 + useEffect(() => { + setMemos(createInitialMemos(showcase)); + setActiveTag(null); + }, [showcase]); + + const [activeMenu, setActiveMenu] = useState("timeline"); + const [activeTag, setActiveTag] = useState(null); + const [timeViewTab, setTimeViewTab] = useState<"trend" | "calendar">("trend"); + const [searchQuery, setSearchQuery] = useState(""); + const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); + const [presetIndex, setPresetIndex] = useState(0); + + // 输入框草稿与同步状态 + const [desktopInput, setDesktopInput] = useState(""); + const [mobileInput, setMobileInput] = useState(""); + const [isSyncing, setIsSyncing] = useState(false); + const [lastSyncedId, setLastSyncedId] = useState(null); + + const totalCount = memos.length + 63; + const tagList = showcase.ui.tags; + + // 发布新笔记 + const publishMemo = (rawText: string, fromMobile = false) => { + let text = rawText.trim(); + if (!text) { + const presets = showcase.presets; + text = presets[presetIndex % presets.length].text; + setPresetIndex((prev) => prev + 1); + } + + setIsSyncing(true); + + const extractedTags = Array.from( + new Set( + (text.match(/#([\w\u4e00-\u9fa5]+)/g) || []).map((t) => + t.replace("#", ""), + ), + ), + ); + const tags = + extractedTags.length > 0 + ? extractedTags + : [showcase.ui.tags[2]?.name || "ideas"]; + + const matchedPreset = showcase.presets.find((p) => p.text === text); + const title = + matchedPreset?.title ?? + (text.length > 18 ? `${text.slice(0, 16)}...` : text); + const quote = matchedPreset?.quote; + const cleanContent = text.replace(/#([\w\u4e00-\u9fa5]+)/g, "").trim(); + + const newId = `memo-${Date.now()}`; + const nextOrder = memos.length > 0 ? memos[0].orderNumber + 1 : 66; + + setTimeout(() => { + const newMemo: Memo = { + id: newId, + orderNumber: nextOrder, + timeLabel: showcase.ui.justNow, + title, + content: cleanContent || text, + quote, + tags, + isNew: true, + }; + + setMemos((prev) => [newMemo, ...prev]); + setLastSyncedId(newId); + if (fromMobile) setMobileInput(""); + else setDesktopInput(""); + setIsSyncing(false); + }, 350); + }; + + const filteredMemos = memos.filter((m) => { + if (activeTag && !m.tags.includes(activeTag)) return false; + if (searchQuery.trim()) { + const q = searchQuery.toLowerCase(); + return ( + m.title.toLowerCase().includes(q) || + m.content.toLowerCase().includes(q) || + m.tags.some((t) => t.toLowerCase().includes(q)) + ); + } + return true; + }); + + // 双端共享的菜单 / 标签选择动作(抽屉额外关闭自身,时间线额外清空标签筛选) + const selectMenu = (menu: ShowcaseMenu, closeDrawer = false) => { + setActiveMenu(menu); + if (menu === "timeline") setActiveTag(null); + if (closeDrawer) setMobileDrawerOpen(false); + }; + + const toggleTag = (name: string) => { + setActiveTag(activeTag === name ? null : name); + }; + + return ( +
+ {/* 真实高对比双端硬件模型 */} +
+ {/* ============================================================ + 电脑端设备:高对比 MacBook 视窗底座 + ============================================================ */} + selectMenu(menu)} + onTimeViewTabChange={setTimeViewTab} + onToggleTag={toggleTag} + onSearchChange={setSearchQuery} + onComposerValueChange={setDesktopInput} + onPublish={() => publishMemo(desktopInput, false)} + /> + + {/* ============================================================ + 手机端设备:高质感钛金属 / 极夜黑真实手机硬件模型 + ============================================================ */} + selectMenu(menu, true)} + onToggleTag={(name) => { + toggleTag(name); + setMobileDrawerOpen(false); + }} + onSearchChange={setSearchQuery} + onComposerValueChange={setMobileInput} + onPublish={() => publishMemo(mobileInput, true)} + onOpenDrawer={() => setMobileDrawerOpen(true)} + onCloseDrawer={() => setMobileDrawerOpen(false)} + onClearTag={() => setActiveTag(null)} + /> +
+
+ ); +} diff --git a/apps/site/src/components/locale-switcher.tsx b/apps/site/src/components/locale-switcher.tsx index 15390df8..4be21627 100644 --- a/apps/site/src/components/locale-switcher.tsx +++ b/apps/site/src/components/locale-switcher.tsx @@ -1,40 +1,105 @@ -import { Link } from "@tanstack/react-router"; -import { Globe } from "lucide-react"; -import type { Locale } from "@/lib/seo"; +import { useRouter } from "@tanstack/react-router"; +import { Check, ChevronDown, Globe } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + getLocalizedPath, + LOCALE_LABELS, + type Locale, + normalizeLocale, + SUPPORTED_LOCALES, + type SupportedLocale, +} from "@/lib/seo"; +import { cn } from "@/lib/utils"; + +const LOCALE_SHORT_LABELS: Record = { + zh: "中文", + en: "EN", + ja: "日本語", + fr: "FR", + es: "ES", + ko: "한국어", + ru: "RU", + ar: "العربية", +}; type LocaleSwitcherProps = { locale: Locale; - /** Path under the locale (e.g. "/", "/docs/deploy"). */ + /** Path of the current route (e.g. "/", "/docs", "/docs/deploy"). */ path: string; + className?: string; + short?: boolean; }; -const LABELS: Record = { - "zh-CN": "简体中文", - "en-US": "English", -}; +export function LocaleSwitcher({ + locale, + path, + className, + short = false, +}: LocaleSwitcherProps) { + const router = useRouter(); + const current = normalizeLocale(locale); -const OTHER_LOCALE: Record = { - "zh-CN": "en-US", - "en-US": "zh-CN", -}; + const handleSelect = (next: SupportedLocale) => { + if (next === current) return; + const nextHref = getLocalizedPath(path, next); + try { + void router.navigate({ to: nextHref as string }); + } catch { + window.location.href = nextHref; + } + }; -function localizedHref(path: string, locale: Locale): string { - if (path === "/") { - return locale === "en-US" ? "/en/" : "/"; - } - return locale === "en-US" ? `/en${path}` : path; -} - -export function LocaleSwitcher({ locale, path }: LocaleSwitcherProps) { - const next = OTHER_LOCALE[locale]; return ( - - - {LABELS[next]} - + + + + + {short ? LOCALE_SHORT_LABELS[current] : LOCALE_LABELS[current]} + + + + + + + 语言 / Language + + + {SUPPORTED_LOCALES.map((loc) => { + const isSelected = loc === current; + return ( + handleSelect(loc)} + className="flex items-center justify-between px-2.5 py-1.5 text-xs cursor-pointer rounded-lg hover:bg-wash focus:bg-wash" + > + + {LOCALE_LABELS[loc]} + + {isSelected && ( + + )} + + ); + })} + + ); } diff --git a/apps/site/src/components/motion.tsx b/apps/site/src/components/motion.tsx new file mode 100644 index 00000000..1bb8f0f6 --- /dev/null +++ b/apps/site/src/components/motion.tsx @@ -0,0 +1,202 @@ +import { animate } from "motion"; +import { motion } from "motion/react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; + +/* ============ 动效 token:全站平滑入场与物理弹跳 ============ */ + +/** 张扬档入场缓动(ease-out-quint 系) */ +export const easeOutQuint: [number, number, number, number] = [ + 0.22, 1, 0.36, 1, +]; + +/** 带轻微过冲的 spring(横幅、弹入用) */ +export const springPop = { + type: "spring" as const, + stiffness: 260, + damping: 18, +}; + +/* ============ SSR 安全入场:首帧渲染最终状态,hydration 后再补动画 ============ */ + +/** + * hydration 前渲染普通元素(SSR/首帧内容完整可见,不伤 SEO/LCP); + * 挂载后换回 motion 元素,从 hidden 态动画到 final。换装只发生一帧内, + * 视觉上先看见成稿、随后补一次轻入场,而不是长时间的空白。 + */ +export function useHydrated() { + const [hydrated, setHydrated] = useState(false); + useEffect(() => { + setHydrated(true); + }, []); + return hydrated; +} + +/** 系统「减弱动态」偏好(SSR 安全);为真时所有动效组件直接渲染静态内容 */ +export function useReducedMotionPreference() { + const [reduced, setReduced] = useState(false); + useEffect(() => { + const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); + const update = () => setReduced(mq.matches); + update(); + mq.addEventListener("change", update); + return () => mq.removeEventListener("change", update); + }, []); + return reduced; +} + +const entranceViewport = { once: true, margin: "0px 0px -64px 0px" } as const; + +/* ============ Reveal:区块入场(进入视口一次) ============ */ + +export function Reveal({ + children, + className, + delay = 0, + y = 18, +}: { + children: ReactNode; + className?: string; + delay?: number; + y?: number; +}) { + const hydrated = useHydrated(); + const reduced = useReducedMotionPreference(); + if (!hydrated || reduced) return
{children}
; + return ( + + {children} + + ); +} + +/* ============ RevealGroup + RevealItem:列表/网格错峰入场 ============ */ + +export function RevealGroup({ + children, + className, + stagger = 0.08, +}: { + children: ReactNode; + className?: string; + stagger?: number; +}) { + const hydrated = useHydrated(); + const reduced = useReducedMotionPreference(); + if (!hydrated || reduced) return
{children}
; + return ( + + {children} + + ); +} + +export function RevealItem({ + children, + className, + y = 20, +}: { + children: ReactNode; + className?: string; + y?: number; +}) { + return ( + + {children} + + ); +} + +/** 惊喜元素弹入:spring 过冲(横幅、提示等) */ +export function PopIn({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + const hydrated = useHydrated(); + const reduced = useReducedMotionPreference(); + if (!hydrated || reduced) return
{children}
; + return ( + + {children} + + ); +} + +/* ============ AnimatedNumber:数字滚动(SSR 渲染最终值,挂载后 0 → 目标) ============ */ + +function formatNumber(n: number, locale: string): string { + return new Intl.NumberFormat(locale).format(n); +} + +export function AnimatedNumber({ + value, + className, + prefix = "", + suffix = "", + locale = "en-US", +}: { + value: number; + className?: string; + prefix?: string; + suffix?: string; + locale?: string; +}) { + const reduced = useReducedMotionPreference(); + const [display, setDisplay] = useState(value); + const started = useRef(false); + + useEffect(() => { + if (started.current) return; + started.current = true; + if (reduced) return; + const controls = animate(0, value, { + duration: 1.2, + delay: 0.15, + ease: [0.16, 1, 0.3, 1], // easeOutExpo + onUpdate: (v) => setDisplay(Math.round(v)), + }); + return () => controls.stop(); + }, [value, reduced]); + + return ( + + {prefix} + {formatNumber(display, locale)} + {suffix} + + ); +} diff --git a/apps/site/src/components/root-layout.tsx b/apps/site/src/components/root-layout.tsx index a10aec9d..e9dfb4e9 100644 --- a/apps/site/src/components/root-layout.tsx +++ b/apps/site/src/components/root-layout.tsx @@ -1,19 +1,41 @@ +import { DirectionProvider } from "@base-ui/react/direction-provider"; import { Outlet, useLocation } from "@tanstack/react-router"; +import { useEffect } from "react"; import { SiteFooter } from "@/components/site-footer"; import { SiteNav } from "@/components/site-nav"; -import type { Locale } from "@/lib/seo"; +import { ThemeProvider } from "@/components/theme-provider"; +import { getLocaleFromPath, localeDirection, localeHtmlLang } from "@/lib/seo"; export function RootLayout() { const { pathname } = useLocation(); - const locale: Locale = pathname.startsWith("/en") ? "en-US" : "zh-CN"; + const locale = getLocaleFromPath(pathname); + const dir = localeDirection(locale); + + // / are written at build time by the SSG shell only. + // LocaleSwitcher navigates client-side (no document reload), so switching to + // /ja or /ar would otherwise leave zh's :lang(zh) font scope and ltr on the + // document — exactly the two rules tokens.css keys off . The root + // route component stays mounted across every navigation (locale routes, docs, + // the 404 route), so this effect is the single choke point that also covers + // back/forward. Effects never run during renderToString, so prerendering is + // untouched; the guard keeps it safe under a DOM-less renderer too. + useEffect(() => { + if (typeof document === "undefined") return; + document.documentElement.lang = localeHtmlLang(locale); + document.documentElement.dir = dir; + }, [locale, dir]); return ( -
- -
- -
- -
+ + +
+ +
+ +
+ +
+
+
); } diff --git a/apps/site/src/components/showcase/composer.tsx b/apps/site/src/components/showcase/composer.tsx new file mode 100644 index 00000000..03795156 --- /dev/null +++ b/apps/site/src/components/showcase/composer.tsx @@ -0,0 +1,123 @@ +import { Hash, Image as ImageIcon, List, Send } from "lucide-react"; +import type { Dispatch, SetStateAction } from "react"; +import type { ShowcaseDevice } from "./fixtures"; + +const WRAPPER_CLASS: Record = { + desktop: + "rounded-2xl border border-line/70 bg-surface p-4 shadow-2xs space-y-3", + mobile: + "rounded-2xl border border-line/70 bg-surface p-3 shadow-2xs space-y-2", +}; + +const TEXTAREA_CLASS: Record = { + desktop: + "w-full bg-transparent text-xs sm:text-sm text-ink placeholder:text-fog resize-none focus:outline-none leading-relaxed", + mobile: + "w-full bg-transparent text-xs text-ink placeholder:text-fog resize-none focus:outline-none leading-relaxed", +}; + +const TOOLBAR_CLASS: Record = { + desktop: "flex items-center justify-between pt-1 border-t border-line/40", + mobile: "flex items-center justify-between pt-1", +}; + +const TOOL_GROUP_CLASS: Record = { + desktop: "flex items-center gap-3 text-mist", + mobile: "flex items-center gap-2.5 text-mist", +}; + +const TOOL_BUTTON_CLASS: Record = { + desktop: "cursor-pointer hover:text-ink transition-colors", + mobile: "cursor-pointer hover:text-ink", +}; + +const TOOL_ICON_CLASS: Record = { + desktop: "size-4", + mobile: "size-3.5", +}; + +const SEND_CLASS: Record = { + desktop: + "inline-flex items-center gap-1.5 rounded-full bg-brand-gradient px-4 py-1.5 text-xs font-semibold text-white shadow-xs hover:brightness-105 active:translate-y-px transition-all cursor-pointer disabled:opacity-40", + mobile: + "inline-flex items-center gap-1 rounded-full bg-brand-gradient px-3 py-1 text-xs font-semibold text-white shadow-xs hover:brightness-105 active:translate-y-px transition-all cursor-pointer disabled:opacity-40", +}; + +type ShowcaseComposerProps = { + variant: ShowcaseDevice; + value: string; + onValueChange: Dispatch>; + onSubmit: () => void; + isSyncing: boolean; + placeholder: string; + sendLabel: string; +}; + +/** 桌面 / 手机共用的笔记发送器(尺寸与发送中的外观由 variant 决定) */ +export function ShowcaseComposer({ + variant, + value, + onValueChange, + onSubmit, + isSyncing, + placeholder, + sendLabel, +}: ShowcaseComposerProps) { + const isMobile = variant === "mobile"; + + return ( +
+