@fukuchang/taskchute-cli 0.3.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);
@@ -217,14 +212,14 @@ async function deriveKEK(password4, salt, params = DEFAULT_ARGON2ID_PARAMS) {
217
212
  }
218
213
 
219
214
  // ../crypto/src/wrap.ts
220
- async function unwrapMasterKey(wrapped, iv, kek) {
215
+ async function unwrapMasterKey(wrapped, iv, kek, options = {}) {
221
216
  return crypto.subtle.unwrapKey(
222
217
  "raw",
223
218
  wrapped,
224
219
  kek,
225
220
  { name: "AES-GCM", iv },
226
221
  { name: "AES-GCM", length: 256 },
227
- false,
222
+ options.extractable ?? false,
228
223
  ["encrypt", "decrypt"]
229
224
  );
230
225
  }
@@ -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,
@@ -510,6 +494,7 @@ async function rowToContext(row, cryptoSession) {
510
494
  id: row.id,
511
495
  name,
512
496
  status: row.status,
497
+ notifyAtScheduledStart: row.notify_at_scheduled_start,
513
498
  createdAt: new Date(row.created_at),
514
499
  updatedAt: new Date(row.updated_at)
515
500
  };
@@ -523,6 +508,9 @@ function nonEncryptedFieldsToRow(input2) {
523
508
  if ("icon" in input2) row.icon = input2.icon ?? null;
524
509
  if ("color" in input2) row.color = input2.color ?? null;
525
510
  if ("status" in input2 && input2.status !== void 0) row.status = input2.status;
511
+ if ("notifyAtScheduledStart" in input2 && input2.notifyAtScheduledStart !== void 0) {
512
+ row.notify_at_scheduled_start = input2.notifyAtScheduledStart;
513
+ }
526
514
  return row;
527
515
  }
528
516
  async function encryptContextFieldsInto(row, entityId, patch, cryptoSession) {
@@ -610,13 +598,10 @@ var SupabaseContextRepository = class {
610
598
  // ../infra-supabase/src/supabase-project-repository.ts
611
599
  async function rowToProject(row, cryptoSession) {
612
600
  const [name, description] = await Promise.all([
613
- decryptRequiredTextFromWire(
614
- cryptoSession,
615
- row.name_ciphertext,
616
- row.name_iv,
617
- row.key_id,
618
- { entityId: row.id, field: "name" }
619
- ),
601
+ decryptRequiredTextFromWire(cryptoSession, row.name_ciphertext, row.name_iv, row.key_id, {
602
+ entityId: row.id,
603
+ field: "name"
604
+ }),
620
605
  decryptOptionalTextFromWire(
621
606
  cryptoSession,
622
607
  row.description_ciphertext,
@@ -770,13 +755,10 @@ var SupabaseRoutineExpansionRepository = class {
770
755
  // ../infra-supabase/src/supabase-routine-repository.ts
771
756
  async function rowToRoutine(row, cryptoSession) {
772
757
  const [title, description] = await Promise.all([
773
- decryptRequiredTextFromWire(
774
- cryptoSession,
775
- row.title_ciphertext,
776
- row.title_iv,
777
- row.key_id,
778
- { entityId: row.id, field: "title" }
779
- ),
758
+ decryptRequiredTextFromWire(cryptoSession, row.title_ciphertext, row.title_iv, row.key_id, {
759
+ entityId: row.id,
760
+ field: "title"
761
+ }),
780
762
  decryptOptionalTextFromWire(
781
763
  cryptoSession,
782
764
  row.description_ciphertext,
@@ -917,13 +899,10 @@ var SupabaseRoutineRepository = class {
917
899
  // ../infra-supabase/src/supabase-tag-repository.ts
918
900
  async function rowToTag(row, cryptoSession) {
919
901
  const [name, description] = await Promise.all([
920
- decryptRequiredTextFromWire(
921
- cryptoSession,
922
- row.name_ciphertext,
923
- row.name_iv,
924
- row.key_id,
925
- { entityId: row.id, field: "name" }
926
- ),
902
+ decryptRequiredTextFromWire(cryptoSession, row.name_ciphertext, row.name_iv, row.key_id, {
903
+ entityId: row.id,
904
+ field: "name"
905
+ }),
927
906
  decryptOptionalTextFromWire(
928
907
  cryptoSession,
929
908
  row.description_ciphertext,
@@ -1034,13 +1013,10 @@ var SupabaseTagRepository = class {
1034
1013
  // ../infra-supabase/src/supabase-task-note-repository.ts
1035
1014
  async function rowToTaskNote(row, cryptoSession) {
1036
1015
  const [body, metaJson, historyJson] = await Promise.all([
1037
- decryptRequiredTextFromWire(
1038
- cryptoSession,
1039
- row.body_ciphertext,
1040
- row.body_iv,
1041
- row.key_id,
1042
- { entityId: row.id, field: "body" }
1043
- ),
1016
+ decryptRequiredTextFromWire(cryptoSession, row.body_ciphertext, row.body_iv, row.key_id, {
1017
+ entityId: row.id,
1018
+ field: "body"
1019
+ }),
1044
1020
  decryptOptionalTextFromWire(
1045
1021
  cryptoSession,
1046
1022
  row.metadata_ciphertext,
@@ -1131,9 +1107,7 @@ async function encryptTaskNoteFieldsInto(row, entityId, patch, cryptoSession) {
1131
1107
  if (touched) {
1132
1108
  const keyId = cryptoSession.getCurrentKeyId();
1133
1109
  if (keyId === null) {
1134
- throw new Error(
1135
- "SupabaseTaskNoteRepository: session became locked mid-write"
1136
- );
1110
+ throw new Error("SupabaseTaskNoteRepository: session became locked mid-write");
1137
1111
  }
1138
1112
  row.key_id = keyId;
1139
1113
  }
@@ -1160,9 +1134,7 @@ var SupabaseTaskNoteRepository = class {
1160
1134
  if (taskIds.length === 0) return result;
1161
1135
  const { data, error } = await this.supabase.from("task_notes").select("*").in("task_id", taskIds).order("created_at", { ascending: true });
1162
1136
  if (error) throw error;
1163
- const decoded = await Promise.all(
1164
- data.map((row) => rowToTaskNote(row, this.cryptoSession))
1165
- );
1137
+ const decoded = await Promise.all(data.map((row) => rowToTaskNote(row, this.cryptoSession)));
1166
1138
  for (const note of decoded) {
1167
1139
  const bucket = result.get(note.taskId);
1168
1140
  if (bucket) bucket.push(note);
@@ -1211,13 +1183,10 @@ var SupabaseTaskNoteRepository = class {
1211
1183
  // ../infra-supabase/src/supabase-task-repository.ts
1212
1184
  async function rowToTask(row, cryptoSession) {
1213
1185
  const [title, description] = await Promise.all([
1214
- decryptRequiredTextFromWire(
1215
- cryptoSession,
1216
- row.title_ciphertext,
1217
- row.title_iv,
1218
- row.key_id,
1219
- { entityId: row.id, field: "title" }
1220
- ),
1186
+ decryptRequiredTextFromWire(cryptoSession, row.title_ciphertext, row.title_iv, row.key_id, {
1187
+ entityId: row.id,
1188
+ field: "title"
1189
+ }),
1221
1190
  decryptOptionalTextFromWire(
1222
1191
  cryptoSession,
1223
1192
  row.description_ciphertext,
@@ -1661,20 +1630,41 @@ async function startTask(repo, taskId) {
1661
1630
  return repo.update(taskId, patch);
1662
1631
  }
1663
1632
  async function pauseTask(repo, taskId) {
1664
- const task = await repo.findById(taskId);
1665
- if (!task) throw new Error(`Task not found: ${taskId}`);
1666
- if (!task.currentSession) {
1633
+ const target = await repo.findById(taskId);
1634
+ if (!target) throw new Error(`Task not found: ${taskId}`);
1635
+ if (!target.currentSession) {
1667
1636
  throw new Error(`Task is not currently running: ${taskId}`);
1668
1637
  }
1669
- const newSession = {
1670
- startedAt: task.currentSession.startedAt,
1671
- finishedAt: /* @__PURE__ */ new Date()
1638
+ const now = /* @__PURE__ */ new Date();
1639
+ const closedSession = {
1640
+ startedAt: target.currentSession.startedAt,
1641
+ finishedAt: now
1672
1642
  };
1673
- return repo.update(taskId, {
1674
- status: "paused",
1675
- sessions: [...task.sessions, newSession],
1643
+ const finishedOriginal = await repo.update(target.id, {
1644
+ status: "completed",
1645
+ sessions: [...target.sessions, closedSession],
1676
1646
  currentSession: void 0
1677
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 };
1678
1668
  }
1679
1669
  async function skipTask(repo, taskId) {
1680
1670
  const task = await repo.findById(taskId);
@@ -1829,19 +1819,13 @@ function buildSetActualTimesPatch(task, input2) {
1829
1819
  // ../domain/src/task-reorder.ts
1830
1820
  function computeNextOrderIndex(existingTasks) {
1831
1821
  if (existingTasks.length === 0) return 10;
1832
- const max = existingTasks.reduce(
1833
- (acc, t) => t.orderIndex > acc ? t.orderIndex : acc,
1834
- 0
1835
- );
1822
+ const max = existingTasks.reduce((acc, t) => t.orderIndex > acc ? t.orderIndex : acc, 0);
1836
1823
  return max + 10;
1837
1824
  }
1838
1825
 
1839
1826
  // ../domain/src/task-suggest.ts
1840
1827
  function normalizeForSearch(str) {
1841
- return str.trim().toLowerCase().replace(
1842
- /[ぁ-ゖ]/g,
1843
- (ch) => String.fromCharCode(ch.charCodeAt(0) + 96)
1844
- );
1828
+ return str.trim().toLowerCase().replace(/[ぁ-ゖ]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) + 96));
1845
1829
  }
1846
1830
  function fuzzyScore(query, target) {
1847
1831
  const q = query;
@@ -1904,7 +1888,6 @@ function findSimilarTasks(query, tasks, limit = 5) {
1904
1888
  function describeStatusTransition(from, to) {
1905
1889
  if (from === "planned" && to === "running") return "\u958B\u59CB";
1906
1890
  if (from === "paused" && to === "running") return "\u518D\u958B";
1907
- if (from === "running" && to === "paused") return "\u4E00\u6642\u505C\u6B62";
1908
1891
  if (to === "completed") return "\u5B8C\u4E86";
1909
1892
  if (to === "skipped") return "\u30B9\u30AD\u30C3\u30D7";
1910
1893
  if (to === "planned") return "\u672A\u7740\u624B\u306B\u623B\u3059";
@@ -2154,6 +2137,42 @@ async function interruptTaskWithAudit(taskRepo, taskNoteRepo, params) {
2154
2137
  }
2155
2138
  return result;
2156
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
+ }
2157
2176
  function buildSplitNoteInputs(source, interruption, continuation) {
2158
2177
  const sourceTitle = titleOrPlaceholder(source);
2159
2178
  const interruptionTitle = titleOrPlaceholder(interruption);
@@ -2190,8 +2209,6 @@ function buildSplitNoteInputs(source, interruption, continuation) {
2190
2209
  }
2191
2210
  ];
2192
2211
  }
2193
-
2194
- // ../domain/src/routine-expansion.ts
2195
2212
  function buildRoutineTaskPatch(routine) {
2196
2213
  return {
2197
2214
  title: routine.title,
@@ -2209,6 +2226,14 @@ function pickPropagationTargets(routine, tasks, today) {
2209
2226
  );
2210
2227
  }
2211
2228
 
2229
+ // ../domain/src/context-task-inheritance.ts
2230
+ function applyContextNotificationInheritance(input2, context) {
2231
+ if (context?.notifyAtScheduledStart !== true) return input2;
2232
+ const current = input2.notifyAtScheduledStart;
2233
+ if (current !== void 0) return input2;
2234
+ return { ...input2, notifyAtScheduledStart: true };
2235
+ }
2236
+
2212
2237
  // ../infra-supabase/src/audit-task-repository.ts
2213
2238
  var AuditingTaskRepository = class {
2214
2239
  // `erasableSyntaxOnly` 配下では parameter property が使えないので、明示的に
@@ -2460,12 +2485,16 @@ function contextsArchiveCommand() {
2460
2485
  });
2461
2486
  }
2462
2487
  function contextsCreateCommand() {
2463
- return new Command("create").description("\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u65B0\u898F\u4F5C\u6210\u3059\u308B").argument("<name>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u540D").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--icon <icon>", "\u7D75\u6587\u5B57\u307E\u305F\u306F\u30A2\u30A4\u30B3\u30F3\u540D (\u4F8B: \u{1F3E0})").option("--color <color>", "UI \u8272").action(async (name, opts) => {
2488
+ return new Command("create").description("\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u65B0\u898F\u4F5C\u6210\u3059\u308B").argument("<name>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u540D").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--icon <icon>", "\u7D75\u6587\u5B57\u307E\u305F\u306F\u30A2\u30A4\u30B3\u30F3\u540D (\u4F8B: \u{1F3E0})").option("--color <color>", "UI \u8272").option(
2489
+ "--notify-at-start",
2490
+ "\u3053\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u4ED8\u3051\u308B\u65B0\u898F/\u66F4\u65B0\u30BF\u30B9\u30AF\u306E\u901A\u77E5\u3092 ON \u306B\u81EA\u52D5\u7D99\u627F\u3059\u308B"
2491
+ ).action(async (name, opts) => {
2464
2492
  const ctx = await createAppContext();
2465
2493
  const input2 = { name, status: "active" };
2466
2494
  if (opts.description !== void 0) input2.description = opts.description;
2467
2495
  if (opts.icon !== void 0) input2.icon = opts.icon;
2468
2496
  if (opts.color !== void 0) input2.color = opts.color;
2497
+ if (opts.notifyAtStart !== void 0) input2.notifyAtScheduledStart = opts.notifyAtStart;
2469
2498
  const context = await ctx.repos.context.create(input2);
2470
2499
  console.log(`\u2713 Created context: ${shortenId(context.id)} ${context.name}`);
2471
2500
  });
@@ -2488,7 +2517,8 @@ function formatContextRow(context) {
2488
2517
  name: context.name,
2489
2518
  status: context.status,
2490
2519
  icon: context.icon ?? "-",
2491
- color: context.color ?? "-"
2520
+ color: context.color ?? "-",
2521
+ notify_at_start: context.notifyAtScheduledStart ? "\u2713" : "-"
2492
2522
  };
2493
2523
  }
2494
2524
 
@@ -2506,7 +2536,7 @@ function contextsListCommand() {
2506
2536
  });
2507
2537
  }
2508
2538
  function contextsUpdateCommand() {
2509
- 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").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) => {
2510
2540
  const ctx = await createAppContext();
2511
2541
  const all = await ctx.repos.context.findAll();
2512
2542
  const id = resolveResourceIdByPrefix(all, prefix, "context");
@@ -2518,9 +2548,12 @@ function contextsUpdateCommand() {
2518
2548
  else if (opts.icon !== void 0) patch.icon = opts.icon;
2519
2549
  if (opts.clearColor) patch.color = void 0;
2520
2550
  else if (opts.color !== void 0) patch.color = opts.color;
2551
+ if (opts.notifyAtStart !== void 0) {
2552
+ patch.notifyAtScheduledStart = opts.notifyAtStart;
2553
+ }
2521
2554
  if (Object.keys(patch).length === 0) {
2522
2555
  throw new CliError(
2523
- "No field to update. Specify at least one of --name / --description / --icon / --color / --clear-*."
2556
+ "No field to update. Specify at least one of --name / --description / --icon / --color / --notify-at-start / --clear-*."
2524
2557
  );
2525
2558
  }
2526
2559
  const updated = await ctx.repos.context.update(id, patch);
@@ -2599,7 +2632,8 @@ function projectsCreateCommand() {
2599
2632
  if (opts.color !== void 0) input2.color = opts.color;
2600
2633
  if (parentProjectId !== void 0) input2.parentProjectId = parentProjectId;
2601
2634
  if (opts.startedAt !== void 0) input2.startedAt = /* @__PURE__ */ new Date(`${opts.startedAt}T00:00:00Z`);
2602
- 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`);
2603
2637
  const project = await ctx.repos.project.create(input2);
2604
2638
  console.log(`\u2713 Created project: ${shortenId(project.id)} ${project.name}`);
2605
2639
  });
@@ -2713,22 +2747,22 @@ function parseEstimated(input2) {
2713
2747
  rest = "";
2714
2748
  }
2715
2749
  if (rest !== "" || hMatch === null && mMatch === null || hours > MAX_HOURS) {
2716
- throw new Error(
2717
- `Invalid estimated format: "${input2}". Use formats like "30m", "1h", "1h30m".`
2718
- );
2750
+ throw new Error(`Invalid estimated format: "${input2}". Use formats like "30m", "1h", "1h30m".`);
2719
2751
  }
2720
2752
  return (hours * 60 + minutes) * 60;
2721
2753
  }
2722
2754
 
2723
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}$/;
2724
2758
  function parseFrequencySpec(spec) {
2725
2759
  if (spec === "daily") return { type: "daily" };
2726
- const [typeKey, rest] = spec.split(":");
2727
- if (!typeKey || rest === void 0) {
2728
- throw new CliError(
2729
- `Invalid --frequency "${spec}". Use one of: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING`
2730
- );
2760
+ const colonIndex = spec.indexOf(":");
2761
+ if (colonIndex < 0) {
2762
+ throw new CliError(`Invalid --frequency "${spec}". Use one of: ${FREQUENCY_USAGE}`);
2731
2763
  }
2764
+ const typeKey = spec.slice(0, colonIndex);
2765
+ const rest = spec.slice(colonIndex + 1);
2732
2766
  if (typeKey === "weekly") {
2733
2767
  const weekdays = parseNumberList(rest, 0, 6);
2734
2768
  if (weekdays.length === 0) {
@@ -2736,6 +2770,21 @@ function parseFrequencySpec(spec) {
2736
2770
  }
2737
2771
  return { type: "weekly", weekdays };
2738
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
+ }
2739
2788
  if (typeKey === "monthly") {
2740
2789
  const days = parseNumberList(rest, 1, 31);
2741
2790
  if (days.length === 0) {
@@ -2743,13 +2792,33 @@ function parseFrequencySpec(spec) {
2743
2792
  }
2744
2793
  return { type: "monthly", days };
2745
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
+ }
2746
2817
  if (typeKey === "custom") {
2747
2818
  if (rest.length === 0) throw new CliError("custom frequency requires a cron string.");
2748
2819
  return { type: "custom", cron: rest };
2749
2820
  }
2750
- throw new CliError(
2751
- `Unknown frequency type "${typeKey}". Use one of: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING`
2752
- );
2821
+ throw new CliError(`Unknown frequency type "${typeKey}". Use one of: ${FREQUENCY_USAGE}`);
2753
2822
  }
2754
2823
  function parseNumberList(csv, min, max) {
2755
2824
  const parts = csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
@@ -2769,11 +2838,11 @@ function parseWeekdaysList(csv) {
2769
2838
 
2770
2839
  // src/commands/routines/create.ts
2771
2840
  var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
2772
- var DATE_RE3 = /^\d{4}-\d{2}-\d{2}$/;
2841
+ var DATE_RE4 = /^\d{4}-\d{2}-\d{2}$/;
2773
2842
  function routinesCreateCommand() {
2774
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(
2775
2844
  "--frequency <spec>",
2776
- "\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"
2777
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) => {
2778
2847
  const ctx = await createAppContext();
2779
2848
  const estimatedSeconds = parseEstimated(opts.estimated);
@@ -2781,10 +2850,10 @@ function routinesCreateCommand() {
2781
2850
  if (opts.scheduled !== void 0 && !HHMM.test(opts.scheduled)) {
2782
2851
  throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
2783
2852
  }
2784
- if (opts.startDate !== void 0 && !DATE_RE3.test(opts.startDate)) {
2853
+ if (opts.startDate !== void 0 && !DATE_RE4.test(opts.startDate)) {
2785
2854
  throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
2786
2855
  }
2787
- if (opts.endDate !== void 0 && !DATE_RE3.test(opts.endDate)) {
2856
+ if (opts.endDate !== void 0 && !DATE_RE4.test(opts.endDate)) {
2788
2857
  throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
2789
2858
  }
2790
2859
  let projectId;
@@ -2841,10 +2910,17 @@ function formatFrequency(freq) {
2841
2910
  switch (freq.type) {
2842
2911
  case "daily":
2843
2912
  return "daily";
2844
- 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
+ }
2845
2918
  return `weekly:${freq.weekdays.join(",")}`;
2919
+ }
2846
2920
  case "monthly":
2847
2921
  return `monthly:${freq.days.join(",")}`;
2922
+ case "monthly-nth-weekday":
2923
+ return `nth-weekday:${freq.occurrences.map((o) => `${o.nth}:${o.weekday}`).join(",")}`;
2848
2924
  case "custom":
2849
2925
  return `custom:${freq.cron}`;
2850
2926
  }
@@ -2874,23 +2950,26 @@ function routinesListCommand() {
2874
2950
  });
2875
2951
  }
2876
2952
  var HHMM2 = /^([01]\d|2[0-3]):[0-5]\d$/;
2877
- var DATE_RE4 = /^\d{4}-\d{2}-\d{2}$/;
2953
+ var DATE_RE5 = /^\d{4}-\d{2}-\d{2}$/;
2878
2954
  var ROUTINE_STATUSES = ["active", "paused", "archived"];
2879
2955
  function isRoutineStatus(value) {
2880
2956
  return ROUTINE_STATUSES.includes(value);
2881
2957
  }
2882
2958
  function routinesUpdateCommand() {
2883
- 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) => {
2884
2963
  const ctx = await createAppContext();
2885
2964
  const all = await ctx.repos.routine.findAll();
2886
2965
  const id = resolveResourceIdByPrefix(all, prefix, "routine");
2887
2966
  if (opts.scheduled !== void 0 && !HHMM2.test(opts.scheduled)) {
2888
2967
  throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
2889
2968
  }
2890
- if (opts.startDate !== void 0 && !DATE_RE4.test(opts.startDate)) {
2969
+ if (opts.startDate !== void 0 && !DATE_RE5.test(opts.startDate)) {
2891
2970
  throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
2892
2971
  }
2893
- if (opts.endDate !== void 0 && !DATE_RE4.test(opts.endDate)) {
2972
+ if (opts.endDate !== void 0 && !DATE_RE5.test(opts.endDate)) {
2894
2973
  throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
2895
2974
  }
2896
2975
  if (opts.status !== void 0 && !isRoutineStatus(opts.status)) {
@@ -2951,9 +3030,11 @@ function routinesUpdateCommand() {
2951
3030
  );
2952
3031
  }
2953
3032
  const taskPatch = buildRoutineTaskPatch(updated);
3033
+ const routineContext = updated.contextId ? await ctx.repos.context.findById(updated.contextId) : null;
3034
+ const propagatedPatch = applyContextNotificationInheritance(taskPatch, routineContext);
2954
3035
  await Promise.all(
2955
3036
  targets.map(
2956
- (task) => auditingTask.updateWithCause(task.id, taskPatch, "routine-propagation")
3037
+ (task) => auditingTask.updateWithCause(task.id, propagatedPatch, "routine-propagation")
2957
3038
  )
2958
3039
  );
2959
3040
  propagated = targets.length;
@@ -3060,9 +3141,11 @@ function tasksAddCommand() {
3060
3141
  projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3061
3142
  }
3062
3143
  let contextId;
3144
+ let context = null;
3063
3145
  if (opts.context !== void 0) {
3064
3146
  const all = await ctx.repos.context.findAll();
3065
3147
  contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3148
+ context = all.find((c) => c.id === contextId) ?? null;
3066
3149
  }
3067
3150
  const tagIds = [];
3068
3151
  if (opts.tag !== void 0 && opts.tag.length > 0) {
@@ -3076,7 +3159,7 @@ function tasksAddCommand() {
3076
3159
  }
3077
3160
  const existing = await ctx.repos.task.findByDate(date);
3078
3161
  const orderIndex = computeNextOrderIndex(existing);
3079
- const task = await ctx.repos.task.create({
3162
+ const createInput = {
3080
3163
  date,
3081
3164
  title,
3082
3165
  description: opts.description,
@@ -3090,7 +3173,10 @@ function tasksAddCommand() {
3090
3173
  scheduledStartTime: opts.scheduled,
3091
3174
  notifyAtScheduledStart: opts.notify,
3092
3175
  isBackground: opts.background
3093
- });
3176
+ };
3177
+ const task = await ctx.repos.task.create(
3178
+ applyContextNotificationInheritance(createInput, context)
3179
+ );
3094
3180
  console.log(`\u2713 Created: ${shortenId(task.id)} ${task.title}`);
3095
3181
  });
3096
3182
  }
@@ -3182,9 +3268,15 @@ function tasksInterruptCommand() {
3182
3268
  })
3183
3269
  );
3184
3270
  console.log(`\u26A1 Interrupted:`);
3185
- console.log(` \u2713 finished: ${shortenId(result.finishedOriginal.id)} ${result.finishedOriginal.title}`);
3186
- console.log(` \u25B6 interruption: ${shortenId(result.interruption.id)} ${result.interruption.title}`);
3187
- 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
+ );
3188
3280
  });
3189
3281
  }
3190
3282
  function collectTag4(value, prev) {
@@ -3241,20 +3333,30 @@ function tasksParallelCommand() {
3241
3333
  })
3242
3334
  );
3243
3335
  const icon = task.status === "running" ? "\u25B6" : "+";
3244
- 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
+ );
3245
3339
  });
3246
3340
  }
3247
3341
  function collectTag5(value, prev) {
3248
3342
  return [...prev, value];
3249
3343
  }
3250
3344
  function tasksPauseCommand() {
3251
- 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) => {
3252
3346
  const ctx = await createAppContext();
3253
3347
  const date = opts.date ?? todayDateString();
3254
3348
  const tasks = await ctx.repos.task.findByDate(date);
3255
3349
  const taskId = resolveTaskIdByPrefix(tasks, prefix);
3256
- const updated = await pauseTask(ctx.repos.task, taskId);
3257
- 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
+ );
3258
3360
  });
3259
3361
  }
3260
3362
  function tasksRecentCommand() {
@@ -3308,7 +3410,9 @@ function tasksReorderCommand() {
3308
3410
  if (opts.clearScheduled) patch.scheduledStartTime = void 0;
3309
3411
  else if (opts.scheduled !== void 0) patch.scheduledStartTime = opts.scheduled;
3310
3412
  const updated = await ctx.repos.task.update(taskId, patch);
3311
- 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
+ );
3312
3416
  });
3313
3417
  }
3314
3418
  var HHMMSS = /^([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/;
@@ -3431,12 +3535,21 @@ async function buildPatch(ctx, opts) {
3431
3535
  return patch;
3432
3536
  }
3433
3537
  function tasksUpdateCommand() {
3434
- 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) => {
3435
3544
  const ctx = await createAppContext();
3436
3545
  const date = opts.date ?? todayDateString();
3437
3546
  const tasks = await ctx.repos.task.findByDate(date);
3438
3547
  const taskId = resolveTaskIdByPrefix(tasks, prefix);
3439
3548
  const patch = await buildPatch(ctx, opts);
3549
+ if (patch.contextId) {
3550
+ const newContext = await ctx.repos.context.findById(patch.contextId);
3551
+ Object.assign(patch, applyContextNotificationInheritance(patch, newContext));
3552
+ }
3440
3553
  const updated = await ctx.repos.task.update(taskId, patch);
3441
3554
  console.log(`\u270E Updated: ${shortenId(updated.id)} ${updated.title}`);
3442
3555
  });
@@ -3459,7 +3572,7 @@ function timeBlocksArchiveCommand() {
3459
3572
  });
3460
3573
  }
3461
3574
  var HHMM4 = /^([01]\d|2[0-3]):[0-5]\d$/;
3462
- var DATE_RE5 = /^\d{4}-\d{2}-\d{2}$/;
3575
+ var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
3463
3576
  function timeBlocksCreateCommand() {
3464
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) => {
3465
3578
  const ctx = await createAppContext();
@@ -3470,10 +3583,10 @@ function timeBlocksCreateCommand() {
3470
3583
  throw new CliError(`--end-time must be HH:MM (got "${opts.endTime}").`);
3471
3584
  }
3472
3585
  const weekdays = parseWeekdaysList(opts.weekdays);
3473
- if (opts.startDate !== void 0 && !DATE_RE5.test(opts.startDate)) {
3586
+ if (opts.startDate !== void 0 && !DATE_RE6.test(opts.startDate)) {
3474
3587
  throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
3475
3588
  }
3476
- if (opts.endDate !== void 0 && !DATE_RE5.test(opts.endDate)) {
3589
+ if (opts.endDate !== void 0 && !DATE_RE6.test(opts.endDate)) {
3477
3590
  throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
3478
3591
  }
3479
3592
  let projectId;
@@ -3562,7 +3675,7 @@ function timeBlocksListCommand() {
3562
3675
  });
3563
3676
  }
3564
3677
  var HHMM5 = /^([01]\d|2[0-3]):[0-5]\d$/;
3565
- var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
3678
+ var DATE_RE7 = /^\d{4}-\d{2}-\d{2}$/;
3566
3679
  var TIME_BLOCK_STATUSES = ["active", "archived"];
3567
3680
  function isTimeBlockStatus(value) {
3568
3681
  return TIME_BLOCK_STATUSES.includes(value);
@@ -3578,10 +3691,10 @@ function timeBlocksUpdateCommand() {
3578
3691
  if (opts.endTime !== void 0 && !HHMM5.test(opts.endTime)) {
3579
3692
  throw new CliError(`--end-time must be HH:MM (got "${opts.endTime}").`);
3580
3693
  }
3581
- if (opts.startDate !== void 0 && !DATE_RE6.test(opts.startDate)) {
3694
+ if (opts.startDate !== void 0 && !DATE_RE7.test(opts.startDate)) {
3582
3695
  throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
3583
3696
  }
3584
- if (opts.endDate !== void 0 && !DATE_RE6.test(opts.endDate)) {
3697
+ if (opts.endDate !== void 0 && !DATE_RE7.test(opts.endDate)) {
3585
3698
  throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
3586
3699
  }
3587
3700
  if (opts.status !== void 0 && !isTimeBlockStatus(opts.status)) {
@@ -3690,7 +3803,7 @@ function whoamiCommand() {
3690
3803
 
3691
3804
  // src/index.ts
3692
3805
  var program = new Command();
3693
- 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.3.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" );
3694
3807
  program.addCommand(loginCommand());
3695
3808
  program.addCommand(whoamiCommand());
3696
3809
  program.addCommand(logoutCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fukuchang/taskchute-cli",
3
- "version": "0.3.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
- "@types/node": "^24.12.3",
50
- "tsup": "^8.5.1",
51
- "tsx": "^4.20.0",
52
- "typescript": "~6.0.2",
53
49
  "@taskchute/crypto": "0.0.0",
54
50
  "@taskchute/domain": "0.0.0",
55
51
  "@taskchute/infra-supabase": "0.0.0",
56
- "@taskchute/node-shared": "0.0.0"
52
+ "@taskchute/node-shared": "0.0.0",
53
+ "@types/node": "^24.12.3",
54
+ "tsup": "^8.5.1",
55
+ "tsx": "^4.20.0",
56
+ "typescript": "~6.0.2"
57
57
  },
58
58
  "scripts": {
59
59
  "start": "tsx src/index.ts",