# @gloablehive/license

> 本地授权模块 — 零 SaaS 依赖的 License 发放、验证、功能开关

## 原理

```
运维/发放端                              客户/Gateway
┌──────────────┐    license.json    ┌──────────────────┐
│ CLI: issue   │  ───────────────→  │ LicenseGuard     │
│ HMAC 签名    │                    │  boot()  校验签名 │
│ 功能+配额    │                    │  isFeatureEnabled │
└──────────────┘                    │  canSendMessage   │
                                    └──────────────────┘
```

- **签名**: HMAC-SHA256，篡改 license.json 任何字段都会校验失败
- **机器绑定**: 可选，防止 License 拷贝到其他机器
- **宽限期**: 过期后 7 天内降级为 free，超 7 天停服
- **无网络**: 不需要 SaaS 后端，纯本地 JSON 文件

## 快速开始

### 安装

```bash
npm install @gloablehive/license
```

### 发放 License (运维端)

```bash
# 设置签名密钥 (发放和验证必须用同一个)
export OPENCLAW_LICENSE_SECRET="your-secret-key"

# 查看可用套餐
npx openclaw-license plans

# 发放 basic 套餐, 365天有效
npx openclaw-license issue \
  --merchant M001 \
  --name "日本水果店" \
  --plan basic \
  --days 365 \
  --output ~/.openclaw/license.json

# 绑定机器 (防拷贝)
npx openclaw-license issue \
  --merchant M001 \
  --name "水果店" \
  --plan pro \
  --bind-machine \
  --output ~/.openclaw/license.json
```

### 验证 License

```bash
npx openclaw-license verify --file ~/.openclaw/license.json
npx openclaw-license info --file ~/.openclaw/license.json
npx openclaw-license machine-id
```

### 在代码中使用

```typescript
import { LicenseGuard } from '@gloablehive/license';

const guard = new LicenseGuard(
  '~/.openclaw/license.json',
  process.env.OPENCLAW_LICENSE_SECRET!,
);

// Boot Gate — 启动时校验
const result = await guard.boot();
if (!result.valid) {
  console.error('License invalid:', result.error);
  process.exit(1);
}

// Feature Gate — 功能开关
if (guard.isFeatureEnabled('fts_search')) {
  // 执行 FTS5 搜索
}

if (guard.isFeatureEnabled('multi_project')) {
  // 允许多项目
}

// Quota Gate — 配额检查
const msgCheck = guard.canSendMessage();
if (!msgCheck.allowed) {
  console.warn(msgCheck.reason);  // "Daily message limit reached"
  return;
}

// 记录用量
guard.recordMessage();
guard.recordTokens(1500);

// 查看用量
const usage = guard.getUsageSummary();
console.log(`今日消息: ${usage.dailyMessages}/${usage.quotas.dailyMessages}`);
```

## 套餐对比

| Plan | 价格 | 项目 | Agent/项目 | 月Token | 日消息 | FTS5 | 向量 | 多项目 |
|------|------|------|-----------|---------|--------|------|------|--------|
| free | 0 | 1 | 4 | 10万 | 100 | ✅ | ❌ | ❌ |
| basic | ¥99 | 3 | 6 | 100万 | 500 | ✅ | ❌ | ✅ |
| pro | ¥299 | 10 | 无限 | 500万 | 无限 | ✅ | ✅ | ✅ |
| enterprise | 定制 | 无限 | 无限 | 无限 | 无限 | ✅ | ✅ | ✅ |

## 安全

- **签名密钥**: 通过 `OPENCLAW_LICENSE_SECRET` 环境变量传入，发放端和客户端必须一致
- **防篡改**: 修改 license.json 任何字段 → 签名校验失败
- **防拷贝**: `--bind-machine` 绑定机器指纹
- **防过期**: 7 天宽限期 → 降级 free → 完全停服
- **计时安全**: 签名比较使用 `crypto.timingSafeEqual` 防计时攻击

## 文件结构

```
license.json          # License 授权文件
├── version: 1        # 格式版本
├── payload           # 授权信息
│   ├── id            # License UUID
│   ├── merchantId    # 商户 ID
│   ├── merchantName  # 商户名
│   ├── plan          # 套餐等级
│   ├── features      # 功能开关
│   ├── quotas        # 配额限制
│   ├── issuedAt      # 发放时间
│   ├── expiresAt     # 过期时间
│   └── machineId     # 机器指纹 (可选)
└── signature         # HMAC-SHA256 签名

usage.json            # 运行时用量 (自动生成)
├── dailyMessages     # 今日消息数
├── monthlyTokens     # 本月 Token 数
└── updatedAt         # 最后更新
```

## 后续: SaaS 联动

当 SaaS 后端就绪后:
1. `license.json` 改为 SaaS 下发 (API: `POST /api/license/issue`)
2. 添加 24h 心跳刷新 (`POST /api/license/heartbeat`)
3. 用量上报改为 5min 批量推送到 SaaS
4. 本地逻辑完全不变，只是 License 来源从手动变成自动
