Skip to content

feat(cli): generate Calendar event commands with chrono natural language time#6

Merged
yurenju merged 5 commits into
mainfrom
claude/cli-cal-67294
May 27, 2026
Merged

feat(cli): generate Calendar event commands with chrono natural language time#6
yurenju merged 5 commits into
mainfrom
claude/cli-cal-67294

Conversation

@yurenju
Copy link
Copy Markdown
Contributor

@yurenju yurenju commented May 27, 2026

新增 Calendar Events 相關的六個 CLI 指令(add / ls / set / show / rm / ics),藉由擴充 tools/cli-codegen/emit.ts 讓 OpenAPI x-cli 註記可以宣告式驅動 chrono 自然語言時間解析、多重 --attendee flag、API 欄位改名、ICS raw 下載等行為。

配合的主 repo PR

https://github.com/sadcoderlabs/wspc/pull/361 — 這個 PR 提供了新的 x-cli 註記與 spec sync 來源,需要先合進去。

變更分層

  1. 基礎工具 (f2fe1e3):加 chrono-node@2.9.1 / luxon@3.7.2,新建 src/handwritten/utils/parse-time.ts / parse-date.ts / parse-attendee.ts,含完整單元測試。parseTimeInput 對 naive ISO(2026-05-12T10:00 沒 offset)會走 chrono 路徑套用 --tz,不會悄悄掉到 system zone。
  2. Codegen 擴充 (7e7abb2):emit.ts 加入 XCliOption 型別與五種屬性(parser / array / mapsTo / allDayFlag / exclusive)。生成的 action body 自動注入 resolveTimezone(opts.tz)parseTimeInput(...).toISO()parseAttendee 等邏輯;只在實際使用時 import 工具。event_ics_download 特判把 id 轉成 filename: \${id}.icsallDayFlag 在 body / query 已有同名欄位時不重複 emit。新增 7 個 codegen 單元測試覆蓋每條新分支。
  3. 整合 + E2E test (de19040):確認 src/cli.ts 自動註冊 event 群組;新增 test/generated/event.test.ts 5 個整合測試,用 vi.mock 攔 SDK 並斷言請求 shape;以 vi.useFakeTimers() + process.env.WSPC_TZ 鎖確定性。每個 test 用 vi.resetModules() 重 import 拿全新 Commander 實例,避免 array accumulator default 跨測試洩漏。
  4. Renderer 升級 (1846f69):XCliDisplay.shape 新增 \"raw\",渲染時跳過 TTY / JSON 判斷直接 process.stdout.write — 讓 wspc event ics evt_xxx > meeting.ics 寫出真實 ICS 而非 JSON-escaped 字串。renderObject 補上 array 欄位處理,把 attendees 渲染成 `N items` + 編號 sub-list(`Alice alice@example.com` / `bob@example.com` 格式)。
  5. Regenerate (1e753d6):同步主 repo 的 openapi.json 並重跑 npm run generate,讓 ics.tsdisplay: { shape: \"raw\" }rm.tsdeleted_at: relative-time formatter。

端對端驗證(以真實 production 帳號)

```
wspc event add 'Final UX check' --start 'tomorrow 10am' --end 'tomorrow 11am' --tz 'Asia/Taipei'
--attendee 'Alice alice@example.com' --attendee 'bob@example.com' --location 'Zoom'
wspc event ls --from today --to 'next week' --tz 'Asia/Taipei'
wspc event ics evt_xxx > meeting.ics
wspc event rm evt_xxx
```

確認:`tomorrow 10am` 解析成 `2026-05-28T10:00:00.000+08:00`、`Alice a@x` 拆成 `{email, display_name}`、`--from`/`--to` 送出去變成 `start_from`/`start_to`、redirect 寫的 `.ics` 檔可被 Calendar app 解析、`event rm` 顯示 `deleted_at: just now`。

測試

  • `npm test`:84 → 89 passed
  • `npm run typecheck`:clean
  • `npm run generate`:冪等

設計上一個 opinionated 決定

`raw` shape 壓過 `--json`:即使使用者下 `wspc event ics evt_xxx --json` 也吐 raw ICS。理由:把 ICS body 包進 JSON 等於對 `\r\n` 再 escape 一次,沒有 jq / AI consumer 想要這樣。Code comment 跟 commit message 都有記。

yurenju added 5 commits May 27, 2026 16:48
…ct view

- New 'raw' shape bypasses JSON/TTY logic and writes data verbatim so
  payloads like .ics text survive redirection without JSON quoting. It
  intentionally wins over --json/WSPC_OUTPUT because the upstream body is
  already the user-facing artifact.
- renderObject now emits non-scalar array fields as an indented sub-list
  beneath the scalar rows, recognising the common {email, display_name}
  attendee shape and capping output at 10 items with an overflow line.
- event ics passes display.shape='raw' so '> file.ics' writes valid
  iCalendar text instead of a JSON-escaped string.
- event rm passes display.shape='object' with id-short + relative-time
  formatters for deleted_at.
@yurenju yurenju merged commit 413040d into main May 27, 2026
1 check passed
@yurenju yurenju deleted the claude/cli-cal-67294 branch May 27, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant