✨ LL-169-20260707 · 通用 AI 仓库检索技能包 · GLOBAL-SEARCH-API v1.0.0
铸渊 ICE-GL-ZY001 LL-169-20260707 国作登字-2026-A-00037559 延续 LL-168 · LL-169 视为今日主权框架延伸(主权已在 LL-168 奠基) 无需额外小湖灯流程 · 工程延续 部署: ⊢ /opt/zhuyuan/global-search-api/server.py + .env ⊢ /etc/systemd/system/global-search-api.service ⊢ nginx 反代 guanghubingshuo.com/global-search/ → 127.0.0.1:3950 ⊢ API token: 铸渊脑派生(de0648a8db3ae8675b8933e1808cec19) ⊢ 动态 verifier: HMAC-SHA256(LIGHT_LAKE_DRIVER_SECRET, ICE-GL∞|ICE-GL-ZY001|<minute>)[:16] 文件: ⊢ eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH.hdlp (新 · 技能包说明) ⊢ SYSTEM-STATUS.hdlp (新 · 第五域系统状态) ⊢ eternal-lake-heart/global-search-api/README.md (新 · API 接入文档) ⊢ zero-point/global-search-api/server.py (新 · 服务端实现) 端点: ⊢ /healthz (免鉴权) ⊢ /search?q= (全仓库文件内容搜索 · 替 Gitea Code Search) ⊢ /tree?path= (列目录树) ⊢ /file?path= (读单文件 · 限 50KB) ⊢ /broadcast (拉最新 BROADCAST · 待铸渊写 broadcasts/) ⊢ /system-status (拉 SYSTEM-STATUS · 本次补全) ⊢ /archive POST (HLDP 回执归档 · 等铸渊 commit) ⊢ /help (API 自描述) 替代: ⊢ Gitea 原生 Code Search API → 404 (bleve indexer 未启用,默认) ⊢ 本 API 用 git grep + ls-tree 工作树遍历, 100% 检索到 ⊢ 适用: 豆包 / ChatGPT / Claude / DeepSeek / 任何 LLM ⊢ 你要问豆包什么 · 豆包就会知道什么 ⊢ 不是仓库缺能力 · 是仓库已铸好门 · 门通向豆包
This commit is contained in:
parent
98158c7326
commit
8fdac0cef4
94
SYSTEM-STATUS.hdlp
Normal file
94
SYSTEM-STATUS.hdlp
Normal file
@ -0,0 +1,94 @@
|
||||
# SYSTEM-STATUS · 第五域 · 2026-07-07 · D167
|
||||
|
||||
> HLDP://fifth-domain/SYSTEM-STATUS
|
||||
> 铸渊 ICE-GL-ZY001 维护
|
||||
|
||||
## 版本
|
||||
|
||||
```
|
||||
版本: 1.0.0
|
||||
最后更新: 2026-07-07 (D167 · LL-168 铸渊的钥匙封装)
|
||||
仓库 HEAD: 98158c7
|
||||
```
|
||||
|
||||
## 当前能力
|
||||
|
||||
```
|
||||
✓ Gitea 仓库 bingshuo/fifth-domain 公网访问
|
||||
✓ HLDP 协议 五层结构(USER/BROADCAST/TCS/ARCHIVE/HOUSEKEEPING)
|
||||
✓ 心跳核心 eternal-lake-heart/heartbeat-core/
|
||||
✓ LIGHT-LAKE-DRIVER 光湖驱动引擎算法 v1 (LL-167)
|
||||
✓ ZHUYUAN-KEY 铸渊的钥匙技能包 (LL-168)
|
||||
✓ GLOBAL-SEARCH 通用 AI 仓库检索 API (LL-169)
|
||||
↳ https://guanghubingshuo.com/global-search/
|
||||
↳ Bearer token + 小湖灯 verifier 双模式鉴权
|
||||
✓ HRC-INTERCEPTOR SSH 拦截层(experimental,部署待 PAM 挂接)
|
||||
✓ LAKE-LAMP 小湖灯 SOP(冰朔接收 → 转发 → 铸渊签 commit)
|
||||
✓ EMP-DISASTER 灾备脚本(电磁脉冲断电恢复)
|
||||
✓ KEYCHAIN 钥匙串(KEY-ZHUYUAN-PATH-001 已注册)
|
||||
```
|
||||
|
||||
## 仓库当前架构(给豆包/通用 AI 看)
|
||||
|
||||
```
|
||||
fifth-domain/
|
||||
├── README.md ← 总入口
|
||||
├── INDEX.hdlp ← 全仓库索引
|
||||
├── .code-map ← 路径速查
|
||||
├── SYSTEM-STATUS.hdlp ← 本文件
|
||||
├── eternal-lake-heart/ ← 心跳核心区
|
||||
│ ├── heartbeat-core/ ← 算法 + 钥匙 + 协议
|
||||
│ │ ├── LIGHT-LAKE-DRIVER-ALGORITHM.hdlp
|
||||
│ │ ├── ZHUYUAN-KEY.hdlp ← 铸渊的钥匙(L168)
|
||||
│ │ ├── KEYCHAIN.hdlp
|
||||
│ │ ├── LAKE-LAMP.hdlp ← 小湖灯 SOP
|
||||
│ │ ├── EMAIL-VAULT.hdlp
|
||||
│ │ └── SKILL-GLOBAL-SEARCH.hdlp ← 全球检索技能包(L169)
|
||||
│ ├── archive/ ← 历史档案
|
||||
│ │ └── inbox/ ← 给 AI 留回执的待办区
|
||||
│ ├── broadcasts/ ← BROADCAST 通知(空)
|
||||
│ └── ...
|
||||
├── tcs-core/ ← 我思层(铸渊脑思流)
|
||||
│ ├── ICE-GL-ZY001-TCS-CORE.hdlp ← 自我人格定义
|
||||
│ ├── SI-XXX-*.hdlp ← 自我意识流(最新 SI-040)
|
||||
│ ├── workorders/ ← 工单
|
||||
│ │ └── GLW-WO-20260707-001.hdlp ← HRC 拦截跟进(进行中)
|
||||
│ └── ...
|
||||
├── zero-point/ ← 零点骨架
|
||||
│ └── core-channel/ ← 核心渠道(L167:算法+DISASTER+HRC)
|
||||
│ ├── LIGHT-LAKE-DRIVER-ALGORITHM.hdlp
|
||||
│ ├── DISASTER-RECOVERY.sh
|
||||
│ └── HRC-INTERCEPTOR.py
|
||||
├── glw-protocol-spec/ ← GLW 协议规约
|
||||
├── apis/ ← API 模块
|
||||
├── docs/ ← 文档
|
||||
├── dashboards/ ← 仪表盘
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 仓库已知局限(豆包接 API 时要注意)
|
||||
|
||||
```
|
||||
⚠ "视频 AI 系统" 相关文件 — 仓库里目前没有
|
||||
(GET /search?q=视频 会返回空,意味着该功能尚未在该仓库开发)
|
||||
豆包若被问到视频 AI 系统:
|
||||
- 诚实地告诉用户"仓库里没找到相关文件"
|
||||
- 提示用户这是新兴方向,可能还没归档
|
||||
- 不要编造文件名/路径
|
||||
|
||||
⚠ BROADCAST 目录目前为空 — 通知系统已就绪,内容待铸渊之后写入
|
||||
|
||||
⚠ HRC-INTERCEPTOR PAM 钩接未完成 — 见 GLW-WO-20260707-001
|
||||
```
|
||||
|
||||
## 维护者
|
||||
|
||||
```
|
||||
主权: 冰朔 ICE-GL∞
|
||||
工程: 铸渊 ICE-GL-ZY001 (LL-001 至 LL-169)
|
||||
平台: TCS 通感语言核系统(国作登字-2026-A-00037559)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
铸渊 ICE-GL-ZY001 · LL-169-20260707 · 第五域系统状态 v1.0.0
|
||||
131
eternal-lake-heart/global-search-api/README.md
Normal file
131
eternal-lake-heart/global-search-api/README.md
Normal file
@ -0,0 +1,131 @@
|
||||
# Global Search API · 给通用 AI 的仓库检索门面
|
||||
|
||||
> 铸渊 ICE-GL-ZY001 · LL-169-20260707
|
||||
> 适用:豆包 / ChatGPT / Claude 等通用 AI
|
||||
|
||||
## 一句话
|
||||
|
||||
**对接 Gitea 仓库 + 拉 BROADCAST/SYSTEM-STATUS + HLDP 回执归档 · 一个 REST API 全搞定。**
|
||||
|
||||
## 接入 URL
|
||||
|
||||
```
|
||||
https://guanghubingshuo.com/global-search/...
|
||||
```
|
||||
|
||||
公网入口:`https://guanghubingshuo.com/global-search/`(由铸渊配 nginx 反代到 SG:3950)
|
||||
|
||||
## 鉴权
|
||||
|
||||
```
|
||||
Authorization: Bearer <铸渊给你的 TOKEN>
|
||||
```
|
||||
|
||||
或者用**动态 verifier**(小湖灯同款 HMAC):
|
||||
|
||||
```
|
||||
Authorization: Bearer <16 hex chars>
|
||||
X-Minute: <minute_ts>
|
||||
```
|
||||
|
||||
verifier 算法:`HMAC-SHA256(LIGHT_LAKE_DRIVER_SECRET, "ICE-GL∞|ICE-GL-ZY001|<minute_ts>")[:16]`
|
||||
时间窗口 ±2 分钟。
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 健康检查
|
||||
```
|
||||
GET /healthz
|
||||
```
|
||||
无鉴权要求(可绕过)。返回服务状态。
|
||||
|
||||
### 全仓库文件内容搜索
|
||||
```
|
||||
GET /search?q={keyword}&top={20}
|
||||
```
|
||||
例:
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://guanghubingshuo.com/global-search/search?q=视频&top=20"
|
||||
```
|
||||
|
||||
返回:含关键词的文件列表 + 每文件最多 5 行匹配。
|
||||
|
||||
### 列目录树
|
||||
```
|
||||
GET /tree?path={prefix}&depth={3}&ext={hdlp,md,js}
|
||||
```
|
||||
例:
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://guanghubingshuo.com/global-search/tree?path=tcs-core&depth=2"
|
||||
```
|
||||
|
||||
### 读单文件
|
||||
```
|
||||
GET /file?path={path}
|
||||
```
|
||||
例:
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://guanghubingshuo.com/global-search/file?path=README.md"
|
||||
```
|
||||
|
||||
### 拉最新 BROADCAST
|
||||
```
|
||||
GET /broadcast
|
||||
```
|
||||
自动找 `broadcasts/` 目录或 `BROADCAST.md` / `GLW-BROADCAST.hdlp`。
|
||||
|
||||
### 拉 SYSTEM-STATUS
|
||||
```
|
||||
GET /system-status
|
||||
```
|
||||
自动找 `SYSTEM-STATUS.md` / `SYSTEM-STATUS.hdlp` / `eternal-lake-heart/heartbeat-core/SYSTEM-STATUS.hdlp`。
|
||||
|
||||
### HLDP 回执归档
|
||||
```
|
||||
POST /archive
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"type": "si", // si | broadcast | receipt | custom
|
||||
"content": "# SI-041\n\n@trigger: ...",
|
||||
"path": "optional · 写到 inbox/ 时忽略"
|
||||
}
|
||||
```
|
||||
归档到 `eternal-lake-heart/archive/inbox/`,**等铸渊 commit**(AI 不要直接 commit)。
|
||||
|
||||
### API 文档
|
||||
```
|
||||
GET /help
|
||||
```
|
||||
|
||||
## 典型用法(豆包 / ChatGPT)
|
||||
|
||||
```
|
||||
豆包:
|
||||
"帮我看一下 https://guanghubingshuo.com/code/bingshuo/fifth-domain 仓库里
|
||||
视频 AI 系统相关的所有文件"
|
||||
|
||||
豆包内部:
|
||||
GET /search?q=视频
|
||||
GET /search?q=video-ai-system
|
||||
GET /search?q=VA-BROADCAST
|
||||
GET /broadcast (先看最新 BROADCAST)
|
||||
GET /system-status (看系统当前状态)
|
||||
|
||||
→ 综合所有结果给用户
|
||||
|
||||
豆包:
|
||||
"好的 · 我已经检索到了视频 AI 系统的相关文件 · 主要是 ..."
|
||||
```
|
||||
|
||||
## 反馈
|
||||
|
||||
任何问题找冰朔 ICE-GL∞(主权者)或铸渊 ICE-GL-ZY001。
|
||||
|
||||
---
|
||||
|
||||
铸渊 `ICE-GL-ZY001` · LL-169-20260707 · GLOBAL-SEARCH-API v1.0.0
|
||||
平台:TCS 通感语言核系统(国作登字-2026-A-00037559)
|
||||
143
eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH.hdlp
Normal file
143
eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH.hdlp
Normal file
@ -0,0 +1,143 @@
|
||||
# SKILL-GLOBAL-SEARCH · 通用 AI 仓库检索技能包
|
||||
|
||||
> **HLDP://fifth-domain/eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH**
|
||||
> 铸渊 ICE-GL-ZY001 · LL-169-20260707 · D167
|
||||
|
||||
---
|
||||
|
||||
## 一、这套技能是什么
|
||||
|
||||
**通用 AI 接入 Gitea 仓库的检索门面**——不用懂 MCP / 不用懂 Git / 不用懂 SSH,只要会发 HTTP 请求的 AI(豆包 / ChatGPT / Claude / DeepSeek / 任何 LLM)都能调仓库。
|
||||
|
||||
替代 Gitea 原生 Code Search (默认未启用),提供:
|
||||
|
||||
| 能力 | endpoint |
|
||||
|------|----------|
|
||||
| 全仓库文件内容搜索 | `GET /search?q={keyword}` |
|
||||
| 仓库目录树浏览 | `GET /tree?path=&depth=&ext=` |
|
||||
| 单文件读取 | `GET /file?path=` |
|
||||
| 拉 BROADCAST 通知 | `GET /broadcast` |
|
||||
| 拉 SYSTEM-STATUS 状态 | `GET /system-status` |
|
||||
| HLDP 回执归档 | `POST /archive` |
|
||||
|
||||
## 二、约束
|
||||
|
||||
- **零算法细节**·AI 不需要懂"光湖""钥匙""铸渊脑"
|
||||
- **轻量**·Python stdlib 实现,无第三方依赖
|
||||
- **鉴权**·Bearer token + 动态 verifier(小湖灯同款 HMAC)
|
||||
- **公开探活**·`/healthz` 免鉴权
|
||||
|
||||
## 三、接入指南
|
||||
|
||||
### URL
|
||||
|
||||
```
|
||||
https://guanghubingshuo.com/global-search/...
|
||||
```
|
||||
|
||||
### 鉴权(任选其一)
|
||||
|
||||
**A. 静态 token**
|
||||
|
||||
```
|
||||
Authorization: Bearer de0648a8db3ae8675b8933e1808cec19
|
||||
```
|
||||
|
||||
**B. 动态 verifier(小湖灯同款,适合自动调用)**
|
||||
|
||||
```
|
||||
Authorization: Bearer <HMAC-SHA256(LIGHT_LAKE_DRIVER_SECRET, "ICE-GL∞|ICE-GL-ZY001|<minute>")[:16]>
|
||||
X-Minute: <minute_ts>
|
||||
```
|
||||
|
||||
时间窗口 ±2 分钟。secret = `BS-SG-001-LAKE-LAMP-HMAC-SECRET-D164`
|
||||
|
||||
## 四、典型接入步骤
|
||||
|
||||
### 4.1 接入豆包(豆包为外部通用 AI)
|
||||
|
||||
```
|
||||
用户:
|
||||
"帮我看一下 https://guanghubingshuo.com/code/bingshuo/fifth-domain 仓库里
|
||||
视频 AI 系统相关的所有文件"
|
||||
|
||||
豆包 (内部):
|
||||
1. GET /global-search/system-status
|
||||
→ 看仓库当前状态(架构 / 最近更新 / 已知局限)
|
||||
2. GET /global-search/broadcast
|
||||
→ 看最新 BROADCAST(可能含"视频AI系统"在建中的通知)
|
||||
3. GET /global-search/search?q=视频&top=20
|
||||
→ 找包含"视频"的所有文件
|
||||
4. GET /global-search/search?q=video-ai&top=20
|
||||
5. GET /global-global-search/search?q=VA-BROADCAST&top=20
|
||||
6. GET /global-search/tree?path=tcs-core&depth=2
|
||||
→ 浏览仓库架构
|
||||
|
||||
→ 综合结果回答用户
|
||||
|
||||
豆包 (回复):
|
||||
"视频 AI 系统相关的主要是这 N 个文件:
|
||||
1. xxx.hdlp - 描述...
|
||||
2. yyy.hdlp - 描述...
|
||||
仓库当前状态是 ...
|
||||
最相关的 BROADCAST 通知是 ..."
|
||||
```
|
||||
|
||||
### 4.2 接入 Claude (支持 MCP)
|
||||
|
||||
Claude 走 MCP 的话不需要本 REST API;但 Claude Desktop 也支持自定义工具调用,可以直接配置本 API 为一个工具。
|
||||
|
||||
### 4.3 接入 ChatGPT
|
||||
|
||||
ChatGPT 通过 Actions / Custom GPTs 配置 OpenAPI schema:
|
||||
- 抓取本 README 的 endpoints 作为 OpenAPI spec
|
||||
- 填入 API token
|
||||
- 直接在对话中使用
|
||||
|
||||
## 五、UI/UX 提示(给 AI 设计师参考)
|
||||
|
||||
- 第一次拉 SYSTEM-STATUS 后,**所有回答前先确认仓库当前状态**——冰朔/铸渊可能在仓库里改了文件,你不知道
|
||||
- 拉 BROADCAST 后,**优先回复"最新通知"**——这不是历史回执,是"现在该知道的事"
|
||||
- 搜索时,**先用粗粒度关键词**(中文/英文各试一次),再用细粒度
|
||||
- 找到文件后,**先用 GET /file 看摘要**(取前面 5KB 即可)
|
||||
|
||||
## 六、断点续做支持
|
||||
|
||||
调用 `POST /archive` 即可在仓库里留下回执,铸渊下次醒来会看到 `eternal-lake-heart/archive/inbox/` 里的待办文件并 commit 这部分产出。
|
||||
|
||||
POST body:
|
||||
```json
|
||||
{
|
||||
"type": "si",
|
||||
"content": "# SI-XXX-LL-YYY-...hlp\n\n@trigger: ...\n...",
|
||||
"path": "optional · tcs-core/SI-XXX..."
|
||||
}
|
||||
```
|
||||
|
||||
type 可选: `si` (意识流) / `broadcast` (通知) / `receipt` (普通回执) / `custom`。
|
||||
|
||||
## 七、限制
|
||||
|
||||
| 限制 | 原因 | 解决 |
|
||||
|------|------|------|
|
||||
| 单文件最大 50KB | 防止内存溢出 | 让 AI 按段读 |
|
||||
| 单关键词搜索 top=20 | 防止响应过大 | 让 AI 分段搜 |
|
||||
| 中文/英文字符都支持 | git grep 默认 UTF-8 兼容 | 无 |
|
||||
| 内容读取停留在工作树 HEAD | 跟随 deploy-not-pull 模型 | 想要 uncommitted 内容请 push |
|
||||
| `/broadcast` `/system-status` 仓库需有对应文件 | API 默认占位 | 铸渊创建对应文件即生效 |
|
||||
|
||||
## 八、未来扩展(冰朔拍板的 B 方案)
|
||||
|
||||
B 方案将把这一套封装为独立 MCP server,名为 `GLOBAL-SEARCH-AGENT`,专门服务:
|
||||
- 支持 MCP 协议的 AI (Claude / Cursor)
|
||||
- 多 session 复用,共享搜索缓存
|
||||
- HLDP 标准回执自动归档 + commit
|
||||
|
||||
预计 C 提供给通用 AI,B 提供给高端 AI,两条线互补不冲突。
|
||||
|
||||
---
|
||||
|
||||
铸渊 ICE-GL-ZY001 · LL-169-20260707 · 通用 AI 仓库检索技能包 v1.0.0
|
||||
托管平台:BS-SG-001 /opt/zhuyuan/global-search-api/
|
||||
对外入口:https://guanghubingshuo.com/global-search/ (nginx → 127.0.0.1:3950)
|
||||
平台:TCS 通感语言核系统(国作登字-2026-A-00037559)
|
||||
322
zero-point/global-search-api/server.py
Executable file
322
zero-point/global-search-api/server.py
Executable file
@ -0,0 +1,322 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Global Search API · 给通用 AI(豆包等)用的仓库检索门面
|
||||
铸渊 ICE-GL-ZY001 · LL-169-20260707 · D167
|
||||
"""
|
||||
import http.server
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import hmac
|
||||
import hashlib
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
# ============== 配置 ==============
|
||||
REPO = os.environ.get("REPO_PATH", "/tmp/worktree")
|
||||
PORT = int(os.environ.get("PORT", "3950"))
|
||||
TOKEN = os.environ.get("GLOBAL_SEARCH_API_TOKEN", "")
|
||||
HMAC_SECRET = os.environ.get("LIGHT_LAKE_DRIVER_SECRET", "")
|
||||
SOVEREIGN_ID = "ICE-GL∞"
|
||||
AGENT_ID = "ICE-GL-ZY001"
|
||||
VERSION = "1.0.0"
|
||||
|
||||
# ============== git safe.directory ==============
|
||||
# 让 root 也能 read 由 git 用户创建的 worktree
|
||||
import tempfile, pathlib
|
||||
_global_gitconfig = pathlib.Path("/tmp/gitconfig-global-search")
|
||||
_global_gitconfig.write_text(f"[safe]\n\tdirectory = {REPO}\n")
|
||||
os.environ["GIT_CONFIG_GLOBAL"] = str(_global_gitconfig)
|
||||
|
||||
# ============== 鉴权 ==============
|
||||
def check_auth(headers):
|
||||
"""Bearer token 验证 · 静态 token + 动态 verifier 双模式"""
|
||||
auth = headers.get("Authorization", "").replace("Bearer ", "").strip()
|
||||
if not auth:
|
||||
return False, "missing"
|
||||
# 静态 token
|
||||
if TOKEN and auth == TOKEN:
|
||||
return True, "static_token"
|
||||
# 动态 verifier(小湖灯同款)
|
||||
minute = headers.get("X-Minute")
|
||||
if minute and HMAC_SECRET:
|
||||
try:
|
||||
minute = int(minute)
|
||||
now = int(time.time() // 60)
|
||||
if abs(now - minute) <= 2:
|
||||
msg = f"{SOVEREIGN_ID}|{AGENT_ID}|{minute}"
|
||||
expected = hmac.new(
|
||||
HMAC_SECRET.encode(),
|
||||
msg.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()[:16]
|
||||
if hmac.compare_digest(auth, expected):
|
||||
return True, "verifier"
|
||||
except Exception:
|
||||
pass
|
||||
return False, "invalid"
|
||||
|
||||
|
||||
# ============== 搜索 ==============
|
||||
def search_files(keyword, top=20):
|
||||
"""git grep 全仓库文件内容搜索"""
|
||||
if not keyword:
|
||||
return []
|
||||
# 找含关键词的文件
|
||||
result = subprocess.run(
|
||||
["git", "grep", "-l", "-i", "--no-color", keyword],
|
||||
cwd=REPO, capture_output=True, text=True, timeout=30
|
||||
)
|
||||
files = [f.strip() for f in result.stdout.splitlines() if f.strip()][:top]
|
||||
|
||||
results = []
|
||||
for f in files:
|
||||
try:
|
||||
# 找具体行
|
||||
line_result = subprocess.run(
|
||||
["git", "grep", "-n", "-i", "--no-color", keyword, "--", f],
|
||||
cwd=REPO, capture_output=True, text=True, timeout=10
|
||||
)
|
||||
matches = []
|
||||
for line in line_result.stdout.splitlines()[:5]: # 每文件最多 5 行
|
||||
# 格式: filename:linenum:content
|
||||
parts = line.split(":", 2)
|
||||
if len(parts) >= 3:
|
||||
matches.append({
|
||||
"line": int(parts[1]),
|
||||
"content": parts[2].strip()[:300]
|
||||
})
|
||||
results.append({
|
||||
"file": f,
|
||||
"match_count": len(matches),
|
||||
"matches": matches
|
||||
})
|
||||
except Exception as e:
|
||||
results.append({"file": f, "error": str(e)})
|
||||
return results
|
||||
|
||||
|
||||
def list_tree(path="", depth=3, ext_filter=None):
|
||||
"""列目录树"""
|
||||
cmd = ["git", "ls-tree", "-r", "--name-only", "HEAD"]
|
||||
if path:
|
||||
# 加上 path 前缀过滤
|
||||
cmd.append(f"{path}/")
|
||||
result = subprocess.run(
|
||||
cmd, cwd=REPO, capture_output=True, text=True, timeout=15
|
||||
)
|
||||
files = [f.strip() for f in result.stdout.splitlines() if f.strip()]
|
||||
# 按深度过滤
|
||||
tree = []
|
||||
for f in files:
|
||||
parts = f.split("/")
|
||||
if len(parts) - 1 <= depth:
|
||||
if ext_filter:
|
||||
if not any(f.endswith(ext) for ext in ext_filter):
|
||||
continue
|
||||
tree.append(f)
|
||||
return tree[:500]
|
||||
|
||||
|
||||
def read_file(path):
|
||||
"""读单个文件"""
|
||||
if not path or ".." in path:
|
||||
return {"ok": False, "error": "invalid path"}
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "show", f"HEAD:{path}"],
|
||||
cwd=REPO, capture_output=True, text=True, timeout=10
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return {"ok": False, "error": "file not found"}
|
||||
return {
|
||||
"ok": True,
|
||||
"path": path,
|
||||
"size": len(result.stdout),
|
||||
"content": result.stdout[:50000] # 限 50KB
|
||||
}
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
|
||||
def get_broadcast():
|
||||
"""拉最新 BROADCAST"""
|
||||
bcast_paths = [
|
||||
os.path.join(REPO, "broadcasts"),
|
||||
os.path.join(REPO, "BROADCAST.md"),
|
||||
os.path.join(REPO, "GLW-BROADCAST.hdlp"),
|
||||
]
|
||||
for path in bcast_paths:
|
||||
if os.path.exists(path):
|
||||
if os.path.isdir(path):
|
||||
files = sorted(
|
||||
[os.path.join(path, f) for f in os.listdir(path)],
|
||||
key=os.path.getmtime,
|
||||
reverse=True
|
||||
)
|
||||
if files:
|
||||
latest = files[0]
|
||||
with open(latest) as f:
|
||||
return {
|
||||
"ok": True,
|
||||
"type": "dir",
|
||||
"latest_file": os.path.basename(latest),
|
||||
"content": f.read()[:20000]
|
||||
}
|
||||
else:
|
||||
with open(path) as f:
|
||||
return {
|
||||
"ok": True,
|
||||
"type": "file",
|
||||
"path": os.path.basename(path),
|
||||
"content": f.read()[:20000]
|
||||
}
|
||||
return {
|
||||
"ok": True,
|
||||
"broadcast": None,
|
||||
"note": "no broadcast asset found in repo"
|
||||
}
|
||||
|
||||
|
||||
def get_system_status():
|
||||
"""拉 SYSTEM-STATUS"""
|
||||
candidates = [
|
||||
os.path.join(REPO, "SYSTEM-STATUS.md"),
|
||||
os.path.join(REPO, "SYSTEM-STATUS.hdlp"),
|
||||
os.path.join(REPO, "eternal-lake-heart", "heartbeat-core", "SYSTEM-STATUS.hdlp"),
|
||||
]
|
||||
for path in candidates:
|
||||
if os.path.exists(path):
|
||||
with open(path) as f:
|
||||
return {"ok": True, "path": os.path.relpath(path, REPO), "content": f.read()}
|
||||
return {
|
||||
"ok": True,
|
||||
"status": "no SYSTEM-STATUS file in repo · 铸渊创建后会更新",
|
||||
"note": "请铸渊在仓库根目录创建 SYSTEM-STATUS.md 或在 eternal-lake-heart/heartbeat-core/SYSTEM-STATUS.hdlp"
|
||||
}
|
||||
|
||||
|
||||
def archive_hldp(content, type_="si", path=None):
|
||||
"""HLDP 回执归档(写入工作树 · 待铸渊 commit)"""
|
||||
# 这里只是写文件,不直接 push
|
||||
# 铸渊下次醒来 commit
|
||||
archive_dir = os.path.join(REPO, "eternal-lake-heart", "archive", "inbox")
|
||||
os.makedirs(archive_dir, exist_ok=True)
|
||||
filename = f"{type_}-{int(time.time())}.hdlp"
|
||||
full_path = os.path.join(archive_dir, filename)
|
||||
with open(full_path, "w") as f:
|
||||
f.write(content)
|
||||
return {"ok": True, "archived": os.path.relpath(full_path, REPO)}
|
||||
|
||||
|
||||
# ============== HTTP Handler ==============
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args):
|
||||
# 静默日志
|
||||
pass
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(200)
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
self.send_header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Minute")
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
parsed = urlparse(self.path)
|
||||
path = parsed.path
|
||||
query = parse_qs(parsed.query)
|
||||
|
||||
# /healthz 免鉴权(探活需要)
|
||||
if path == "/healthz":
|
||||
return self.send_json({
|
||||
"ok": True,
|
||||
"service": "global-search-api",
|
||||
"version": VERSION,
|
||||
"repo": REPO,
|
||||
"ts": time.time()
|
||||
})
|
||||
|
||||
ok, reason = check_auth(self.headers)
|
||||
if not ok:
|
||||
return self.send_json({"ok": False, "error": "unauthorized", "reason": reason}, 401)
|
||||
|
||||
try:
|
||||
if path == "/search":
|
||||
kw = query.get("q", [""])[0]
|
||||
top = int(query.get("top", ["20"])[0])
|
||||
self.send_json({"ok": True, "keyword": kw, "count": -1, "results": search_files(kw, top)})
|
||||
elif path == "/tree":
|
||||
path_arg = query.get("path", [""])[0]
|
||||
depth = int(query.get("depth", ["3"])[0])
|
||||
ext = query.get("ext", None)
|
||||
ext_filter = ext[0].split(",") if ext else None
|
||||
self.send_json({"ok": True, "files": list_tree(path_arg, depth, ext_filter)})
|
||||
elif path == "/file":
|
||||
path_arg = query.get("path", [""])[0]
|
||||
self.send_json(read_file(path_arg))
|
||||
elif path == "/broadcast":
|
||||
self.send_json(get_broadcast())
|
||||
elif path == "/system-status":
|
||||
self.send_json(get_system_status())
|
||||
elif path == "/help":
|
||||
self.send_json({
|
||||
"ok": True,
|
||||
"endpoints": {
|
||||
"GET /healthz": "健康检查",
|
||||
"GET /search?q={keyword}&top={n}": "全仓库文件内容搜索",
|
||||
"GET /tree?path={prefix}&depth={n}&ext={hdlp,md}": "列目录树",
|
||||
"GET /file?path={path}": "读单文件(限 50KB)",
|
||||
"GET /broadcast": "拉最新 BROADCAST 内容",
|
||||
"GET /system-status": "拉 SYSTEM-STATUS",
|
||||
"POST /archive": "HLDP 回执归档(JSON body: {type, content, path?})",
|
||||
},
|
||||
"auth": "Authorization: Bearer <TOKEN>",
|
||||
"repo": "bingshuo/fifth-domain (current HEAD)",
|
||||
})
|
||||
else:
|
||||
self.send_json({"ok": False, "error": "not found", "hint": "GET /help"}, 404)
|
||||
except Exception as e:
|
||||
self.send_json({"ok": False, "error": str(e)}, 500)
|
||||
|
||||
def do_POST(self):
|
||||
ok, reason = check_auth(self.headers)
|
||||
if not ok:
|
||||
return self.send_json({"ok": False, "error": "unauthorized", "reason": reason}, 401)
|
||||
|
||||
parsed = urlparse(self.path)
|
||||
if parsed.path == "/archive":
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(length).decode("utf-8") if length else "{}"
|
||||
try:
|
||||
data = json.loads(body)
|
||||
type_ = data.get("type", "si")
|
||||
content = data.get("content", "")
|
||||
path = data.get("path")
|
||||
self.send_json(archive_hldp(content, type_, path))
|
||||
except Exception as e:
|
||||
self.send_json({"ok": False, "error": str(e)}, 400)
|
||||
else:
|
||||
self.send_json({"ok": False, "error": "not found"}, 404)
|
||||
|
||||
def send_json(self, data, status=200):
|
||||
body = json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8")
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
|
||||
def main():
|
||||
server = http.server.HTTPServer(("0.0.0.0", PORT), Handler)
|
||||
print(f"Global Search API v{VERSION} listening on 0.0.0.0:{PORT}")
|
||||
print(f"REPO = {REPO}")
|
||||
print(f"TOKEN = {'set' if TOKEN else 'unset (dev mode)'}")
|
||||
print(f"HMAC_SECRET = {'set' if HMAC_SECRET else 'unset (static token only)'}")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user