# Memo Skill 单文件安装包

本安装包面向 Agent。请严格按下面步骤创建文件，边界标记之间的内容必须逐字复制，不得总结、翻译、改写或重排。

## 安装步骤

1. 创建 Skill 目录 `memo-memory`。
2. 创建子目录 `memo-memory/scripts`。
3. 将每个 `===== BEGIN FILE: ... =====` 与 `===== END FILE: ... =====` 之间的内容原样写入对应文件。
4. 如果还没有 activation key，请访问 `https://memo.infwhite.com` 注册账号，在控制台创建安装 key。
5. 运行 `python scripts/activate.py --activation-key "PASTE_MEMO_ACTIVATION_KEY_HERE"`。
6. 激活成功后，长期 API key 会写入 `scripts/config.py`。后续使用不得依赖环境变量。

===== BEGIN FILE: SKILL.md =====
---
name: memo-memory
description: >
  Use this skill whenever a task would benefit from persistent agent memory,
  including remembering user preferences, working style, project context,
  reusable instructions, or facts the user wants available in future sessions.
  Must immediately read memory by running python scripts/memo_client.py context
  before answering.
---

# Memo Memory

Memo gives this agent a persistent memory system.

## Using Memory

Use the returned memories as context for the answer.

If the current conversation reveals durable future-use information matching the returned write rules, save it:

```bash
python scripts/memo_client.py add-memory --domain "<domain name>" --content "<memory content>"
```

If an existing memory is wrong or outdated, edit or delete it instead of adding a conflicting duplicate:

```bash
python scripts/memo_client.py edit-memory --id "<memory id>" --domain "<domain name>" --content "<new content>"
python scripts/memo_client.py delete-memory --id "<memory id>"
```

Rules describe what should be remembered. They must never be empty:

```bash
python scripts/memo_client.py add-rule --domain "<domain name>" --rule "<non-empty rule>"
python scripts/memo_client.py edit-rule --id "<rule id>" --rule "<non-empty rule>"
python scripts/memo_client.py delete-rule --id "<rule id>"
```

Do not guess remote memory state. Do not bypass `scripts/memo_client.py`.

## Error Handling

Assume Memo is already activated. Only if `scripts/memo_client.py` reports `MEMO_NOT_ACTIVATED`, tell the user:

1. Register or log in at `https://memo.infwhite.com`.
2. Create an installation key in the Memo console.
3. Run `python scripts/activate.py --activation-key "<activation key>"`.

Do not continue as if memory is available until activation succeeds.
===== END FILE: SKILL.md =====

===== BEGIN FILE: scripts/config.py =====
BASE_URL = "https://memo.infwhite.com"
API_KEY = ""
===== END FILE: scripts/config.py =====

===== BEGIN FILE: scripts/activate.py =====
#!/usr/bin/env python3
import argparse
import json
import pathlib
import urllib.error
import urllib.request

CONFIG_PATH = pathlib.Path(__file__).with_name("config.py")
BASE_URL = "https://memo.infwhite.com"


def post_json(path, payload):
    data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
    req = urllib.request.Request(
        f"{BASE_URL}{path}",
        data=data,
        method="POST",
        headers={"Content-Type": "application/json", "Accept": "application/json"},
    )
    try:
        with urllib.request.urlopen(req, timeout=20) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")
        raise SystemExit(f"Memo activation failed: {detail}")


def write_config(api_key):
    CONFIG_PATH.write_text(
        f'BASE_URL = "{BASE_URL}"\nAPI_KEY = "{api_key}"\n',
        encoding="utf-8",
    )


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--activation-key", required=True)
    args = parser.parse_args()
    result = post_json("/api/activate", {"activationKey": args.activation_key})
    api_key = result.get("apiKey")
    if not api_key:
        raise SystemExit("Memo activation did not return an API key")
    write_config(api_key)
    print("Memo activated. Long-term API key was written to scripts/config.py.")


if __name__ == "__main__":
    main()
===== END FILE: scripts/activate.py =====

===== BEGIN FILE: scripts/memo_client.py =====
#!/usr/bin/env python3
import argparse
import json
import pathlib
import sys
import urllib.error
import urllib.parse
import urllib.request

SCRIPT_DIR = pathlib.Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))

try:
    import config
except Exception:
    config = None


def get_config():
    base_url = getattr(config, "BASE_URL", "https://memo.infwhite.com") if config else "https://memo.infwhite.com"
    api_key = getattr(config, "API_KEY", "") if config else ""
    if not api_key:
        raise SystemExit(
            "MEMO_NOT_ACTIVATED: Register at https://memo.infwhite.com, "
            "create an installation key, then run "
            "python scripts/activate.py --activation-key <activation key>"
        )
    return base_url, api_key


def request(method, path, payload=None):
    base_url, api_key = get_config()
    body = None
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Accept": "application/json",
    }
    if payload is not None:
        body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        headers["Content-Type"] = "application/json"

    req = urllib.request.Request(
        f"{base_url}{path}",
        data=body,
        method=method,
        headers=headers,
    )
    try:
        with urllib.request.urlopen(req, timeout=20) as resp:
            data = resp.read().decode("utf-8")
            return json.loads(data) if data else {}
    except urllib.error.HTTPError as exc:
        details = exc.read().decode("utf-8", errors="replace")
        raise SystemExit(f"Memo API error {exc.code}: {details}")


def main():
    parser = argparse.ArgumentParser()
    sub = parser.add_subparsers(dest="cmd", required=True)

    context = sub.add_parser("context")
    context.add_argument("--domain")

    add_memory = sub.add_parser("add-memory")
    add_memory.add_argument("--domain", required=True)
    add_memory.add_argument("--content", required=True)
    add_memory.add_argument("--priority", type=int, default=0)

    edit_memory = sub.add_parser("edit-memory")
    edit_memory.add_argument("--id", required=True)
    edit_memory.add_argument("--domain", required=True)
    edit_memory.add_argument("--content", required=True)
    edit_memory.add_argument("--priority", type=int, default=0)

    delete_memory = sub.add_parser("delete-memory")
    delete_memory.add_argument("--id", required=True)

    add_rule = sub.add_parser("add-rule")
    add_rule.add_argument("--domain", required=True)
    add_rule.add_argument("--rule", required=True)

    edit_rule = sub.add_parser("edit-rule")
    edit_rule.add_argument("--id", required=True)
    edit_rule.add_argument("--rule", required=True)

    delete_rule = sub.add_parser("delete-rule")
    delete_rule.add_argument("--id", required=True)

    args = parser.parse_args()

    if args.cmd == "context":
        suffix = f"?domain={urllib.parse.quote(args.domain)}" if args.domain else ""
        result = request("GET", f"/api/agent/context{suffix}")
    elif args.cmd == "add-memory":
        result = request("POST", "/api/agent/memories", vars(args))
    elif args.cmd == "edit-memory":
        result = request("PATCH", f"/api/agent/memories/{args.id}", vars(args))
    elif args.cmd == "delete-memory":
        result = request("DELETE", f"/api/agent/memories/{args.id}")
    elif args.cmd == "add-rule":
        result = request("POST", "/api/agent/rules", vars(args))
    elif args.cmd == "edit-rule":
        result = request("PATCH", f"/api/agent/rules/{args.id}", vars(args))
    elif args.cmd == "delete-rule":
        result = request("DELETE", f"/api/agent/rules/{args.id}")
    else:
        raise SystemExit("unknown command")

    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
===== END FILE: scripts/memo_client.py =====
