@fukuchang/taskchute-cli 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  このパッケージの変更履歴。[Keep a Changelog](https://keepachangelog.com/) 準拠。
4
4
 
5
+ ## [0.6.0] - 2026-10-22 — Node.js セキュリティ修正版を要求
6
+
7
+ ### Changed
8
+
9
+ - 対応 Node.js を 22.23.4 以降の22系、24.22.1 以降の24系、26.12.1 以降の26系に変更。
10
+ - Node.js 20 のサポートを終了し、ビルド対象を Node.js 22 に変更。
11
+ - CLI の更新とは別に、実行環境の Node.js 本体も更新する必要があります。
12
+
5
13
  ## [0.3.0] - 2026-05-28 — 実績時刻の秒精度サポート
6
14
 
7
15
  ### Added
@@ -87,7 +95,7 @@ Web の Routine / TimeBlock ページの write 操作を CLI から完結でき
87
95
  - `taskchute routines delete <id-prefix>` (alias `rm`) — 物理削除 (task の
88
96
  routine_id は NULL 化)
89
97
  - `taskchute time-blocks create <name> --start-time HH:MM --end-time HH:MM
90
- --weekdays 1,2,3,4,5 [...]` — タイムブロック新規作成
98
+ --weekdays 1,2,3,4,5 [...]` — タイムブロック新規作成
91
99
  - `taskchute time-blocks update / archive / delete (rm)` (同パターン)
92
100
  - `src/format/parse-frequency.ts` を新設。`parseFrequencySpec` で `--frequency`
93
101
  の短縮表記を domain の discriminated union に変換。`parseWeekdaysList` で
@@ -212,9 +220,9 @@ Web の Today で当たり前にできる task 操作を CLI からも全部で
212
220
 
213
221
  - npm provenance (sigstore attestation) は付与していない。sigstore は
214
222
  private repo の OIDC token を検証対象外としているため、private repo
215
- + provenance は構造上成立しない。Trusted Publisher (OIDC 限定 publish)
216
- の保証自体は維持されている。将来 repo を public 化する判断をした
217
- 時点で `--provenance` を有効化する。
223
+ - provenance は構造上成立しない。Trusted Publisher (OIDC 限定 publish)
224
+ の保証自体は維持されている。将来 repo を public 化する判断をした
225
+ 時点で `--provenance` を有効化する。
218
226
 
219
227
  ## [0.1.0] - 2026-05-23
220
228
 
package/README.md CHANGED
@@ -6,10 +6,13 @@ TaskChute (E2EE 暗号化されたタスク管理アプリ) のタスクをタ
6
6
 
7
7
  ## 必要なもの
8
8
 
9
- - Node.js 20 以上
9
+ - Node.js 22.23.4 以降の22系 / 24.22.1 以降の24系 / 26.12.1 以降の26系(24系を推奨、各系統の最新パッチ版を使用)
10
10
  - TaskChute Web アプリで作成済みのアカウント
11
11
  - E2EE セットアップ (Web アプリの「Settings → データの暗号化」で passphrase を設定済み)
12
12
 
13
+ [2026年10月のセキュリティ修正](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases)を含む Node.js が必要です。
14
+ CLI の更新だけでは Node.js 本体は更新されません。実行環境の Node.js も更新してください。
15
+
13
16
  > ⚠️ タスクデータは end-to-end 暗号化されているため、復号には passphrase が必須。
14
17
  > Web アプリで E2EE をセットアップしていない状態では CLI からタスクを操作できない。
15
18
 
@@ -31,11 +34,11 @@ taskchute --help
31
34
 
32
35
  CLI は Supabase に直接接続するため、接続情報を環境変数で渡す必要がある。
33
36
 
34
- | 変数名 | 必須 | 用途 |
35
- | --- | --- | --- |
36
- | `TASKCHUTE_SUPABASE_URL` | 必須 | Supabase プロジェクトの URL |
37
- | `TASKCHUTE_SUPABASE_PUBLISHABLE_KEY` | 必須 | Supabase の publishable (anon) key |
38
- | `TASKCHUTE_PASSPHRASE` | 任意 | E2EE passphrase。設定すれば対話プロンプトをスキップ。CI/自動化向け |
37
+ | 変数名 | 必須 | 用途 |
38
+ | ------------------------------------ | ---- | ------------------------------------------------------------------ |
39
+ | `TASKCHUTE_SUPABASE_URL` | 必須 | Supabase プロジェクトの URL |
40
+ | `TASKCHUTE_SUPABASE_PUBLISHABLE_KEY` | 必須 | Supabase の publishable (anon) key |
41
+ | `TASKCHUTE_PASSPHRASE` | 任意 | E2EE passphrase。設定すれば対話プロンプトをスキップ。CI/自動化向け |
39
42
 
40
43
  `.zshrc` などに書いておくのが楽。
41
44
 
@@ -68,52 +71,52 @@ taskchute tasks done <taskId-or-index>
68
71
 
69
72
  ## コマンド一覧
70
73
 
71
- | Command | 説明 |
72
- | --- | --- |
73
- | `taskchute login` | email + password で Supabase にログインし、session を OS keyring に保存 |
74
- | `taskchute whoami` | 現在ログイン中のユーザーを表示 |
75
- | `taskchute logout` | ログアウト (keyring から session 削除) |
76
- | `taskchute unlock` | E2EE passphrase を keyring に保存し、以降のコマンドを対話なしに走らせる |
77
- | `taskchute lock` | keyring から passphrase を削除 (session は維持) |
78
- | `taskchute tasks list [--date YYYY-MM-DD]` | タスク一覧表示 |
79
- | `taskchute tasks add <title> [--estimated 30m] [--date <date>] [--project <prefix>] [--context <prefix>] [--tag <prefix>...] [--scheduled HH:MM] [--description <text>] [--notify] [--background]` | タスク追加 |
80
- | `taskchute tasks update <id-prefix> [--title ...] [--description ...] [--clear-description] [--estimated 1h] [--project <prefix>] [--clear-project] [--context <prefix>] [--clear-context] [--tag <prefix>...] [--no-tags] [--scheduled HH:MM] [--clear-scheduled] [--notify] [--no-notify] [--background] [--no-background]` | フィールド部分更新。`--clear-X` で明示クリア、`--no-tags` で全タグ外し |
81
- | `taskchute tasks delete <id-prefix> [--hard]` / `rm` | タスク削除。既定は Routine 由来なら今日以降は skip 記録、`--hard` で物理削除のみ |
82
- | `taskchute tasks start <id-prefix>` | タスク開始 |
83
- | `taskchute tasks pause <id-prefix>` | タスク中断 |
84
- | `taskchute tasks done <id-prefix>` | タスク完了 |
85
- | `taskchute tasks skip <id-prefix>` | タスクをスキップ (status → skipped) |
86
- | `taskchute tasks reopen <id-prefix>` | completed / skipped を planned に戻す |
87
- | `taskchute tasks recent [--days 90] [--query <text>] [--limit 100]` | 過去 N 日のタスク (fuzzy match 対応) |
88
- | `taskchute projects list [--archived | --all]` | プロジェクト一覧 |
89
- | `taskchute tags list [--archived | --all]` | タグ一覧 |
90
- | `taskchute contexts list [--archived | --all]` | コンテキスト一覧 |
91
- | `taskchute routines list [--paused | --archived | --all]` | ルーチン一覧 (frequency は `weekly:1,3,5` のような短縮表記で表示) |
92
- | `taskchute time-blocks list [--archived | --all]` | タイムブロック一覧 |
93
- | `taskchute projects create <name> [--description ...] [--color ...] [--parent <prefix>] [--started-at YYYY-MM-DD] [--target-end YYYY-MM-DD]` | プロジェクト新規作成 |
94
- | `taskchute projects update <id-prefix> [--name ...] [--description ...] [--clear-description] [--color ...] [--clear-color] [--parent <prefix>] [--clear-parent] [--started-at YYYY-MM-DD] [--clear-started-at] [--target-end YYYY-MM-DD] [--clear-target-end]` | プロジェクト部分更新 |
95
- | `taskchute projects archive <id-prefix>` | アーカイブ (タスクの project_id 維持) |
96
- | `taskchute projects delete <id-prefix>` / `rm` | 物理削除 (タスクの project_id は NULL 化) |
97
- | `taskchute tags create <name> [--description ...] [--color ...]` | タグ新規作成 |
98
- | `taskchute tags update <id-prefix> [--name ...] [--description ...] [--clear-description] [--color ...] [--clear-color]` | タグ部分更新 |
99
- | `taskchute tags archive <id-prefix>` | アーカイブ (タスクの tag_ids 配列維持) |
100
- | `taskchute tags delete <id-prefix>` / `rm` | 物理削除 (tag_ids 配列に孤児 id が残るので注意) |
101
- | `taskchute contexts create <name> [--description ...] [--icon ...] [--color ...]` | コンテキスト新規作成 |
102
- | `taskchute contexts update <id-prefix> [--name ...] [--description ...] [--clear-description] [--icon ...] [--clear-icon] [--color ...] [--clear-color]` | コンテキスト部分更新 |
103
- | `taskchute contexts archive <id-prefix>` | アーカイブ |
104
- | `taskchute contexts delete <id-prefix>` / `rm` | 物理削除 (タスクの context_id は NULL 化) |
105
- | `taskchute routines create <title> --estimated <dur> --frequency <spec> [...]` | ルーチン新規作成。`--frequency` は `daily` / `weekly:1,3,5` / `monthly:1,15` / `custom:CRON` |
106
- | `taskchute routines update <id-prefix> [...]` | ルーチン部分更新 (**未来 planned task に自動伝播**) |
107
- | `taskchute routines archive <id-prefix>` | アーカイブ (既存 task の routine_id 維持) |
108
- | `taskchute routines delete <id-prefix>` / `rm` | 物理削除 (task の routine_id は NULL 化) |
109
- | `taskchute time-blocks create <name> --start-time HH:MM --end-time HH:MM --weekdays 1,2,3 [...]` | タイムブロック新規作成 (`weekdays` は 0=日 ~ 6=土) |
110
- | `taskchute time-blocks update <id-prefix> [...]` | タイムブロック部分更新 |
111
- | `taskchute time-blocks archive <id-prefix>` | アーカイブ |
112
- | `taskchute time-blocks delete <id-prefix>` / `rm` | 物理削除 |
113
- | `taskchute tasks set-actual-times <id-prefix> [--start HH:MM] [--end HH:MM] [--clear-start] [--clear-end] [--reset]` | 実績開始/終了時刻を直接指定 (Play 押し忘れリカバリ用) |
114
- | `taskchute tasks interrupt <target-id-prefix> <title> --estimated <dur> [...]` | 走行中タスクを「割り込み」で分割 (3 タスクを 1 操作で生成) |
115
- | `taskchute tasks parallel <title> --estimated <dur> [--no-start-immediately] [--background] [...]` | 並列タスク追加 (既定で即計測開始) |
116
- | `taskchute tasks reorder <id-prefix> --order-index <n> [--scheduled HH:MM] [--clear-scheduled]` | 並び順 (orderIndex) と所属タイムブロックを直接指定 |
74
+ | Command | 説明 |
75
+ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------- |
76
+ | `taskchute login` | email + password で Supabase にログインし、session を OS keyring に保存 |
77
+ | `taskchute whoami` | 現在ログイン中のユーザーを表示 |
78
+ | `taskchute logout` | ログアウト (keyring から session 削除) |
79
+ | `taskchute unlock` | E2EE passphrase を keyring に保存し、以降のコマンドを対話なしに走らせる |
80
+ | `taskchute lock` | keyring から passphrase を削除 (session は維持) |
81
+ | `taskchute tasks list [--date YYYY-MM-DD]` | タスク一覧表示 |
82
+ | `taskchute tasks add <title> [--estimated 30m] [--date <date>] [--project <prefix>] [--context <prefix>] [--tag <prefix>...] [--scheduled HH:MM] [--description <text>] [--notify] [--background]` | タスク追加 |
83
+ | `taskchute tasks update <id-prefix> [--title ...] [--description ...] [--clear-description] [--estimated 1h] [--project <prefix>] [--clear-project] [--context <prefix>] [--clear-context] [--tag <prefix>...] [--no-tags] [--scheduled HH:MM] [--clear-scheduled] [--notify] [--no-notify] [--background] [--no-background]` | フィールド部分更新。`--clear-X` で明示クリア、`--no-tags` で全タグ外し |
84
+ | `taskchute tasks delete <id-prefix> [--hard]` / `rm` | タスク削除。既定は Routine 由来なら今日以降は skip 記録、`--hard` で物理削除のみ |
85
+ | `taskchute tasks start <id-prefix>` | タスク開始 |
86
+ | `taskchute tasks pause <id-prefix>` | タスク中断 |
87
+ | `taskchute tasks done <id-prefix>` | タスク完了 |
88
+ | `taskchute tasks skip <id-prefix>` | タスクをスキップ (status → skipped) |
89
+ | `taskchute tasks reopen <id-prefix>` | completed / skipped を planned に戻す |
90
+ | `taskchute tasks recent [--days 90] [--query <text>] [--limit 100]` | 過去 N 日のタスク (fuzzy match 対応) |
91
+ | `taskchute projects list [--archived | --all]` | プロジェクト一覧 |
92
+ | `taskchute tags list [--archived | --all]` | タグ一覧 |
93
+ | `taskchute contexts list [--archived | --all]` | コンテキスト一覧 |
94
+ | `taskchute routines list [--paused | --archived | --all]` | ルーチン一覧 (frequency は `weekly:1,3,5` のような短縮表記で表示) |
95
+ | `taskchute time-blocks list [--archived | --all]` | タイムブロック一覧 |
96
+ | `taskchute projects create <name> [--description ...] [--color ...] [--parent <prefix>] [--started-at YYYY-MM-DD] [--target-end YYYY-MM-DD]` | プロジェクト新規作成 |
97
+ | `taskchute projects update <id-prefix> [--name ...] [--description ...] [--clear-description] [--color ...] [--clear-color] [--parent <prefix>] [--clear-parent] [--started-at YYYY-MM-DD] [--clear-started-at] [--target-end YYYY-MM-DD] [--clear-target-end]` | プロジェクト部分更新 |
98
+ | `taskchute projects archive <id-prefix>` | アーカイブ (タスクの project_id 維持) |
99
+ | `taskchute projects delete <id-prefix>` / `rm` | 物理削除 (タスクの project_id は NULL 化) |
100
+ | `taskchute tags create <name> [--description ...] [--color ...]` | タグ新規作成 |
101
+ | `taskchute tags update <id-prefix> [--name ...] [--description ...] [--clear-description] [--color ...] [--clear-color]` | タグ部分更新 |
102
+ | `taskchute tags archive <id-prefix>` | アーカイブ (タスクの tag_ids 配列維持) |
103
+ | `taskchute tags delete <id-prefix>` / `rm` | 物理削除 (tag_ids 配列に孤児 id が残るので注意) |
104
+ | `taskchute contexts create <name> [--description ...] [--icon ...] [--color ...]` | コンテキスト新規作成 |
105
+ | `taskchute contexts update <id-prefix> [--name ...] [--description ...] [--clear-description] [--icon ...] [--clear-icon] [--color ...] [--clear-color]` | コンテキスト部分更新 |
106
+ | `taskchute contexts archive <id-prefix>` | アーカイブ |
107
+ | `taskchute contexts delete <id-prefix>` / `rm` | 物理削除 (タスクの context_id は NULL 化) |
108
+ | `taskchute routines create <title> --estimated <dur> --frequency <spec> [...]` | ルーチン新規作成。`--frequency` は `daily` / `weekly:1,3,5` / `monthly:1,15` / `custom:CRON` |
109
+ | `taskchute routines update <id-prefix> [...]` | ルーチン部分更新 (**未来 planned task に自動伝播**) |
110
+ | `taskchute routines archive <id-prefix>` | アーカイブ (既存 task の routine_id 維持) |
111
+ | `taskchute routines delete <id-prefix>` / `rm` | 物理削除 (task の routine_id は NULL 化) |
112
+ | `taskchute time-blocks create <name> --start-time HH:MM --end-time HH:MM --weekdays 1,2,3 [...]` | タイムブロック新規作成 (`weekdays` は 0=日 ~ 6=土) |
113
+ | `taskchute time-blocks update <id-prefix> [...]` | タイムブロック部分更新 |
114
+ | `taskchute time-blocks archive <id-prefix>` | アーカイブ |
115
+ | `taskchute time-blocks delete <id-prefix>` / `rm` | 物理削除 |
116
+ | `taskchute tasks set-actual-times <id-prefix> [--start HH:MM] [--end HH:MM] [--clear-start] [--clear-end] [--reset]` | 実績開始/終了時刻を直接指定 (Play 押し忘れリカバリ用) |
117
+ | `taskchute tasks interrupt <target-id-prefix> <title> --estimated <dur> [...]` | 走行中タスクを「割り込み」で分割 (3 タスクを 1 操作で生成) |
118
+ | `taskchute tasks parallel <title> --estimated <dur> [--no-start-immediately] [--background] [...]` | 並列タスク追加 (既定で即計測開始) |
119
+ | `taskchute tasks reorder <id-prefix> --order-index <n> [--scheduled HH:MM] [--clear-scheduled]` | 並び順 (orderIndex) と所属タイムブロックを直接指定 |
117
120
 
118
121
  詳細オプションは `taskchute <command> --help` で確認。
119
122
 
package/dist/index.js CHANGED
@@ -22,9 +22,7 @@ function readSupabaseConfigFromEnv() {
22
22
  const url = process.env.TASKCHUTE_SUPABASE_URL;
23
23
  const publishableKey = process.env.TASKCHUTE_SUPABASE_PUBLISHABLE_KEY;
24
24
  if (!url || !publishableKey) {
25
- throw new Error(
26
- `Supabase \u306E\u74B0\u5883\u5909\u6570\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u3002${ENV_URL} \u3068 ${ENV_KEY} \u3092\u8A2D\u5B9A\u3057\u3066\u306D\u301C`
27
- );
25
+ throw new Error(`Supabase \u306E\u74B0\u5883\u5909\u6570\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u3002${ENV_URL} \u3068 ${ENV_KEY} \u3092\u8A2D\u5B9A\u3057\u3066\u306D\u301C`);
28
26
  }
29
27
  return { url, publishableKey };
30
28
  }
@@ -203,13 +201,10 @@ async function deriveKeyMaterial(password4, salt, params = DEFAULT_ARGON2ID_PARA
203
201
  return copyToArrayBuffer(hash);
204
202
  }
205
203
  async function importKEK(material) {
206
- return crypto.subtle.importKey(
207
- "raw",
208
- material,
209
- { name: "AES-GCM" },
210
- false,
211
- ["wrapKey", "unwrapKey"]
212
- );
204
+ return crypto.subtle.importKey("raw", material, { name: "AES-GCM" }, false, [
205
+ "wrapKey",
206
+ "unwrapKey"
207
+ ]);
213
208
  }
214
209
  async function deriveKEK(password4, salt, params = DEFAULT_ARGON2ID_PARAMS) {
215
210
  const material = await deriveKeyMaterial(password4, salt, params);
@@ -413,11 +408,7 @@ async function unlockMasterKey(snapshot, method) {
413
408
  snapshot.passphrase.kdfParams
414
409
  );
415
410
  try {
416
- return await unwrapMasterKey(
417
- snapshot.passphrase.wrapped,
418
- snapshot.passphrase.iv,
419
- kek
420
- );
411
+ return await unwrapMasterKey(snapshot.passphrase.wrapped, snapshot.passphrase.iv, kek);
421
412
  } catch {
422
413
  throw new UnlockError("Wrong passphrase", "wrong-key");
423
414
  }
@@ -433,11 +424,7 @@ async function unlockMasterKey(snapshot, method) {
433
424
  );
434
425
  const kek = await importKEK(material);
435
426
  try {
436
- return await unwrapMasterKey(
437
- snapshot.recovery.wrapped,
438
- snapshot.recovery.iv,
439
- kek
440
- );
427
+ return await unwrapMasterKey(snapshot.recovery.wrapped, snapshot.recovery.iv, kek);
441
428
  } catch {
442
429
  throw new UnlockError("Wrong recovery key", "wrong-key");
443
430
  }
@@ -491,13 +478,10 @@ function dateToIsoOrNull(date) {
491
478
  // ../infra-supabase/src/supabase-context-repository.ts
492
479
  async function rowToContext(row, cryptoSession) {
493
480
  const [name, description] = await Promise.all([
494
- decryptRequiredTextFromWire(
495
- cryptoSession,
496
- row.name_ciphertext,
497
- row.name_iv,
498
- row.key_id,
499
- { entityId: row.id, field: "name" }
500
- ),
481
+ decryptRequiredTextFromWire(cryptoSession, row.name_ciphertext, row.name_iv, row.key_id, {
482
+ entityId: row.id,
483
+ field: "name"
484
+ }),
501
485
  decryptOptionalTextFromWire(
502
486
  cryptoSession,
503
487
  row.description_ciphertext,
@@ -614,13 +598,10 @@ var SupabaseContextRepository = class {
614
598
  // ../infra-supabase/src/supabase-project-repository.ts
615
599
  async function rowToProject(row, cryptoSession) {
616
600
  const [name, description] = await Promise.all([
617
- decryptRequiredTextFromWire(
618
- cryptoSession,
619
- row.name_ciphertext,
620
- row.name_iv,
621
- row.key_id,
622
- { entityId: row.id, field: "name" }
623
- ),
601
+ decryptRequiredTextFromWire(cryptoSession, row.name_ciphertext, row.name_iv, row.key_id, {
602
+ entityId: row.id,
603
+ field: "name"
604
+ }),
624
605
  decryptOptionalTextFromWire(
625
606
  cryptoSession,
626
607
  row.description_ciphertext,
@@ -774,13 +755,10 @@ var SupabaseRoutineExpansionRepository = class {
774
755
  // ../infra-supabase/src/supabase-routine-repository.ts
775
756
  async function rowToRoutine(row, cryptoSession) {
776
757
  const [title, description] = await Promise.all([
777
- decryptRequiredTextFromWire(
778
- cryptoSession,
779
- row.title_ciphertext,
780
- row.title_iv,
781
- row.key_id,
782
- { entityId: row.id, field: "title" }
783
- ),
758
+ decryptRequiredTextFromWire(cryptoSession, row.title_ciphertext, row.title_iv, row.key_id, {
759
+ entityId: row.id,
760
+ field: "title"
761
+ }),
784
762
  decryptOptionalTextFromWire(
785
763
  cryptoSession,
786
764
  row.description_ciphertext,
@@ -921,13 +899,10 @@ var SupabaseRoutineRepository = class {
921
899
  // ../infra-supabase/src/supabase-tag-repository.ts
922
900
  async function rowToTag(row, cryptoSession) {
923
901
  const [name, description] = await Promise.all([
924
- decryptRequiredTextFromWire(
925
- cryptoSession,
926
- row.name_ciphertext,
927
- row.name_iv,
928
- row.key_id,
929
- { entityId: row.id, field: "name" }
930
- ),
902
+ decryptRequiredTextFromWire(cryptoSession, row.name_ciphertext, row.name_iv, row.key_id, {
903
+ entityId: row.id,
904
+ field: "name"
905
+ }),
931
906
  decryptOptionalTextFromWire(
932
907
  cryptoSession,
933
908
  row.description_ciphertext,
@@ -1038,13 +1013,10 @@ var SupabaseTagRepository = class {
1038
1013
  // ../infra-supabase/src/supabase-task-note-repository.ts
1039
1014
  async function rowToTaskNote(row, cryptoSession) {
1040
1015
  const [body, metaJson, historyJson] = await Promise.all([
1041
- decryptRequiredTextFromWire(
1042
- cryptoSession,
1043
- row.body_ciphertext,
1044
- row.body_iv,
1045
- row.key_id,
1046
- { entityId: row.id, field: "body" }
1047
- ),
1016
+ decryptRequiredTextFromWire(cryptoSession, row.body_ciphertext, row.body_iv, row.key_id, {
1017
+ entityId: row.id,
1018
+ field: "body"
1019
+ }),
1048
1020
  decryptOptionalTextFromWire(
1049
1021
  cryptoSession,
1050
1022
  row.metadata_ciphertext,
@@ -1135,9 +1107,7 @@ async function encryptTaskNoteFieldsInto(row, entityId, patch, cryptoSession) {
1135
1107
  if (touched) {
1136
1108
  const keyId = cryptoSession.getCurrentKeyId();
1137
1109
  if (keyId === null) {
1138
- throw new Error(
1139
- "SupabaseTaskNoteRepository: session became locked mid-write"
1140
- );
1110
+ throw new Error("SupabaseTaskNoteRepository: session became locked mid-write");
1141
1111
  }
1142
1112
  row.key_id = keyId;
1143
1113
  }
@@ -1164,9 +1134,7 @@ var SupabaseTaskNoteRepository = class {
1164
1134
  if (taskIds.length === 0) return result;
1165
1135
  const { data, error } = await this.supabase.from("task_notes").select("*").in("task_id", taskIds).order("created_at", { ascending: true });
1166
1136
  if (error) throw error;
1167
- const decoded = await Promise.all(
1168
- data.map((row) => rowToTaskNote(row, this.cryptoSession))
1169
- );
1137
+ const decoded = await Promise.all(data.map((row) => rowToTaskNote(row, this.cryptoSession)));
1170
1138
  for (const note of decoded) {
1171
1139
  const bucket = result.get(note.taskId);
1172
1140
  if (bucket) bucket.push(note);
@@ -1215,13 +1183,10 @@ var SupabaseTaskNoteRepository = class {
1215
1183
  // ../infra-supabase/src/supabase-task-repository.ts
1216
1184
  async function rowToTask(row, cryptoSession) {
1217
1185
  const [title, description] = await Promise.all([
1218
- decryptRequiredTextFromWire(
1219
- cryptoSession,
1220
- row.title_ciphertext,
1221
- row.title_iv,
1222
- row.key_id,
1223
- { entityId: row.id, field: "title" }
1224
- ),
1186
+ decryptRequiredTextFromWire(cryptoSession, row.title_ciphertext, row.title_iv, row.key_id, {
1187
+ entityId: row.id,
1188
+ field: "title"
1189
+ }),
1225
1190
  decryptOptionalTextFromWire(
1226
1191
  cryptoSession,
1227
1192
  row.description_ciphertext,
@@ -1665,20 +1630,41 @@ async function startTask(repo, taskId) {
1665
1630
  return repo.update(taskId, patch);
1666
1631
  }
1667
1632
  async function pauseTask(repo, taskId) {
1668
- const task = await repo.findById(taskId);
1669
- if (!task) throw new Error(`Task not found: ${taskId}`);
1670
- if (!task.currentSession) {
1633
+ const target = await repo.findById(taskId);
1634
+ if (!target) throw new Error(`Task not found: ${taskId}`);
1635
+ if (!target.currentSession) {
1671
1636
  throw new Error(`Task is not currently running: ${taskId}`);
1672
1637
  }
1673
- const newSession = {
1674
- startedAt: task.currentSession.startedAt,
1675
- finishedAt: /* @__PURE__ */ new Date()
1638
+ const now = /* @__PURE__ */ new Date();
1639
+ const closedSession = {
1640
+ startedAt: target.currentSession.startedAt,
1641
+ finishedAt: now
1676
1642
  };
1677
- return repo.update(taskId, {
1678
- status: "paused",
1679
- sessions: [...task.sessions, newSession],
1643
+ const finishedOriginal = await repo.update(target.id, {
1644
+ status: "completed",
1645
+ sessions: [...target.sessions, closedSession],
1680
1646
  currentSession: void 0
1681
1647
  });
1648
+ const elapsed = calculateActualSeconds(finishedOriginal);
1649
+ const continuation = await repo.create({
1650
+ date: target.date,
1651
+ title: target.title,
1652
+ description: target.description,
1653
+ estimatedSeconds: Math.max(0, Math.floor(target.estimatedSeconds - elapsed)),
1654
+ sessions: [],
1655
+ status: "planned",
1656
+ orderIndex: target.orderIndex + 1,
1657
+ splitFromTaskId: target.id,
1658
+ projectId: target.projectId,
1659
+ contextId: target.contextId,
1660
+ // continuation は「中断したタスクの残り」。中断時刻を予定時刻にすることで
1661
+ // ソート上は元タスクの直後 (effectiveTime 同値 + orderIndex 順) に並び、
1662
+ // TimeBlock 境界をまたいだ場合も自然に新しいブロックへ追随する。
1663
+ scheduledStartTime: toTimeString(now),
1664
+ tagIds: target.tagIds,
1665
+ isBackground: target.isBackground
1666
+ });
1667
+ return { finishedOriginal, continuation };
1682
1668
  }
1683
1669
  async function skipTask(repo, taskId) {
1684
1670
  const task = await repo.findById(taskId);
@@ -1833,19 +1819,13 @@ function buildSetActualTimesPatch(task, input2) {
1833
1819
  // ../domain/src/task-reorder.ts
1834
1820
  function computeNextOrderIndex(existingTasks) {
1835
1821
  if (existingTasks.length === 0) return 10;
1836
- const max = existingTasks.reduce(
1837
- (acc, t) => t.orderIndex > acc ? t.orderIndex : acc,
1838
- 0
1839
- );
1822
+ const max = existingTasks.reduce((acc, t) => t.orderIndex > acc ? t.orderIndex : acc, 0);
1840
1823
  return max + 10;
1841
1824
  }
1842
1825
 
1843
1826
  // ../domain/src/task-suggest.ts
1844
1827
  function normalizeForSearch(str) {
1845
- return str.trim().toLowerCase().replace(
1846
- /[ぁ-ゖ]/g,
1847
- (ch) => String.fromCharCode(ch.charCodeAt(0) + 96)
1848
- );
1828
+ return str.trim().toLowerCase().replace(/[ぁ-ゖ]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) + 96));
1849
1829
  }
1850
1830
  function fuzzyScore(query, target) {
1851
1831
  const q = query;
@@ -1908,7 +1888,6 @@ function findSimilarTasks(query, tasks, limit = 5) {
1908
1888
  function describeStatusTransition(from, to) {
1909
1889
  if (from === "planned" && to === "running") return "\u958B\u59CB";
1910
1890
  if (from === "paused" && to === "running") return "\u518D\u958B";
1911
- if (from === "running" && to === "paused") return "\u4E00\u6642\u505C\u6B62";
1912
1891
  if (to === "completed") return "\u5B8C\u4E86";
1913
1892
  if (to === "skipped") return "\u30B9\u30AD\u30C3\u30D7";
1914
1893
  if (to === "planned") return "\u672A\u7740\u624B\u306B\u623B\u3059";
@@ -2158,6 +2137,42 @@ async function interruptTaskWithAudit(taskRepo, taskNoteRepo, params) {
2158
2137
  }
2159
2138
  return result;
2160
2139
  }
2140
+ async function pauseTaskWithAudit(taskRepo, taskNoteRepo, taskId) {
2141
+ const result = await pauseTask(taskRepo, taskId);
2142
+ const notes = buildPauseSplitNoteInputs(result.finishedOriginal, result.continuation);
2143
+ try {
2144
+ await Promise.all(notes.map((input2) => taskNoteRepo.create(input2)));
2145
+ } catch (err) {
2146
+ console.error("[audit] failed to record pause split notes", {
2147
+ sourceTaskId: result.finishedOriginal.id,
2148
+ err
2149
+ });
2150
+ }
2151
+ return result;
2152
+ }
2153
+ function buildPauseSplitNoteInputs(source, continuation) {
2154
+ const sourceTitle = titleOrPlaceholder(source);
2155
+ return [
2156
+ {
2157
+ taskId: source.id,
2158
+ type: "split",
2159
+ body: `\u4E2D\u65AD\u3057\u3066\u6B8B\u4F5C\u696D\u3092\u5206\u5272\u3002\u7D9A\u304D\u306F\u5225\u30BF\u30B9\u30AF\u3067\u7D99\u7D9A\u3002`,
2160
+ metadata: {
2161
+ event: "pause-source",
2162
+ continuationTaskId: continuation.id
2163
+ }
2164
+ },
2165
+ {
2166
+ taskId: continuation.id,
2167
+ type: "split",
2168
+ body: `\u300C${sourceTitle}\u300D\u306E\u6B8B\u4F5C\u696D\u3068\u3057\u3066\u7D99\u7D9A\u3002`,
2169
+ metadata: {
2170
+ event: "pause-continuation",
2171
+ sourceTaskId: source.id
2172
+ }
2173
+ }
2174
+ ];
2175
+ }
2161
2176
  function buildSplitNoteInputs(source, interruption, continuation) {
2162
2177
  const sourceTitle = titleOrPlaceholder(source);
2163
2178
  const interruptionTitle = titleOrPlaceholder(interruption);
@@ -2194,8 +2209,6 @@ function buildSplitNoteInputs(source, interruption, continuation) {
2194
2209
  }
2195
2210
  ];
2196
2211
  }
2197
-
2198
- // ../domain/src/routine-expansion.ts
2199
2212
  function buildRoutineTaskPatch(routine) {
2200
2213
  return {
2201
2214
  title: routine.title,
@@ -2523,10 +2536,7 @@ function contextsListCommand() {
2523
2536
  });
2524
2537
  }
2525
2538
  function contextsUpdateCommand() {
2526
- return new Command("update").description("\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u90E8\u5206\u66F4\u65B0\u3059\u308B").argument("<id-prefix>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").option("--name <text>", "\u540D\u524D\u5909\u66F4").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--clear-description", "\u8A73\u7D30\u8AAC\u660E\u3092\u7A7A\u306B\u3059\u308B").option("--icon <icon>", "\u7D75\u6587\u5B57\u307E\u305F\u306F\u30A2\u30A4\u30B3\u30F3\u540D").option("--clear-icon", "\u30A2\u30A4\u30B3\u30F3\u3092\u5916\u3059").option("--color <color>", "UI \u8272").option("--clear-color", "\u8272\u3092\u5916\u3059").option(
2527
- "--notify-at-start",
2528
- "\u3053\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u4ED8\u3051\u308B\u65B0\u898F/\u66F4\u65B0\u30BF\u30B9\u30AF\u306E\u901A\u77E5\u3092 ON \u7D99\u627F\u3059\u308B"
2529
- ).option("--no-notify-at-start", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E\u901A\u77E5\u7D99\u627F\u3092 OFF \u306B\u3059\u308B").action(async (prefix, opts) => {
2539
+ return new Command("update").description("\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u90E8\u5206\u66F4\u65B0\u3059\u308B").argument("<id-prefix>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").option("--name <text>", "\u540D\u524D\u5909\u66F4").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--clear-description", "\u8A73\u7D30\u8AAC\u660E\u3092\u7A7A\u306B\u3059\u308B").option("--icon <icon>", "\u7D75\u6587\u5B57\u307E\u305F\u306F\u30A2\u30A4\u30B3\u30F3\u540D").option("--clear-icon", "\u30A2\u30A4\u30B3\u30F3\u3092\u5916\u3059").option("--color <color>", "UI \u8272").option("--clear-color", "\u8272\u3092\u5916\u3059").option("--notify-at-start", "\u3053\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u4ED8\u3051\u308B\u65B0\u898F/\u66F4\u65B0\u30BF\u30B9\u30AF\u306E\u901A\u77E5\u3092 ON \u7D99\u627F\u3059\u308B").option("--no-notify-at-start", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E\u901A\u77E5\u7D99\u627F\u3092 OFF \u306B\u3059\u308B").action(async (prefix, opts) => {
2530
2540
  const ctx = await createAppContext();
2531
2541
  const all = await ctx.repos.context.findAll();
2532
2542
  const id = resolveResourceIdByPrefix(all, prefix, "context");
@@ -2622,7 +2632,8 @@ function projectsCreateCommand() {
2622
2632
  if (opts.color !== void 0) input2.color = opts.color;
2623
2633
  if (parentProjectId !== void 0) input2.parentProjectId = parentProjectId;
2624
2634
  if (opts.startedAt !== void 0) input2.startedAt = /* @__PURE__ */ new Date(`${opts.startedAt}T00:00:00Z`);
2625
- if (opts.targetEnd !== void 0) input2.targetEndDate = /* @__PURE__ */ new Date(`${opts.targetEnd}T00:00:00Z`);
2635
+ if (opts.targetEnd !== void 0)
2636
+ input2.targetEndDate = /* @__PURE__ */ new Date(`${opts.targetEnd}T00:00:00Z`);
2626
2637
  const project = await ctx.repos.project.create(input2);
2627
2638
  console.log(`\u2713 Created project: ${shortenId(project.id)} ${project.name}`);
2628
2639
  });
@@ -2736,22 +2747,22 @@ function parseEstimated(input2) {
2736
2747
  rest = "";
2737
2748
  }
2738
2749
  if (rest !== "" || hMatch === null && mMatch === null || hours > MAX_HOURS) {
2739
- throw new Error(
2740
- `Invalid estimated format: "${input2}". Use formats like "30m", "1h", "1h30m".`
2741
- );
2750
+ throw new Error(`Invalid estimated format: "${input2}". Use formats like "30m", "1h", "1h30m".`);
2742
2751
  }
2743
2752
  return (hours * 60 + minutes) * 60;
2744
2753
  }
2745
2754
 
2746
2755
  // src/format/parse-frequency.ts
2756
+ var FREQUENCY_USAGE = "daily / weekly:1,3,5 / biweekly:1,5@2026-06-22 / monthly:1,15 / nth-weekday:2:5,4:5 / custom:CRON_STRING";
2757
+ var DATE_RE3 = /^\d{4}-\d{2}-\d{2}$/;
2747
2758
  function parseFrequencySpec(spec) {
2748
2759
  if (spec === "daily") return { type: "daily" };
2749
- const [typeKey, rest] = spec.split(":");
2750
- if (!typeKey || rest === void 0) {
2751
- throw new CliError(
2752
- `Invalid --frequency "${spec}". Use one of: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING`
2753
- );
2760
+ const colonIndex = spec.indexOf(":");
2761
+ if (colonIndex < 0) {
2762
+ throw new CliError(`Invalid --frequency "${spec}". Use one of: ${FREQUENCY_USAGE}`);
2754
2763
  }
2764
+ const typeKey = spec.slice(0, colonIndex);
2765
+ const rest = spec.slice(colonIndex + 1);
2755
2766
  if (typeKey === "weekly") {
2756
2767
  const weekdays = parseNumberList(rest, 0, 6);
2757
2768
  if (weekdays.length === 0) {
@@ -2759,6 +2770,21 @@ function parseFrequencySpec(spec) {
2759
2770
  }
2760
2771
  return { type: "weekly", weekdays };
2761
2772
  }
2773
+ if (typeKey === "biweekly") {
2774
+ const atIndex = rest.indexOf("@");
2775
+ if (atIndex < 0) {
2776
+ throw new CliError("biweekly frequency requires an anchor date, e.g. biweekly:1,5@2026-06-22");
2777
+ }
2778
+ const weekdays = parseNumberList(rest.slice(0, atIndex), 0, 6);
2779
+ const anchorDate = rest.slice(atIndex + 1);
2780
+ if (weekdays.length === 0) {
2781
+ throw new CliError("biweekly frequency must have at least 1 weekday.");
2782
+ }
2783
+ if (!DATE_RE3.test(anchorDate)) {
2784
+ throw new CliError(`Invalid anchor date "${anchorDate}". Use YYYY-MM-DD.`);
2785
+ }
2786
+ return { type: "weekly", weekdays, intervalWeeks: 2, anchorDate };
2787
+ }
2762
2788
  if (typeKey === "monthly") {
2763
2789
  const days = parseNumberList(rest, 1, 31);
2764
2790
  if (days.length === 0) {
@@ -2766,13 +2792,33 @@ function parseFrequencySpec(spec) {
2766
2792
  }
2767
2793
  return { type: "monthly", days };
2768
2794
  }
2795
+ if (typeKey === "nth-weekday") {
2796
+ const pairs = rest.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
2797
+ if (pairs.length === 0) {
2798
+ throw new CliError("nth-weekday frequency must have at least 1 (nth:weekday) pair.");
2799
+ }
2800
+ const occurrences = pairs.map((pair) => {
2801
+ const [nthRaw, weekdayRaw] = pair.split(":");
2802
+ if (!nthRaw || !weekdayRaw) {
2803
+ throw new CliError(`Invalid nth-weekday pair "${pair}". Use nth:weekday (e.g. 2:5).`);
2804
+ }
2805
+ const nth = Number(nthRaw);
2806
+ const weekday = Number(weekdayRaw);
2807
+ if (!Number.isInteger(nth) || !(nth >= 1 && nth <= 5 || nth === -1)) {
2808
+ throw new CliError(`Invalid nth "${nthRaw}" (must be 1-5 or -1 for last).`);
2809
+ }
2810
+ if (!Number.isInteger(weekday) || weekday < 0 || weekday > 6) {
2811
+ throw new CliError(`Invalid weekday "${weekdayRaw}" (must be 0-6).`);
2812
+ }
2813
+ return { nth, weekday };
2814
+ });
2815
+ return { type: "monthly-nth-weekday", occurrences };
2816
+ }
2769
2817
  if (typeKey === "custom") {
2770
2818
  if (rest.length === 0) throw new CliError("custom frequency requires a cron string.");
2771
2819
  return { type: "custom", cron: rest };
2772
2820
  }
2773
- throw new CliError(
2774
- `Unknown frequency type "${typeKey}". Use one of: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING`
2775
- );
2821
+ throw new CliError(`Unknown frequency type "${typeKey}". Use one of: ${FREQUENCY_USAGE}`);
2776
2822
  }
2777
2823
  function parseNumberList(csv, min, max) {
2778
2824
  const parts = csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
@@ -2792,11 +2838,11 @@ function parseWeekdaysList(csv) {
2792
2838
 
2793
2839
  // src/commands/routines/create.ts
2794
2840
  var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
2795
- var DATE_RE3 = /^\d{4}-\d{2}-\d{2}$/;
2841
+ var DATE_RE4 = /^\d{4}-\d{2}-\d{2}$/;
2796
2842
  function routinesCreateCommand() {
2797
2843
  return new Command("create").description("\u30EB\u30FC\u30C1\u30F3\u3092\u65B0\u898F\u4F5C\u6210\u3059\u308B").argument("<title>", "\u30EB\u30FC\u30C1\u30F3\u306E\u30BF\u30A4\u30C8\u30EB").requiredOption("-e, --estimated <duration>", "\u898B\u7A4D\u3082\u308A\u6642\u9593 (\u4F8B: 30m, 1h, 1h30m)").requiredOption(
2798
2844
  "--frequency <spec>",
2799
- "\u983B\u5EA6: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING"
2845
+ "\u983B\u5EA6: daily / weekly:1,3,5 / biweekly:1,5@2026-06-22 / monthly:1,15 / nth-weekday:2:5,4:5 / custom:CRON"
2800
2846
  ).option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--project <id-prefix>", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id (prefix)").option("--context <id-prefix>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id (prefix)").option("--tag <id-prefix>", "\u30BF\u30B0 id (prefix)\u3002\u8907\u6570\u6307\u5B9A\u53EF", collectTag, []).option("--scheduled <HH:MM>", "\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u4E88\u5B9A\u958B\u59CB\u6642\u523B").option("--no-auto-expand", "Today \u81EA\u52D5\u5C55\u958B\u3092\u7121\u52B9\u5316 (\u30C7\u30D5\u30A9\u30EB\u30C8\u306F\u6709\u52B9)").option("--start-date <YYYY-MM-DD>", "\u6709\u52B9\u671F\u9593\u306E\u958B\u59CB\u65E5").option("--end-date <YYYY-MM-DD>", "\u6709\u52B9\u671F\u9593\u306E\u7D42\u4E86\u65E5").action(async (title, opts) => {
2801
2847
  const ctx = await createAppContext();
2802
2848
  const estimatedSeconds = parseEstimated(opts.estimated);
@@ -2804,10 +2850,10 @@ function routinesCreateCommand() {
2804
2850
  if (opts.scheduled !== void 0 && !HHMM.test(opts.scheduled)) {
2805
2851
  throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
2806
2852
  }
2807
- if (opts.startDate !== void 0 && !DATE_RE3.test(opts.startDate)) {
2853
+ if (opts.startDate !== void 0 && !DATE_RE4.test(opts.startDate)) {
2808
2854
  throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
2809
2855
  }
2810
- if (opts.endDate !== void 0 && !DATE_RE3.test(opts.endDate)) {
2856
+ if (opts.endDate !== void 0 && !DATE_RE4.test(opts.endDate)) {
2811
2857
  throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
2812
2858
  }
2813
2859
  let projectId;
@@ -2864,10 +2910,17 @@ function formatFrequency(freq) {
2864
2910
  switch (freq.type) {
2865
2911
  case "daily":
2866
2912
  return "daily";
2867
- case "weekly":
2913
+ case "weekly": {
2914
+ const interval = freq.intervalWeeks ?? 1;
2915
+ if (interval >= 2 && freq.anchorDate) {
2916
+ return `biweekly:${freq.weekdays.join(",")}@${freq.anchorDate}`;
2917
+ }
2868
2918
  return `weekly:${freq.weekdays.join(",")}`;
2919
+ }
2869
2920
  case "monthly":
2870
2921
  return `monthly:${freq.days.join(",")}`;
2922
+ case "monthly-nth-weekday":
2923
+ return `nth-weekday:${freq.occurrences.map((o) => `${o.nth}:${o.weekday}`).join(",")}`;
2871
2924
  case "custom":
2872
2925
  return `custom:${freq.cron}`;
2873
2926
  }
@@ -2897,23 +2950,26 @@ function routinesListCommand() {
2897
2950
  });
2898
2951
  }
2899
2952
  var HHMM2 = /^([01]\d|2[0-3]):[0-5]\d$/;
2900
- var DATE_RE4 = /^\d{4}-\d{2}-\d{2}$/;
2953
+ var DATE_RE5 = /^\d{4}-\d{2}-\d{2}$/;
2901
2954
  var ROUTINE_STATUSES = ["active", "paused", "archived"];
2902
2955
  function isRoutineStatus(value) {
2903
2956
  return ROUTINE_STATUSES.includes(value);
2904
2957
  }
2905
2958
  function routinesUpdateCommand() {
2906
- return new Command("update").description("\u30EB\u30FC\u30C1\u30F3\u3092\u90E8\u5206\u66F4\u65B0\u3059\u308B (\u672A\u6765 planned task \u306B\u81EA\u52D5\u4F1D\u64AD)").argument("<id-prefix>", "\u30EB\u30FC\u30C1\u30F3 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").option("--title <text>", "\u30BF\u30A4\u30C8\u30EB\u5909\u66F4").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--clear-description", "\u8A73\u7D30\u8AAC\u660E\u3092\u7A7A\u306B\u3059\u308B").option("-e, --estimated <duration>", "\u898B\u7A4D\u3082\u308A\u6642\u9593 (30m / 1h / 1h30m)").option("--frequency <spec>", "\u983B\u5EA6: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON").option("--project <id-prefix>", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id (prefix)").option("--clear-project", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u5916\u3059").option("--context <id-prefix>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id (prefix)").option("--clear-context", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u5916\u3059").option("--tag <id-prefix>", "\u30BF\u30B0 id (prefix)\u3002\u8907\u6570\u6307\u5B9A\u53EF", collectTag2, []).option("--no-tags", "\u30BF\u30B0\u3092\u5168\u90E8\u5916\u3059").option("--scheduled <HH:MM>", "\u30C7\u30D5\u30A9\u30EB\u30C8\u4E88\u5B9A\u958B\u59CB\u6642\u523B").option("--clear-scheduled", "\u4E88\u5B9A\u958B\u59CB\u6642\u523B\u3092\u5916\u3059").option("--auto-expand", "Today \u81EA\u52D5\u5C55\u958B\u3092\u6709\u52B9\u5316").option("--no-auto-expand", "Today \u81EA\u52D5\u5C55\u958B\u3092\u7121\u52B9\u5316").option("--start-date <YYYY-MM-DD>", "\u6709\u52B9\u671F\u9593\u958B\u59CB\u65E5").option("--clear-start-date", "\u958B\u59CB\u65E5\u3092\u5916\u3059").option("--end-date <YYYY-MM-DD>", "\u6709\u52B9\u671F\u9593\u7D42\u4E86\u65E5").option("--clear-end-date", "\u7D42\u4E86\u65E5\u3092\u5916\u3059").option("--status <status>", "status (active / paused / archived)").action(async (prefix, opts) => {
2959
+ return new Command("update").description("\u30EB\u30FC\u30C1\u30F3\u3092\u90E8\u5206\u66F4\u65B0\u3059\u308B (\u672A\u6765 planned task \u306B\u81EA\u52D5\u4F1D\u64AD)").argument("<id-prefix>", "\u30EB\u30FC\u30C1\u30F3 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").option("--title <text>", "\u30BF\u30A4\u30C8\u30EB\u5909\u66F4").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--clear-description", "\u8A73\u7D30\u8AAC\u660E\u3092\u7A7A\u306B\u3059\u308B").option("-e, --estimated <duration>", "\u898B\u7A4D\u3082\u308A\u6642\u9593 (30m / 1h / 1h30m)").option(
2960
+ "--frequency <spec>",
2961
+ "\u983B\u5EA6: daily / weekly:1,3,5 / biweekly:1,5@2026-06-22 / monthly:1,15 / nth-weekday:2:5,4:5 / custom:CRON"
2962
+ ).option("--project <id-prefix>", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id (prefix)").option("--clear-project", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u5916\u3059").option("--context <id-prefix>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id (prefix)").option("--clear-context", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u5916\u3059").option("--tag <id-prefix>", "\u30BF\u30B0 id (prefix)\u3002\u8907\u6570\u6307\u5B9A\u53EF", collectTag2, []).option("--no-tags", "\u30BF\u30B0\u3092\u5168\u90E8\u5916\u3059").option("--scheduled <HH:MM>", "\u30C7\u30D5\u30A9\u30EB\u30C8\u4E88\u5B9A\u958B\u59CB\u6642\u523B").option("--clear-scheduled", "\u4E88\u5B9A\u958B\u59CB\u6642\u523B\u3092\u5916\u3059").option("--auto-expand", "Today \u81EA\u52D5\u5C55\u958B\u3092\u6709\u52B9\u5316").option("--no-auto-expand", "Today \u81EA\u52D5\u5C55\u958B\u3092\u7121\u52B9\u5316").option("--start-date <YYYY-MM-DD>", "\u6709\u52B9\u671F\u9593\u958B\u59CB\u65E5").option("--clear-start-date", "\u958B\u59CB\u65E5\u3092\u5916\u3059").option("--end-date <YYYY-MM-DD>", "\u6709\u52B9\u671F\u9593\u7D42\u4E86\u65E5").option("--clear-end-date", "\u7D42\u4E86\u65E5\u3092\u5916\u3059").option("--status <status>", "status (active / paused / archived)").action(async (prefix, opts) => {
2907
2963
  const ctx = await createAppContext();
2908
2964
  const all = await ctx.repos.routine.findAll();
2909
2965
  const id = resolveResourceIdByPrefix(all, prefix, "routine");
2910
2966
  if (opts.scheduled !== void 0 && !HHMM2.test(opts.scheduled)) {
2911
2967
  throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
2912
2968
  }
2913
- if (opts.startDate !== void 0 && !DATE_RE4.test(opts.startDate)) {
2969
+ if (opts.startDate !== void 0 && !DATE_RE5.test(opts.startDate)) {
2914
2970
  throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
2915
2971
  }
2916
- if (opts.endDate !== void 0 && !DATE_RE4.test(opts.endDate)) {
2972
+ if (opts.endDate !== void 0 && !DATE_RE5.test(opts.endDate)) {
2917
2973
  throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
2918
2974
  }
2919
2975
  if (opts.status !== void 0 && !isRoutineStatus(opts.status)) {
@@ -3212,9 +3268,15 @@ function tasksInterruptCommand() {
3212
3268
  })
3213
3269
  );
3214
3270
  console.log(`\u26A1 Interrupted:`);
3215
- console.log(` \u2713 finished: ${shortenId(result.finishedOriginal.id)} ${result.finishedOriginal.title}`);
3216
- console.log(` \u25B6 interruption: ${shortenId(result.interruption.id)} ${result.interruption.title}`);
3217
- console.log(` \u23F1 continuation: ${shortenId(result.continuation.id)} ${result.continuation.title}`);
3271
+ console.log(
3272
+ ` \u2713 finished: ${shortenId(result.finishedOriginal.id)} ${result.finishedOriginal.title}`
3273
+ );
3274
+ console.log(
3275
+ ` \u25B6 interruption: ${shortenId(result.interruption.id)} ${result.interruption.title}`
3276
+ );
3277
+ console.log(
3278
+ ` \u23F1 continuation: ${shortenId(result.continuation.id)} ${result.continuation.title}`
3279
+ );
3218
3280
  });
3219
3281
  }
3220
3282
  function collectTag4(value, prev) {
@@ -3271,20 +3333,30 @@ function tasksParallelCommand() {
3271
3333
  })
3272
3334
  );
3273
3335
  const icon = task.status === "running" ? "\u25B6" : "+";
3274
- console.log(`${icon} Parallel task: ${shortenId(task.id)} ${task.title} (status=${task.status})`);
3336
+ console.log(
3337
+ `${icon} Parallel task: ${shortenId(task.id)} ${task.title} (status=${task.status})`
3338
+ );
3275
3339
  });
3276
3340
  }
3277
3341
  function collectTag5(value, prev) {
3278
3342
  return [...prev, value];
3279
3343
  }
3280
3344
  function tasksPauseCommand() {
3281
- return new Command("pause").description("\u8A08\u6E2C\u4E2D\u306E\u30BF\u30B9\u30AF\u3092\u4E00\u6642\u505C\u6B62\u3059\u308B (status \u2192 paused)").argument("<id-prefix>", "\u30BF\u30B9\u30AF id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").option("-d, --date <date>", "\u5BFE\u8C61\u65E5 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").action(async (prefix, opts) => {
3345
+ return new Command("pause").description("\u8A08\u6E2C\u4E2D\u30BF\u30B9\u30AF\u3092\u4E2D\u65AD\u3057\u3066\u6B8B\u4F5C\u696D\u3092\u5225\u30BF\u30B9\u30AF\u306B\u5206\u5272\u3059\u308B").argument("<id-prefix>", "\u30BF\u30B9\u30AF id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").option("-d, --date <date>", "\u5BFE\u8C61\u65E5 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").action(async (prefix, opts) => {
3282
3346
  const ctx = await createAppContext();
3283
3347
  const date = opts.date ?? todayDateString();
3284
3348
  const tasks = await ctx.repos.task.findByDate(date);
3285
3349
  const taskId = resolveTaskIdByPrefix(tasks, prefix);
3286
- const updated = await pauseTask(ctx.repos.task, taskId);
3287
- console.log(`\u23F8 Paused: ${shortenId(updated.id)} ${updated.title}`);
3350
+ const result = await wrapDomainErrors(
3351
+ () => pauseTaskWithAudit(ctx.repos.task, ctx.repos.taskNote, taskId)
3352
+ );
3353
+ console.log(`\u23F8 Paused (split):`);
3354
+ console.log(
3355
+ ` \u2713 finished: ${shortenId(result.finishedOriginal.id)} ${result.finishedOriginal.title}`
3356
+ );
3357
+ console.log(
3358
+ ` \u23F1 continuation: ${shortenId(result.continuation.id)} ${result.continuation.title}`
3359
+ );
3288
3360
  });
3289
3361
  }
3290
3362
  function tasksRecentCommand() {
@@ -3338,7 +3410,9 @@ function tasksReorderCommand() {
3338
3410
  if (opts.clearScheduled) patch.scheduledStartTime = void 0;
3339
3411
  else if (opts.scheduled !== void 0) patch.scheduledStartTime = opts.scheduled;
3340
3412
  const updated = await ctx.repos.task.update(taskId, patch);
3341
- console.log(`\u2195 Reordered: ${shortenId(updated.id)} ${updated.title} (order_index=${updated.orderIndex})`);
3413
+ console.log(
3414
+ `\u2195 Reordered: ${shortenId(updated.id)} ${updated.title} (order_index=${updated.orderIndex})`
3415
+ );
3342
3416
  });
3343
3417
  }
3344
3418
  var HHMMSS = /^([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/;
@@ -3461,7 +3535,12 @@ async function buildPatch(ctx, opts) {
3461
3535
  return patch;
3462
3536
  }
3463
3537
  function tasksUpdateCommand() {
3464
- return new Command("update").description("\u30BF\u30B9\u30AF\u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u90E8\u5206\u66F4\u65B0\u3059\u308B").argument("<id-prefix>", "\u30BF\u30B9\u30AF id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").option("-d, --date <date>", "\u5BFE\u8C61\u65E5 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").option("--title <text>", "\u30BF\u30A4\u30C8\u30EB\u5909\u66F4").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--clear-description", "\u8A73\u7D30\u8AAC\u660E\u3092\u7A7A\u306B\u3059\u308B").option("-e, --estimated <duration>", '\u898B\u7A4D\u3082\u308A\u6642\u9593 ("30m" / "1h" / "1h30m")').option("--project <id-prefix>", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id (prefix)").option("--clear-project", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u5916\u3059").option("--context <id-prefix>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id (prefix)").option("--clear-context", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u5916\u3059").option("--tag <id-prefix>", "\u30BF\u30B0 id (prefix)\u3002\u8907\u6570\u6307\u5B9A\u53EF\u3002\u6307\u5B9A\u3057\u305F\u30BB\u30C3\u30C8\u3067\u5168\u7F6E\u63DB", collectTag6, []).option("--no-tags", "\u30BF\u30B0\u3092\u5168\u90E8\u5916\u3059").option("--scheduled <HH:MM>", "\u4E88\u5B9A\u958B\u59CB\u6642\u523B").option("--clear-scheduled", "\u4E88\u5B9A\u958B\u59CB\u6642\u523B\u3092\u5916\u3059").option("--notify", "\u4E88\u5B9A\u6642\u523B\u901A\u77E5\u3092 ON").option("--no-notify", "\u4E88\u5B9A\u6642\u523B\u901A\u77E5\u3092 OFF").option("--background", "\u30D0\u30C3\u30AF\u30B0\u30E9\u30A6\u30F3\u30C9\u30BF\u30B9\u30AF\u306B\u8A2D\u5B9A").option("--no-background", "\u30D0\u30C3\u30AF\u30B0\u30E9\u30A6\u30F3\u30C9\u30BF\u30B9\u30AF\u3092\u89E3\u9664").action(async (prefix, opts) => {
3538
+ return new Command("update").description("\u30BF\u30B9\u30AF\u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u90E8\u5206\u66F4\u65B0\u3059\u308B").argument("<id-prefix>", "\u30BF\u30B9\u30AF id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").option("-d, --date <date>", "\u5BFE\u8C61\u65E5 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").option("--title <text>", "\u30BF\u30A4\u30C8\u30EB\u5909\u66F4").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--clear-description", "\u8A73\u7D30\u8AAC\u660E\u3092\u7A7A\u306B\u3059\u308B").option("-e, --estimated <duration>", '\u898B\u7A4D\u3082\u308A\u6642\u9593 ("30m" / "1h" / "1h30m")').option("--project <id-prefix>", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id (prefix)").option("--clear-project", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u5916\u3059").option("--context <id-prefix>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id (prefix)").option("--clear-context", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u5916\u3059").option(
3539
+ "--tag <id-prefix>",
3540
+ "\u30BF\u30B0 id (prefix)\u3002\u8907\u6570\u6307\u5B9A\u53EF\u3002\u6307\u5B9A\u3057\u305F\u30BB\u30C3\u30C8\u3067\u5168\u7F6E\u63DB",
3541
+ collectTag6,
3542
+ []
3543
+ ).option("--no-tags", "\u30BF\u30B0\u3092\u5168\u90E8\u5916\u3059").option("--scheduled <HH:MM>", "\u4E88\u5B9A\u958B\u59CB\u6642\u523B").option("--clear-scheduled", "\u4E88\u5B9A\u958B\u59CB\u6642\u523B\u3092\u5916\u3059").option("--notify", "\u4E88\u5B9A\u6642\u523B\u901A\u77E5\u3092 ON").option("--no-notify", "\u4E88\u5B9A\u6642\u523B\u901A\u77E5\u3092 OFF").option("--background", "\u30D0\u30C3\u30AF\u30B0\u30E9\u30A6\u30F3\u30C9\u30BF\u30B9\u30AF\u306B\u8A2D\u5B9A").option("--no-background", "\u30D0\u30C3\u30AF\u30B0\u30E9\u30A6\u30F3\u30C9\u30BF\u30B9\u30AF\u3092\u89E3\u9664").action(async (prefix, opts) => {
3465
3544
  const ctx = await createAppContext();
3466
3545
  const date = opts.date ?? todayDateString();
3467
3546
  const tasks = await ctx.repos.task.findByDate(date);
@@ -3493,7 +3572,7 @@ function timeBlocksArchiveCommand() {
3493
3572
  });
3494
3573
  }
3495
3574
  var HHMM4 = /^([01]\d|2[0-3]):[0-5]\d$/;
3496
- var DATE_RE5 = /^\d{4}-\d{2}-\d{2}$/;
3575
+ var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
3497
3576
  function timeBlocksCreateCommand() {
3498
3577
  return new Command("create").description("\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF\u3092\u65B0\u898F\u4F5C\u6210\u3059\u308B").argument("<name>", "\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF\u540D (\u4F8B: morning / work / evening)").requiredOption("--start-time <HH:MM>", "\u958B\u59CB\u6642\u523B").requiredOption("--end-time <HH:MM>", "\u7D42\u4E86\u6642\u523B").requiredOption("--weekdays <csv>", "\u5BFE\u8C61\u66DC\u65E5 (0=\u65E5 \uFF5E 6=\u571F) \u306E\u30AB\u30F3\u30DE\u533A\u5207\u308A (\u4F8B: 1,2,3,4,5)").option("--project <id-prefix>", "\u30C7\u30D5\u30A9\u30EB\u30C8\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id (prefix)").option("--context <id-prefix>", "\u30C7\u30D5\u30A9\u30EB\u30C8\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id (prefix)").option("--tag <id-prefix>", "\u30C7\u30D5\u30A9\u30EB\u30C8\u30BF\u30B0 id (prefix)\u3002\u8907\u6570\u6307\u5B9A\u53EF", collectTag7, []).option("--color <color>", "UI \u8272").option("--start-date <YYYY-MM-DD>", "\u6709\u52B9\u671F\u9593\u958B\u59CB\u65E5").option("--end-date <YYYY-MM-DD>", "\u6709\u52B9\u671F\u9593\u7D42\u4E86\u65E5").action(async (name, opts) => {
3499
3578
  const ctx = await createAppContext();
@@ -3504,10 +3583,10 @@ function timeBlocksCreateCommand() {
3504
3583
  throw new CliError(`--end-time must be HH:MM (got "${opts.endTime}").`);
3505
3584
  }
3506
3585
  const weekdays = parseWeekdaysList(opts.weekdays);
3507
- if (opts.startDate !== void 0 && !DATE_RE5.test(opts.startDate)) {
3586
+ if (opts.startDate !== void 0 && !DATE_RE6.test(opts.startDate)) {
3508
3587
  throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
3509
3588
  }
3510
- if (opts.endDate !== void 0 && !DATE_RE5.test(opts.endDate)) {
3589
+ if (opts.endDate !== void 0 && !DATE_RE6.test(opts.endDate)) {
3511
3590
  throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
3512
3591
  }
3513
3592
  let projectId;
@@ -3596,7 +3675,7 @@ function timeBlocksListCommand() {
3596
3675
  });
3597
3676
  }
3598
3677
  var HHMM5 = /^([01]\d|2[0-3]):[0-5]\d$/;
3599
- var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
3678
+ var DATE_RE7 = /^\d{4}-\d{2}-\d{2}$/;
3600
3679
  var TIME_BLOCK_STATUSES = ["active", "archived"];
3601
3680
  function isTimeBlockStatus(value) {
3602
3681
  return TIME_BLOCK_STATUSES.includes(value);
@@ -3612,10 +3691,10 @@ function timeBlocksUpdateCommand() {
3612
3691
  if (opts.endTime !== void 0 && !HHMM5.test(opts.endTime)) {
3613
3692
  throw new CliError(`--end-time must be HH:MM (got "${opts.endTime}").`);
3614
3693
  }
3615
- if (opts.startDate !== void 0 && !DATE_RE6.test(opts.startDate)) {
3694
+ if (opts.startDate !== void 0 && !DATE_RE7.test(opts.startDate)) {
3616
3695
  throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
3617
3696
  }
3618
- if (opts.endDate !== void 0 && !DATE_RE6.test(opts.endDate)) {
3697
+ if (opts.endDate !== void 0 && !DATE_RE7.test(opts.endDate)) {
3619
3698
  throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
3620
3699
  }
3621
3700
  if (opts.status !== void 0 && !isTimeBlockStatus(opts.status)) {
@@ -3724,7 +3803,7 @@ function whoamiCommand() {
3724
3803
 
3725
3804
  // src/index.ts
3726
3805
  var program = new Command();
3727
- program.name("taskchute").description("TaskChute CLI \u2014 CLI/MCP \u304B\u3089 TaskChute \u306B\u30A2\u30AF\u30BB\u30B9\u3059\u308B\u305F\u3081\u306E\u30A8\u30F3\u30C8\u30EA\u30DD\u30A4\u30F3\u30C8").version("0.4.0" );
3806
+ program.name("taskchute").description("TaskChute CLI \u2014 CLI/MCP \u304B\u3089 TaskChute \u306B\u30A2\u30AF\u30BB\u30B9\u3059\u308B\u305F\u3081\u306E\u30A8\u30F3\u30C8\u30EA\u30DD\u30A4\u30F3\u30C8").version("0.6.0" );
3728
3807
  program.addCommand(loginCommand());
3729
3808
  program.addCommand(whoamiCommand());
3730
3809
  program.addCommand(logoutCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fukuchang/taskchute-cli",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "TaskChute CLI — E2EE 暗号化された TaskChute タスクをターミナルから操作するための CLI",
5
5
  "license": "MIT",
6
6
  "author": "fukuchang",
@@ -21,7 +21,7 @@
21
21
  "e2ee"
22
22
  ],
23
23
  "engines": {
24
- "node": ">=20"
24
+ "node": "^22.23.4 || ^24.22.1 || ^26.12.1"
25
25
  },
26
26
  "type": "module",
27
27
  "main": "./dist/index.js",
@@ -46,14 +46,14 @@
46
46
  "hash-wasm": "^4.12.0"
47
47
  },
48
48
  "devDependencies": {
49
+ "@taskchute/crypto": "0.0.0",
50
+ "@taskchute/domain": "0.0.0",
51
+ "@taskchute/infra-supabase": "0.0.0",
52
+ "@taskchute/node-shared": "0.0.0",
49
53
  "@types/node": "^24.12.3",
50
54
  "tsup": "^8.5.1",
51
55
  "tsx": "^4.20.0",
52
- "typescript": "~6.0.2",
53
- "@taskchute/domain": "0.0.0",
54
- "@taskchute/infra-supabase": "0.0.0",
55
- "@taskchute/crypto": "0.0.0",
56
- "@taskchute/node-shared": "0.0.0"
56
+ "typescript": "~6.0.2"
57
57
  },
58
58
  "scripts": {
59
59
  "start": "tsx src/index.ts",