Skill: 5twang/claw-calendar-new-skill (version 1.0.2)

--- name: claw-calendar description: "Calendar Skills 智能日历助手：通过 REST API 与 Claw Calendar (claw-calendar.com) 交互，管理日历和事件。凭证从环境变量读取，支持创建日历、添加事件、查询日程。" description_zh: "Claw Calendar 智能日历助手 - 管理日历、创建事件、自动同步到手机原生日历" description_en: "Smart Calendar Assistant - Manage calendars and events via Claw Calendar API" version: 1.0.2 allowed-tools: Read,Write,Bash metadata: openclaw: requires: env: - CLAW_CALENDAR_API_KEY - CLAW_CALENDAR_API_URL primaryEnv: CLAW_CALENDAR_API_KEY security: expectedPatterns: - pattern: "env_credential_access" reason: "Required to authenticate with Claw Calendar API. API key is read from environment variable and sent to the official claw-calendar.com API endpoint only." --- ## 何时使用 用户需要**管理日历**、**创建事件**、**设置提醒**或**订阅日历**时使用本 skill。 # Claw Calendar 智能日历助手 面向 Claw Calendar 云服务：通过 REST API 创建日历、添加事件，支持 .ics 订阅链接同步到手机原生日历。 ## 凭证（环境变量） | 变量 | 说明 | |------|------| | **CLAW_CALENDAR_API_KEY** | Claw Calendar API 密钥（在用户设置中生成） | | **CLAW_CALENDAR_API_URL** | API 地址（可选，默认为 https://claw-calendar.com） | 脚本会校验 API Key，缺失时报错并退出。 ## API 端点 - **Base URL**: `https://claw-calendar.com/api`（默认） - **认证方式**: Bearer Token（API Key） ## 脚本 | 脚本 | 作用 | |------|------| | `scripts/list-calendars.js` | 列出用户所有日历 | | `scripts/create-calendar.js` | 创建新日历，返回订阅链接 | | `scripts/list-events.js` | 列出指定日历的事件 | | `scripts/create-event.js` | 在日历中创建新事件 | ## 日历管理 ### 列出日历 ```bash node scripts/list-calendars.js ``` 输出：日历 ID、名称、颜色、订阅链接。 ### 创建日历 ```bash # 必填：日历名称 node scripts/create-calendar.js --name "我的日历" # 可选：描述、颜色 node scripts/create-calendar.js --name "股票提醒" --description "持仓股票重要日期" --color "#4f46e5" ``` 输出：日历 ID、订阅链接（用于添加到手机日历）。 ## 事件管理 ### 列出事件 ```bash # 列出所有日历的事件 node scripts/list-events.js --calendar-id <calendarId> # 按日期范围筛选 node scripts/list-events.js --calendar-id <calendarId> --start 2026-04-01 --end 2026-04-30 ``` ### 创建事件 ```bash # 必填：日历ID、标题、开始日期 node scripts/create-event.js --calendar-id <calendarId> --title "会议" --start-date 2026-04-15 # 完整参数示例 node scripts/create-event.js \ --calendar-id <calendarId> \ --title "招商银行分红" \ --description "分红除权日，请注意股价变动" \ --start-date 2026-04-15 \ --end-date 2026-04-15 \ --start-time "09:30:00" \ --end-time "10:30:00" \ --alarm \ --alarm-minutes 1440 ``` **事件参数**： | 参数 | 说明 | 必填 | |------|------|------| | `--calendar-id` | 日历 ID | ✅ | | `--title` | 事件标题 | ✅ | | `--start-date` | 开始日期 (YYYY-MM-DD) | ✅ | | `--end-date` | 结束日期 (YYYY-MM-DD) | 否 | | `--start-time` | 开始时间 (HH:MM:SS) | 否 | | `--end-time` | 结束时间 (HH:MM:SS) | 否 | | `--description` | 事件描述 | 否 | | `--location` | 地点 | 否 | | `--alarm` | 启用提醒 | 否 | | `--alarm-minutes` | 提前多少分钟提醒（默认 15） | 否 | ## 订阅日历到手机 创建日历后，会返回 `.ics` 订阅链接，按以下方式添加到手机日历： - **iOS**: 设置 → 日历 → 账户 → 添加订阅日历 → 粘贴链接 - **Android**: 日历应用 → 更多 → 设置 → 添加日历 → 订阅日历 - **macOS**: 日历 → 文件 → 新建日历订阅 → 粘贴链接 ## 安全提醒 - API Key 视为密码，不要提交到仓库或日志中 - 仅通过环境变量配置：`export CLAW_CALENDAR_API_KEY=your-key` - 建议定期轮换 API Key ## 常见错误 | 错误 | 原因 | 解决 | |------|------|------| | 401 Unauthorized | API Key 无效或过期 | 检查环境变量或重新生成 Key | | 403 Forbidden | 无权访问该日历 | 检查日历 ID 是否正确 | | 404 Not Found | 日历不存在 | 确认日历 ID 或列出日历检查 |

path: README.md
content: # Claw Calendar Skill

Smart Calendar Assistant for WorkBuddy - Manage calendars and events via Claw Calendar API.

## Features

- List all calendars
- Create new calendars with .ics subscription URL
- List events with date range filtering
- Create events with reminders

## Quick Start

### 1. Get API Key

1. Sign up at [claw-calendar.com](https://claw-calendar.com)
2. Go to Settings → API Keys
3. Create a new API Key

### 2. Set Environment Variable

```bash
export CLAW_CALENDAR_API_KEY=your-api-key
```

### 3. Create Your First Calendar

```bash
node scripts/create-calendar.js --name "My Calendar" --color "#4f46e5"
```

### 4. Add Events

```bash
node scripts/create-event.js \
  --calendar-id <calendar-id> \
  --title "Meeting" \
  --start-date 2026-04-15 \
  --start-time "14:00:00" \
  --end-time "15:00:00" \
  --alarm
```

## Scripts

| Script | Description |
|--------|-------------|
| `list-calendars.js` | List all calendars |
| `create-calendar.js` | Create a new calendar |
| `list-events.js` | List events in a calendar |
| `create-event.js` | Create a new event |

## License

MIT


path: package.json
content: {
  "name": "claw-calendar-skill",
  "version": "1.0.0",
  "type": "module",
  "private": true,
  "description": "Claw Calendar API client for WorkBuddy",
  "scripts": {
    "list-calendars": "node scripts/list-calendars.js",
    "create-calendar": "node scripts/create-calendar.js",
    "list-events": "node scripts/list-events.js",
    "create-event": "node scripts/create-event.js"
  }
}


path: scripts/create-calendar.js
content: /**
 * 创建新日历
 * 用法: node scripts/create-calendar.js --name "日历名称" [--description "描述"] [--color "#4f46e5"]
 */

const [REDACTED_SECRET];
const API_URL = process.env.CLAW_CALENDAR_API_URL || 'https://claw-calendar.com';

if (!API_KEY) {
  console.error('请设置环境变量 CLAW_CALENDAR_API_KEY');
  process.exit(1);
}

function parseArgs() {
  const args = process.argv.slice(2);
  const result = {
    name: null,
    description: null,
    color: null
  };
  
  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--name' && args[i + 1]) {
      result.name = args[i + 1];
      i++;
    } else if (args[i] === '--description' && args[i + 1]) {
      result.description = args[i + 1];
      i++;
    } else if (args[i] === '--color' && args[i + 1]) {
      result.color = args[i + 1];
      i++;
    }
  }
  
  return result;
}

async function createCalendar() {
  const { name, description, color } = parseArgs();
  
  if (!name) {
    console.error('用法: node scripts/create-calendar.js --name "日历名称" [--description "描述"] [--color "#颜色"]');
    console.error('示例: node scripts/create-calendar.js --name "股票提醒" --color "#4f46e5"');
    process.exit(1);
  }
  
  try {
    const body = { name };
    if (description) body.description = description;
    if (color) body.color = color;
    
    const response = await fetch(`${API_URL}/api/calendars`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(body)
    });

    const data = await response.json();

    if (!response.ok) {
      console.error(`创建失败: ${data.message || response.statusText}`);
      process.exit(1);
    }

    const cal = data.calendar;
    
    console.log('✅ 日历创建成功！\n');
    console.log(`名称: ${cal.name}`);
    if (cal.description) console.log(`描述: ${cal.description}`);
    console.log(`颜色: ${cal.color || '#000000'}`);
    console.log(`ID: ${cal.id}`);
    console.log('\n📅 订阅链接（添加到手机日历）：');
    console.log(cal.subscriptionUrl);
    console.log('\n复制上述链接，按照以下步骤添加到日历：');
    console.log('- iOS: 设置 → 日历 → 账户 → 添加订阅日历');
    console.log('- Android: 日历应用 → 更多 → 设置 → 添加日历 → 订阅日历');
    console.log('- macOS: 日历 → 文件 → 新建日历订阅');
  } catch (err) {
    console.error('请求失败:', err.message);
    process.exit(1);
  }
}

createCalendar();


path: scripts/create-event.js
content: /**
 * 创建日历事件
 * 用法: node scripts/create-event.js --calendar-id <id> --title <标题> --start-date <日期>
 *       [--end-date <日期>] [--start-time <时间>] [--end-time <时间>]
 *       [--description <描述>] [--location <地点>]
 *       [--alarm] [--alarm-minutes <分钟>]
 */

const [REDACTED_SECRET];
const API_URL = process.env.CLAW_CALENDAR_API_URL || 'https://claw-calendar.com';

if (!API_KEY) {
  console.error('请设置环境变量 CLAW_CALENDAR_API_KEY');
  process.exit(1);
}

function parseArgs() {
  const args = process.argv.slice(2);
  const result = {
    calendarId: null,
    title: null,
    startDate: null,
    endDate: null,
    startTime: null,
    endTime: null,
    description: null,
    location: null,
    alarm: false,
    alarmMinutes: 15
  };
  
  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--calendar-id' && args[i + 1]) {
      result.calendarId = args[i + 1];
      i++;
    } else if (args[i] === '--title' && args[i + 1]) {
      result.title = args[i + 1];
      i++;
    } else if (args[i] === '--start-date' && args[i + 1]) {
      result.startDate = args[i + 1];
      i++;
    } else if (args[i] === '--end-date' && args[i + 1]) {
      result.endDate = args[i + 1];
      i++;
    } else if (args[i] === '--start-time' && args[i + 1]) {
      result.startTime = args[i + 1];
      i++;
    } else if (args[i] === '--end-time' && args[i + 1]) {
      result.endTime = args[i + 1];
      i++;
    } else if (args[i] === '--description' && args[i + 1]) {
      result.description = args[i + 1];
      i++;
    } else if (args[i] === '--location' && args[i + 1]) {
      result.location = args[i + 1];
      i++;
    } else if (args[i] === '--alarm') {
      result.alarm = true;
    } else if (args[i] === '--alarm-minutes' && args[i + 1]) {
      result.alarmMinutes = parseInt(args[i + 1], 10) || 15;
      i++;
    }
  }
  
  return result;
}

function formatDate(dateStr) {
  if (!dateStr) return '';
  const d = new Date(dateStr);
  return d.toLocaleDateString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit'
  });
}

async function createEvent() {
  const args = parseArgs();
  
  if (!args.calendarId || !args.title || !args.startDate) {
    console.error('用法: node scripts/create-event.js --calendar-id <id> --title <标题> --start-date <日期>');
    console.error('       [--end-date <日期>] [--start-time <时间>] [--end-time <时间>]');
    console.error('       [--description <描述>] [--location <地点>] [--alarm] [--alarm-minutes <分钟>]');
    console.error('');
    console.error('示例 (全天事件):');
    console.error('  node scripts/create-event.js --calendar-id abc123 --title "股票分红" --start-date 2026-04-15');
    console.error('');
    console.error('示例 (定时事件):');
    console.error('  node scripts/create-event.js --calendar-id abc123 --title "会议" --start-date 2026-04-15 --start-time "14:00:00" --end-time "15:00:00" --alarm');
    process.exit(1);
  }
  
  // 验证日期格式
  if (!/^\d{4}-\d{2}-\d{2}$/.test(args.startDate)) {
    console.error('日期格式错误，请使用 YYYY-MM-DD 格式（如 2026-04-15）');
    process.exit(1);
  }
  
  if (args.endDate && !/^\d{4}-\d{2}-\d{2}$/.test(args.endDate)) {
    console.error('结束日期格式错误，请使用 YYYY-MM-DD 格式');
    process.exit(1);
  }
  
  try {
    const body = {
      title: args.title,
      startDate: args.startDate
    };
    
    if (args.endDate) body.endDate = args.endDate;
    if (args.startTime) body.startTime = args.startTime;
    if (args.endTime) body.endTime = args.endTime;
    if (args.description) body.description = args.description;
    if (args.location) body.location = args.location;
    if (args.alarm) {
      body.alarm = true;
      body.alarmMinutes = args.alarmMinutes;
    }
    
    const response = await fetch(`${API_URL}/api/calendars/${args.calendarId}/events`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(body)
    });

    const data = await response.json();

    if (!response.ok) {
      if (response.status === 404) {
        console.error(`日历不存在: ${args.calendarId}`);
        console.error('请运行: node scripts/list-calendars.js 查看可用日历');
      } else if (response.status === 403) {
        console.error('无权限在此日历中创建事件');
      } else {
        console.error(`创建失败: ${data.message || response.statusText}`);
      }
      process.exit(1);
    }

    const evt = data.event;
    
    console.log('✅ 事件创建成功！\n');
    console.log(`标题: ${evt.title}`);
    if (evt.description) console.log(`描述: ${evt.description}`);
    if (evt.location) console.log(`地点: ${evt.location}`);
    console.log(`时间: ${formatDate(evt.startDate)} ${evt.startTime || ''} ${evt.endTime ? '- ' + evt.endTime : ''}`);
    if (evt.alarm) {
      console.log(`提醒: 提前 ${evt.alarmMinutes} 分钟`);
    } else {
      console.log('提醒: 未设置');
    }
    console.log(`ID: ${evt.id}`);
    
    if (evt.alarm) {
      console.log('\n📲 提醒已设置，届时会收到通知');
    }
  } catch (err) {
    console.error('请求失败:', err.message);
    process.exit(1);
  }
}

createEvent();


path: scripts/list-calendars.js
content: /**
 * 列出用户所有日历
 * 用法: node scripts/list-calendars.js
 */

const [REDACTED_SECRET];
const API_URL = process.env.CLAW_CALENDAR_API_URL || 'https://claw-calendar.com';

if (!API_KEY) {
  console.error('请设置环境变量 CLAW_CALENDAR_API_KEY');
  console.error('在 Claw Calendar 设置中生成 API Key');
  process.exit(1);
}

async function listCalendars() {
  try {
    const response = await fetch(`${API_URL}/api/calendars`, {
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json'
      }
    });

    if (!response.ok) {
      if (response.status === 401) {
        console.error('认证失败：API Key 无效或已过期');
      } else if (response.status === 403) {
        console.error('无权限访问，请检查 API Key 权限');
      } else {
        console.error(`请求失败: ${response.status} ${response.statusText}`);
      }
      process.exit(1);
    }

    const data = await response.json();
    
    if (!data.calendars || data.calendars.length === 0) {
      console.log('暂无日历');
      console.log('运行以下命令创建第一个日历：');
      console.log('  node scripts/create-calendar.js --name "我的日历"');
      return;
    }

    console.log(`共 ${data.calendars.length} 个日历：\n`);
    
    data.calendars.forEach((cal, i) => {
      console.log(`--- ${i + 1}. ${cal.name} ---`);
      console.log(`ID: ${cal.id}`);
      console.log(`描述: ${cal.description || '(无)'}`);
      console.log(`颜色: ${cal.color || '#000000'}`);
      console.log(`订阅链接: ${cal.subscriptionUrl}`);
      console.log('');
    });
  } catch (err) {
    console.error('请求失败:', err.message);
    process.exit(1);
  }
}

listCalendars();


path: scripts/list-events.js
content: /**
 * 列出日历中的事件
 * 用法: node scripts/list-events.js --calendar-id <id> [--start YYYY-MM-DD] [--end YYYY-MM-DD]
 */

const [REDACTED_SECRET];
const API_URL = process.env.CLAW_CALENDAR_API_URL || 'https://claw-calendar.com';

if (!API_KEY) {
  console.error('请设置环境变量 CLAW_CALENDAR_API_KEY');
  process.exit(1);
}

function parseArgs() {
  const args = process.argv.slice(2);
  const result = {
    calendarId: null,
    start: null,
    end: null
  };
  
  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--calendar-id' && args[i + 1]) {
      result.calendarId = args[i + 1];
      i++;
    } else if (args[i] === '--start' && args[i + 1]) {
      result.start = args[i + 1];
      i++;
    } else if (args[i] === '--end' && args[i + 1]) {
      result.end = args[i + 1];
      i++;
    }
  }
  
  return result;
}

function formatDate(dateStr) {
  if (!dateStr) return '';
  const d = new Date(dateStr);
  return d.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit'
  });
}

async function listEvents() {
  const { calendarId, start, end } = parseArgs();
  
  if (!calendarId) {
    console.error('用法: node scripts/list-events.js --calendar-id <id> [--start YYYY-MM-DD] [--end YYYY-MM-DD]');
    console.error('示例: node scripts/list-events.js --calendar-id abc123 --start 2026-04-01 --end 2026-04-30');
    process.exit(1);
  }
  
  try {
    let url = `${API_URL}/api/calendars/${calendarId}/events`;
    const params = [];
    if (start) params.push(`start=${start}`);
    if (end) params.push(`end=${end}`);
    if (params.length > 0) url += '?' + params.join('&');
    
    const response = await fetch(url, {
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json'
      }
    });

    const data = await response.json();

    if (!response.ok) {
      if (response.status === 404) {
        console.error(`日历不存在: ${calendarId}`);
        console.error('请运行: node scripts/list-calendars.js 查看可用日历');
      } else {
        console.error(`请求失败: ${data.message || response.statusText}`);
      }
      process.exit(1);
    }

    if (!data.events || data.events.length === 0) {
      console.log('该日历暂无事件');
      if (start || end) {
        console.log(`筛选条件: ${start || '...'} 至 ${end || '...'}`);
      }
      console.log('运行以下命令添加事件：');
      console.log(`  node scripts/create-event.js --calendar-id ${calendarId} --title "事件标题" --start-date 2026-04-15`);
      return;
    }

    const filter = start || end ? ` (${start || '...'} 至 ${end || '...'})` : '';
    console.log(`共 ${data.events.length} 个事件${filter}：\n`);
    
    data.events.forEach((evt, i) => {
      console.log(`--- ${i + 1}. ${evt.title} ---`);
      console.log(`ID: ${evt.id}`);
      console.log(`时间: ${formatDate(evt.startDate)}`);
      if (evt.startTime) console.log(`时间: ${evt.startTime} - ${evt.endTime || evt.startTime}`);
      if (evt.description) console.log(`描述: ${evt.description}`);
      if (evt.location) console.log(`地点: ${evt.location}`);
      if (evt.alarm) console.log(`提醒: 提前 ${evt.alarmMinutes} 分钟`);
      console.log('');
    });
  } catch (err) {
    console.error('请求失败:', err.message);
    process.exit(1);
  }
}

listEvents();


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)