diff --git a/SYSTEM-STATUS.hdlp b/SYSTEM-STATUS.hdlp new file mode 100644 index 0000000..1c02e85 --- /dev/null +++ b/SYSTEM-STATUS.hdlp @@ -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 \ No newline at end of file diff --git a/eternal-lake-heart/global-search-api/README.md b/eternal-lake-heart/global-search-api/README.md new file mode 100644 index 0000000..b2b5530 --- /dev/null +++ b/eternal-lake-heart/global-search-api/README.md @@ -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: +``` + +verifier 算法:`HMAC-SHA256(LIGHT_LAKE_DRIVER_SECRET, "ICE-GL∞|ICE-GL-ZY001|")[: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) \ No newline at end of file diff --git a/eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH.hdlp b/eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH.hdlp new file mode 100644 index 0000000..1ee660f --- /dev/null +++ b/eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH.hdlp @@ -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 ")[:16]> +X-Minute: +``` + +时间窗口 ±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) \ No newline at end of file diff --git a/zero-point/global-search-api/server.py b/zero-point/global-search-api/server.py new file mode 100755 index 0000000..55a0981 --- /dev/null +++ b/zero-point/global-search-api/server.py @@ -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 ", + "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() \ No newline at end of file