144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
|
|
# ============================================================
|
|||
|
|
# APS 用户主目录(moduleId: server-aps-home, 可重生 ✅)
|
|||
|
|
# 对标 Codex ~/.codex:桌面端默认 ~/.aps/{skills,sessions,data,…}
|
|||
|
|
# Web 部署默认仍用仓库内 server/data(可用 APS_HOME 覆盖)
|
|||
|
|
# ============================================================
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
# 与 Codex 类似的子目录约定
|
|||
|
|
_LAYOUT = (
|
|||
|
|
"skills", # 算法 / Agent Skill(每 skill 一目录 + manifest.json)
|
|||
|
|
"sessions", # 会话落盘(桌面端持久化)
|
|||
|
|
"cache",
|
|||
|
|
"tmp",
|
|||
|
|
"logs",
|
|||
|
|
"data", # world / sqlite / knowledge / checkpoints
|
|||
|
|
"packs", # 可选:用户级数据包
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def mode() -> str:
|
|||
|
|
"""运行形态:desktop | web(默认 web,便于仓库内开发与测试)。"""
|
|||
|
|
m = (os.environ.get("APS_MODE") or "web").strip().lower()
|
|||
|
|
return "desktop" if m in ("desktop", "desk", "electron", "app") else "web"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def aps_home() -> Path:
|
|||
|
|
"""解析 APS 根目录。
|
|||
|
|
|
|||
|
|
优先级:
|
|||
|
|
1. APS_HOME(显式,任意形态)
|
|||
|
|
2. desktop → 用户主目录/.aps(对标 Codex ~/.codex;Windows=%USERPROFILE%\\.aps)
|
|||
|
|
3. web → APS_DATA_DIR 或 仓库 server/data
|
|||
|
|
"""
|
|||
|
|
raw = os.environ.get("APS_HOME")
|
|||
|
|
if raw:
|
|||
|
|
return Path(raw).expanduser().resolve()
|
|||
|
|
if mode() == "desktop":
|
|||
|
|
# 安装到电脑后的唯一默认:用户根目录下的 .aps(不可落到安装目录)
|
|||
|
|
return (Path.home() / ".aps").resolve()
|
|||
|
|
data = os.environ.get("APS_DATA_DIR")
|
|||
|
|
if data:
|
|||
|
|
return Path(data).expanduser().resolve()
|
|||
|
|
# 相对仓库:server/data(与历史路径兼容)
|
|||
|
|
return Path("server/data").resolve()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def ensure_aps_home(home: Path | None = None) -> Path:
|
|||
|
|
"""创建主目录与标准子目录;写入 config.json(若不存在)。"""
|
|||
|
|
root = home or aps_home()
|
|||
|
|
root.mkdir(parents=True, exist_ok=True)
|
|||
|
|
for name in _LAYOUT:
|
|||
|
|
(root / name).mkdir(parents=True, exist_ok=True)
|
|||
|
|
cfg = root / "config.json"
|
|||
|
|
if not cfg.exists():
|
|||
|
|
cfg.write_text(
|
|||
|
|
json.dumps({
|
|||
|
|
"version": 1,
|
|||
|
|
"mode": mode(),
|
|||
|
|
"layout": list(_LAYOUT),
|
|||
|
|
"note": "对标 Codex ~/.codex:skills / sessions / data 等本地落盘",
|
|||
|
|
}, ensure_ascii=False, indent=2),
|
|||
|
|
encoding="utf-8",
|
|||
|
|
)
|
|||
|
|
return root
|
|||
|
|
|
|||
|
|
|
|||
|
|
def skills_dir() -> Path:
|
|||
|
|
return ensure_aps_home() / "skills"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def sessions_dir() -> Path:
|
|||
|
|
return ensure_aps_home() / "sessions"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def default_projects_path() -> str:
|
|||
|
|
"""工作区元数据(项目/会话/消息):落在 ~/.aps/sessions/workspace.json。"""
|
|||
|
|
if os.environ.get("APS_PROJECTS_PATH"):
|
|||
|
|
return os.environ["APS_PROJECTS_PATH"]
|
|||
|
|
return str(sessions_dir() / "workspace.json")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def data_dir() -> Path:
|
|||
|
|
"""世界状态 / DB / 知识库等运行数据目录。"""
|
|||
|
|
root = ensure_aps_home()
|
|||
|
|
# web 默认 APS_HOME 就是 server/data,数据平铺在根下以保持兼容
|
|||
|
|
if mode() == "web" and not os.environ.get("APS_HOME"):
|
|||
|
|
return root
|
|||
|
|
d = root / "data"
|
|||
|
|
d.mkdir(parents=True, exist_ok=True)
|
|||
|
|
return d
|
|||
|
|
|
|||
|
|
|
|||
|
|
def path_under_data(*parts: str) -> Path:
|
|||
|
|
return data_dir().joinpath(*parts)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def default_world_path() -> str:
|
|||
|
|
if os.environ.get("APS_WORLD_PATH"):
|
|||
|
|
return os.environ["APS_WORLD_PATH"]
|
|||
|
|
return str(path_under_data("world.json"))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def default_db_path() -> str:
|
|||
|
|
if os.environ.get("APS_DB_PATH"):
|
|||
|
|
return os.environ["APS_DB_PATH"]
|
|||
|
|
return str(path_under_data("master.db"))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def default_checkpoint_path() -> str:
|
|||
|
|
if os.environ.get("APS_CHECKPOINT_PATH"):
|
|||
|
|
return os.environ["APS_CHECKPOINT_PATH"]
|
|||
|
|
return str(path_under_data("checkpoints.json"))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def default_knowledge_path() -> str:
|
|||
|
|
if os.environ.get("APS_KNOWLEDGE_PATH"):
|
|||
|
|
return os.environ["APS_KNOWLEDGE_PATH"]
|
|||
|
|
return str(path_under_data("knowledge.json"))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def default_embeddings_path() -> str:
|
|||
|
|
if os.environ.get("APS_EMBEDDINGS_PATH"):
|
|||
|
|
return os.environ["APS_EMBEDDINGS_PATH"]
|
|||
|
|
return str(path_under_data("embeddings.json"))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def describe() -> dict:
|
|||
|
|
"""供 /api/system/paths 与桌面关于页展示。"""
|
|||
|
|
root = ensure_aps_home()
|
|||
|
|
return {
|
|||
|
|
"mode": mode(),
|
|||
|
|
"home": str(root),
|
|||
|
|
"skills": str(skills_dir()),
|
|||
|
|
"sessions": str(sessions_dir()),
|
|||
|
|
"workspace": default_projects_path(),
|
|||
|
|
"data": str(data_dir()),
|
|||
|
|
"world": default_world_path(),
|
|||
|
|
"db": default_db_path(),
|
|||
|
|
}
|