@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 +12 -4
- package/README.md +55 -52
- package/dist/index.js +247 -134
- package/package.json +7 -7
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
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`
|
|
37
|
-
| `TASKCHUTE_SUPABASE_PUBLISHABLE_KEY` | 必須 | Supabase の publishable (anon) key
|
|
38
|
-
| `TASKCHUTE_PASSPHRASE`
|
|
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`
|
|
74
|
-
| `taskchute whoami`
|
|
75
|
-
| `taskchute logout`
|
|
76
|
-
| `taskchute unlock`
|
|
77
|
-
| `taskchute lock`
|
|
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`
|
|
82
|
-
| `taskchute tasks start <id-prefix>`
|
|
83
|
-
| `taskchute tasks pause <id-prefix>`
|
|
84
|
-
| `taskchute tasks done <id-prefix>`
|
|
85
|
-
| `taskchute tasks skip <id-prefix>`
|
|
86
|
-
| `taskchute tasks reopen <id-prefix>`
|
|
87
|
-
| `taskchute tasks recent [--days 90] [--query <text>] [--limit 100]`
|
|
88
|
-
| `taskchute projects list [--archived
|
|
89
|
-
| `taskchute tags list [--archived
|
|
90
|
-
| `taskchute contexts list [--archived
|
|
91
|
-
| `taskchute routines list [--paused
|
|
92
|
-
| `taskchute time-blocks list [--archived
|
|
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>`
|
|
96
|
-
| `taskchute projects delete <id-prefix>` / `rm`
|
|
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>`
|
|
100
|
-
| `taskchute tags delete <id-prefix>` / `rm`
|
|
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`
|
|
105
|
-
| `taskchute routines create <title> --estimated <dur> --frequency <spec> [...]`
|
|
106
|
-
| `taskchute routines update <id-prefix> [...]`
|
|
107
|
-
| `taskchute routines archive <id-prefix>`
|
|
108
|
-
| `taskchute routines delete <id-prefix>` / `rm`
|
|
109
|
-
| `taskchute time-blocks create <name> --start-time HH:MM --end-time HH:MM --weekdays 1,2,3 [...]`
|
|
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]`
|
|
114
|
-
| `taskchute tasks interrupt <target-id-prefix> <title> --estimated <dur> [...]`
|
|
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]`
|
|
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
|
-
"
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
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
|
|
1665
|
-
if (!
|
|
1666
|
-
if (!
|
|
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
|
|
1670
|
-
|
|
1671
|
-
|
|
1638
|
+
const now = /* @__PURE__ */ new Date();
|
|
1639
|
+
const closedSession = {
|
|
1640
|
+
startedAt: target.currentSession.startedAt,
|
|
1641
|
+
finishedAt: now
|
|
1672
1642
|
};
|
|
1673
|
-
|
|
1674
|
-
status: "
|
|
1675
|
-
sessions: [...
|
|
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").
|
|
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)
|
|
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
|
|
2727
|
-
if (
|
|
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
|
|
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:
|
|
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 && !
|
|
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 && !
|
|
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
|
|
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(
|
|
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 && !
|
|
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 && !
|
|
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,
|
|
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
|
|
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(
|
|
3186
|
-
|
|
3187
|
-
|
|
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(
|
|
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\
|
|
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
|
|
3257
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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 && !
|
|
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 && !
|
|
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
|
|
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 && !
|
|
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 && !
|
|
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.
|
|
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
|
+
"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": "
|
|
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",
|