Install
openclaw skills install pingcode-timeloggerAutomate PingCode timesheet filling — create sub-tasks and log work hours. Use when asked to fill PingCode timesheets, log work hours, create work items, or...
openclaw skills install pingcode-timeloggerAutomate PingCode timesheet: create sub-tasks under a parent work item, set properties, and log work hours. Supports manual task lists and auto-generation from Git commits.
Config lives at ~/.openclaw/skills/pingcode-timelogger/config.yaml. Read it first on every invocation.
If config doesn't exist, guide the user through first-time setup:
references/config-template.yaml in this skill directorypingcode:
url: https://company.pingcode.com # PingCode instance URL
cookie_file: ./cookies.txt # Path to cookie file (relative to config dir)
project_id: "" # Project _id (24-char hex)
default_parent_id: "" # Default parent work item _id
user_id: "" # Assignee user _id
task_type_id: "" # "任务" type _id
completed_state_id: "" # "已完成" state _id
git:
provider: github # github or gitlab
url: https://api.github.com # API base URL
token_file: ./git-token.txt # Path to token file (relative to config dir)
username: "" # Git username
repos: [] # Repos to scan, e.g. ["owner/repo1"]
defaults:
work_category: 研发 # 研发/设计/部署/测试/文档/产品/调研/其他
assignee_name: "" # Display name for confirmation messages
Two strategies, with automatic fallback:
Read cookie from cookie_file in config. Use credentials: 'include' or Cookie header for all API calls.
If API calls fail (401/403), fall back to browser automation:
Always try API first. Only fall back to browser when API auth fails.
Before creating anything, always present the full plan to the user and wait for explicit confirmation:
Two modes depending on the project:
Simple parent → child tasks. Each task is a direct sub-item under the parent story/task.
Three-level structure aligned with delivery phases:
Story (需求)
├── 前端开发 (task) ← phase node, sub-tasks underneath
│ ├── sub-task: feature A
│ └── sub-task: feature B
├── 后端开发 (task) ← phase node, sub-tasks underneath
│ └── sub-task: API work
├── 自测 (task) ← phase node, usually no children
├── 联调 (task) ← phase node
├── 研发自扫 (task) ← phase node
├── 产品文档 (task) ← phase node
├── PRD文档 (task) ← phase node
├── 测试 (task) ← phase node, may have children (test cases, test report)
├── 上线准备 (task) ← phase node, may have children (manual, release plan)
└── 上线文档梳理 (task) ← phase node
Rules for Mode B:
estimated_workload) = registered hours (same value)Determining the mode: Check the project identifier prefix (SMA → Mode A, RD-IT → Mode B). If unclear, ask the user.
User provides one of:
https://company.pingcode.com/pjm/projects/SMA/work-items/SMA-348 → look up SMA-348SMA-348 → GET /api/agile/work-items/SMA-348 to get _iddefault_parent_id from configFor Mode B, also check existing children of the story:
GET {pingcode_url}/api/agile/work-items/{story_id}/children
Reuse existing phase tasks (e.g., if "后端开发" already exists, create sub-tasks under it).
For each task in the list:
POST {pingcode_url}/api/agile/work-item
Cookie: <from cookie_file>
{
"property_values": [
{"key": "title", "value": "<task title>"},
{"key": "type", "value": "<task_type_id from config>"},
{"key": "project_id", "value": "<project_id from config>"},
{"key": "assignee", "value": "<user_id from config>"},
{"key": "parent_id", "value": "<parent _id>"},
{"key": "due", "value": null},
{"key": "phase", "value": null}
]
}
Note: Endpoint is /api/agile/work-item (singular), NOT /api/agile/work-items.
PUT {pingcode_url}/api/agile/work-items/{_id}/property
Cookie: <from cookie_file>
# Set start date:
{"key": "start", "value": {"date": <unix_timestamp>}}
# Set end date:
{"key": "due", "value": {"date": <unix_timestamp>}}
Batch set status to completed:
POST {pingcode_url}/api/agile/work-items/state
Cookie: <from cookie_file>
{
"state_id": "<completed_state_id from config>",
"work_item_ids": ["id1", "id2", ...],
"bypass": 0
}
PUT {pingcode_url}/api/agile/work-items/{_id}/property
Cookie: <from cookie_file>
{"key": "estimated_workload", "value": <hours_as_number>}
⚠️ The value is in HOURS (not seconds). Do NOT multiply by 3600.
Set estimated workload = registered hours (same value) on every task/sub-task that has hours logged.
POST {pingcode_url}/api/ladon/workload/register
Cookie: <from cookie_file>
{
"principal_type": "work_item",
"principal_id": "<task_id>",
"man_hour": <hours>,
"remaining_workload": 0,
"register_date": <cst_midnight_unix_ts>,
"work_category_id": "<category_id>",
"work_content": "<task description>",
"pilot_id": "<project_id>"
}
⚠️ pilot_id is required — set it to the project's _id (same as project_id).
work_category_id mapping — read references/category-ids.md for the full list. Common: 研发=5cb7e7fffda1ce4ca0050002.
register_date: Must be CST (UTC+8) midnight timestamp. Formula: new Date('YYYY-MM-DDT00:00:00+08:00').getTime() / 1000
Distribute hours across weekdays (Mon–Fri). Never pile all hours on one day.
When user says "根据Git提交填工时" or similar:
config.yaml (provider, token, repos)references/git-integration.md/api/agile/work-item for creation| Error | Action |
|---|---|
| 401/403 on API | Switch to browser fallback |
| Cookie expired | Ask user to re-export cookie or log in via browser |
| Parent is Epic (type=2) | Cannot create task directly under Epic; find or create a User Story first |
| work_category_id missing | Required field since 2026-04; always include it |
| pilot_id not found | Add pilot_id: <project_id> to workload register request |
| estimated_workload too large | Unit is hours, not seconds; do NOT multiply by 3600 |
If config file doesn't exist, walk user through setup:
mkdir -p ~/.openclaw/skills/pingcode-timeloggerreferences/config-template.yaml and save to config dir