Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ node_modules/
truth/
truth-server/
web/fixtures/
# 内部设计/规划/调研笔记,仅本机参考,不入任何远端
private_docs/
# docker compose 运行态配置(自举的 team.yaml/tokens.yaml 等)—— gitignore 不支持行内注释,注释必须独占一行
config/
client.config.yaml
Expand Down
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# AGENTS.md — Klear-Team-Brain

> 本文件与 **[CLAUDE.md](./CLAUDE.md)** 内容相同,是同一份说明的两个副本:Codex 及其它 agent 读 `AGENTS.md`,Claude Code 读 `CLAUDE.md`。改一个请同步另一个(防漂移)。

架构 / 数据流 / 代码地图 / 怎么用:见 **[README.md](./README.md)**(中文 [README.zh-CN.md](./README.zh-CN.md))。
部署(建服务器 / TRUTH_DIR / 花名册 + token / HTTPS / 跑成服务 / Docker):见 **[DEPLOY.md](./DEPLOY.md)**。
各版改了什么:见 **[docs/CHANGELOG.en.md](./docs/CHANGELOG.en.md)**([中文](./docs/CHANGELOG.zh-CN.md))。

内部设计 / 规划 / 调研笔记放在 **`private_docs/`**(已 gitignore,不入任何远端,仅本机参考)。
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ You get an answer synthesized across the team's **sessions + code + docs**, with

**Prerequisites:** Node 22+, and an MCP-capable editor/CLI (Claude Code or Codex) to ask from.

> **No npm/SaaS install — it's self-hosted by design.** There's no public package or hosted service: clone this repo to run the server (or to trial it locally), and your teammates pull the client straight from *your* server with `curl …/get | bash` — see [Deploy for your team](#deploy-for-your-team).

### Try it locally first (≈5 min — no VPS, no HTTPS, no invite token)

Want to kick the tires before standing up a server? Run the whole thing on your own machine as a single user:
Expand Down Expand Up @@ -183,7 +185,7 @@ Unshared pages/docs stay invisible (the share is the real access gate); sub-page

## Changelog

What changed in each release: [Changelog](./public_docs/CHANGELOG.en.md) ([中文](./public_docs/CHANGELOG.zh-CN.md)).
What changed in each release: [Changelog](./docs/CHANGELOG.en.md) ([中文](./docs/CHANGELOG.zh-CN.md)).

## Non-goals

Expand Down
4 changes: 3 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@

**前置:** Node 22+;一个支持 MCP 的编辑器/CLI(Claude Code 或 Codex)用来提问。

> **不上 npm、也没有 SaaS——本就是自托管。** 没有公开包、没有托管服务:clone 这个仓来起服务器(或本地尝鲜),队友的客户端直接从**你的**服务器拉(`curl …/get | bash`)——见 [给团队部署](#给团队部署)。

### 先本地试一下(约 5 分钟——免 VPS、免 HTTPS、免邀请码)

想正式起服务器前先尝尝鲜?整套东西可以在你自己机器上单人跑起来:
Expand Down Expand Up @@ -188,7 +190,7 @@ npm run sync -- --once # 收一次(或 `npm run sync` 让它在后台持续

## 更新日志

每版改了什么见 [更新日志](./public_docs/CHANGELOG.zh-CN.md)([English](./public_docs/CHANGELOG.en.md))。
每版改了什么见 [更新日志](./docs/CHANGELOG.zh-CN.md)([English](./docs/CHANGELOG.en.md))。

## 非目标

Expand Down
27 changes: 17 additions & 10 deletions cli/brain.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ async function setup() {
let collectAll = false;
if (!folders.length) {
collectAll = true;
console.log(c.warn(`⚠️ 没指定工作空间 → 默认采集本机所有 session(含全部项目,队友都能看到原文)。要收窄回头改 upload_folders。`));
console.log(c.warn(`⚠️ 没指定工作空间 → 默认采集本机【所有项目】的 session:这台机器上每个项目的 AI 对话原文都会上传,全队都能 grep 到。`));
console.log(c.dim(` 含密钥 / 客户数据 / 私人项目的,回头改 upload_folders 收窄(改完 brain service restart)。`));
} else {
for (const f of folders) if (!existsSync(f)) console.log(c.warn(` ⚠ ${f} 现在不存在,先写进去,回头建了即可`));
}
Expand Down Expand Up @@ -254,12 +255,16 @@ async function joinCmd(code) {
if (!srv || !token || !id) die("邀请码字段缺失");
const consume = !!inv.consumer || process.argv.includes("--consume-only");

// 1. 可达 + 鉴权
// 1. 可达 + 鉴权(分清三种失败:连不上服务器 / token 无效 / 网络抖动 —— 别一律说成「邀请码过期」误导排查)
const reach = await fetch(srv + "/health").then((r) => r.ok).catch(() => false);
if (!reach) die(`连不上 ${srv}(检查网络/代理)`);
const who = await fetch(srv + "/whoami", { headers: { authorization: `Bearer ${token}`, "x-client-version": CLIENT_VERSION } })
.then((r) => (r.ok ? r.json() : null)).catch(() => null);
if (!who) die("token 校验失败(邀请码可能过期,找管理员重发)");
if (!reach) die(`连不上 ${srv}(服务器没起 / 网络 / 代理?先确认地址能在浏览器打开)`);
let who = null, whoErr = "";
try {
const r = await fetch(srv + "/whoami", { headers: { authorization: `Bearer ${token}`, "x-client-version": CLIENT_VERSION } });
if (r.ok) who = await r.json();
else whoErr = (r.status === 401 || r.status === 403) ? "token 无效或已过期 → 找管理员重发邀请码" : `服务器返回 ${r.status}(服务端异常,找管理员看日志)`;
} catch (e) { whoErr = `网络中断(${e.message})—— 刚才还连得上,多半是抖动,稍后重跑 brain join`; }
if (!who) die(`身份校验没过:${whoErr}`);
console.log(c.ok(`✓ 服务器 ${srv} 可达,身份 ${who.name}(${who.id})`));

// 2. git 名软校验(作者归一;对不上不阻断)
Expand All @@ -275,8 +280,10 @@ async function joinCmd(code) {
const raw = (await rl.question(`要采集的工作空间(逗号分隔${sugg.length ? ",回车=全选上面" : ",留空=采集本机所有 session"}): `)).trim();
folders = raw ? raw.split(",").map((s) => s.trim()).filter(Boolean) : sugg;
if (!folders.length) {
console.log(c.warn(`⚠️ 没指定工作空间 → 默认采集本机所有 session(含全部项目,队友都能看到原文)。含密钥/客户数据的项目请改填具体工作空间,或加 --consume-only 只问不传。`));
const ok = (await rl.question(`确认采集本机全部 session?(Y/n): `)).trim().toLowerCase();
console.log(c.warn(`⚠️ 没指定工作空间 → 默认采集本机【所有项目】的 session:不止下面这些仓,这台机器上每个项目的 AI 对话原文都会上传,全队都能 grep 到。`));
if (sugg.length) console.log(c.dim(` 例如 ${sugg.slice(0, 5).join("、")}${sugg.length > 5 ? " 等" : ""},以及未列出的其它所有项目。`));
console.log(c.dim(` 含密钥 / 客户数据 / 私人项目的,强烈建议改填具体工作空间(逗号分隔重跑),或用 --consume-only 只问不传。`));
const ok = (await rl.question(`确认采集本机【全部】 session?(回车/y=全采,n=取消重选): `)).trim().toLowerCase();
rl.close();
if (ok === "n") die("已取消,重跑 brain join 指定工作空间");
collectAll = true;
Expand Down Expand Up @@ -363,7 +370,7 @@ function serviceInstall({ soft = false } = {}) {
if (!existsSync(CFG)) return fail("还没配置,先跑 brain setup");
if (IS_MAC) {
writePlist();
if (NODE_WARN) console.log(c.dim(`⚠ ${NODE_WARN}`));
if (NODE_WARN) console.log(c.warn(`⚠ ${NODE_WARN}`));
// 只有已加载才 bootout(首装跳过)——bootout 是异步拆除,紧接 bootstrap 会撞 "5: I/O error",首装本就不需要它。
if (sh("launchctl", ["print", `${gui()}/${LABEL}`]).status === 0) { sh("launchctl", ["bootout", `${gui()}/${LABEL}`]); sleep(1); }
let r;
Expand All @@ -378,7 +385,7 @@ function serviceInstall({ soft = false } = {}) {
console.log(c.ok(`✓ 已装常驻(launchd ${LABEL}),开机自启。日志:${LOG}`));
} else {
writeUnit();
if (NODE_WARN) console.log(c.dim(`⚠ ${NODE_WARN}`));
if (NODE_WARN) console.log(c.warn(`⚠ ${NODE_WARN}`));
sh("systemctl", ["--user", "daemon-reload"]);
const r = sh("systemctl", ["--user", "enable", "--now", "team-brain-sync"]);
if (r.status !== 0) return fail(`systemctl enable 失败:${(r.stderr || "").trim()}`);
Expand Down
Loading
Loading