@fukuchang/taskchute-mcp 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -4
- package/README.md +36 -29
- package/dist/index.js +139 -104
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 (2026-10-22) — Node.js セキュリティ修正版を要求
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- 対応 Node.js を 22.23.4 以降の22系、24.22.1 以降の24系、26.12.1 以降の26系に変更。
|
|
8
|
+
- Node.js 20 のサポートを終了し、ビルド対象を Node.js 22 に変更。
|
|
9
|
+
- MCP パッケージの更新とは別に、起動に使う Node.js 本体を更新し、MCP サーバーを再起動する必要があります。
|
|
10
|
+
|
|
3
11
|
## 0.3.0 (2026-05-28) — 実績時刻の秒精度サポート
|
|
4
12
|
|
|
5
13
|
### Added
|
|
@@ -95,7 +103,7 @@ PR2 で露出した `repos.auditingTask` 経由で `routine-propagation` cause
|
|
|
95
103
|
(`daily` / `weekly` + weekdays / `monthly` + days / `custom` + cron)
|
|
96
104
|
- update_routine は `pickPropagationTargets` で未来 planned task を絞り込み、
|
|
97
105
|
`buildRoutineTaskPatch` で patch を構築、`auditingTask.updateWithCause(id,
|
|
98
|
-
|
|
106
|
+
patch, 'routine-propagation')` で伝播。`propagated_task_count` を返す
|
|
99
107
|
- 過去日 / running / completed / skipped / 別 routine_id は保護される
|
|
100
108
|
- `auditingTask` が undefined の時 (テスト等) は通常 update に fallback
|
|
101
109
|
- `create_time_block` / `update_time_block` / `archive_time_block` /
|
|
@@ -182,9 +190,9 @@ update_task のフル機能が初めて実用化される。
|
|
|
182
190
|
Web 側 `useAuditingTaskRepository` は既存の `instanceof` 判定で動き続ける
|
|
183
191
|
(互換)
|
|
184
192
|
- `test-helpers.ts` に `InMemoryRoutineRepository` / `InMemoryTimeBlockRepository`
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
- `makeRoutine` / `makeTimeBlock` を追加。`makeMockAppContext` に
|
|
194
|
+
`routineRepo` / `timeBlockRepo` opts と `refreshLookups: () => Promise.resolve()`
|
|
195
|
+
既定実装を追加
|
|
188
196
|
|
|
189
197
|
### PR1: Task CRUD パリティ
|
|
190
198
|
|
package/README.md
CHANGED
|
@@ -10,6 +10,15 @@ E2EE 暗号化された TaskChute タスクを Claude Desktop / Claude Code 等
|
|
|
10
10
|
> も有効。初回バージョン (0.1.2) は CLI と歩調を揃えるためのもので、以降は
|
|
11
11
|
> 独立 SemVer。
|
|
12
12
|
|
|
13
|
+
## 必要な Node.js
|
|
14
|
+
|
|
15
|
+
Node.js 22.23.4 以降の22系 / 24.22.1 以降の24系 / 26.12.1 以降の26系に対応します。
|
|
16
|
+
24系を推奨し、各系統の最新パッチ版を使用してください。
|
|
17
|
+
[2026年10月のセキュリティ修正](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases)を含むバージョンが必要です。
|
|
18
|
+
|
|
19
|
+
MCP パッケージの更新だけでは Node.js 本体は更新されません。
|
|
20
|
+
MCP クライアントが起動に使う Node.js を更新し、MCP サーバーを再起動してください。
|
|
21
|
+
|
|
13
22
|
## インストール
|
|
14
23
|
|
|
15
24
|
```bash
|
|
@@ -21,32 +30,32 @@ npm i -g @fukuchang/taskchute-mcp
|
|
|
21
30
|
|
|
22
31
|
## 提供 tool
|
|
23
32
|
|
|
24
|
-
| tool
|
|
25
|
-
|
|
|
26
|
-
| `list_tasks`
|
|
27
|
-
| `add_task`
|
|
28
|
-
| `update_task`
|
|
29
|
-
| `delete_task`
|
|
30
|
-
| `start_task`
|
|
31
|
-
| `pause_task`
|
|
32
|
-
| `done_task`
|
|
33
|
-
| `skip_task`
|
|
34
|
-
| `reopen_task`
|
|
35
|
-
| `list_projects`
|
|
36
|
-
| `list_tags`
|
|
37
|
-
| `list_contexts`
|
|
38
|
-
| `list_routines`
|
|
39
|
-
| `list_time_blocks`
|
|
40
|
-
| `list_recent_tasks`
|
|
41
|
-
| `create_project` / `update_project` / `archive_project` / `delete_project`
|
|
42
|
-
| `create_tag` / `update_tag` / `archive_tag` / `delete_tag`
|
|
43
|
-
| `create_context` / `update_context` / `archive_context` / `delete_context`
|
|
44
|
-
| `create_routine` / `update_routine` / `archive_routine` / `delete_routine`
|
|
45
|
-
| `create_time_block` / `update_time_block` / `archive_time_block` / `delete_time_block` | タイムブロック CRUD。`weekdays` は 0=日曜~6=土曜の配列 (空不可)
|
|
46
|
-
| `set_actual_times`
|
|
47
|
-
| `interrupt_task`
|
|
48
|
-
| `add_parallel_task`
|
|
49
|
-
| `reorder_task`
|
|
33
|
+
| tool | 説明 |
|
|
34
|
+
| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `list_tasks` | 指定日 (省略時は今日) のタスク一覧を返す |
|
|
36
|
+
| `add_task` | タスクを追加する (title, date?, estimated_seconds?, description?, project_id?, context_id?, tag_ids?, scheduled_start_time?, notify_at_scheduled_start?, is_background?) |
|
|
37
|
+
| `update_task` | タスクのフィールドを部分更新する (タイトル / 詳細 / 見積もり / 予定時刻 / project / context / tags / 通知 / バックグラウンドフラグ)。`null` で明示クリア、`tag_ids` は全置換 |
|
|
38
|
+
| `delete_task` | タスクを削除する (Routine 由来タスクを今日以降に消すと `routine_expansions` に skip 記録、`hard_delete: true` で物理削除のみ) |
|
|
39
|
+
| `start_task` | タスクの計測を開始する (status → running) |
|
|
40
|
+
| `pause_task` | タスクを一時停止する (status → paused) |
|
|
41
|
+
| `done_task` | タスクを完了する (status → completed) |
|
|
42
|
+
| `skip_task` | タスクをスキップする (status → skipped)。reopen で planned に戻せる |
|
|
43
|
+
| `reopen_task` | completed / skipped を planned に戻す |
|
|
44
|
+
| `list_projects` | プロジェクト一覧 (status filter / limit 既定 100)。add/update_task の `project_id` を引くために使う |
|
|
45
|
+
| `list_tags` | タグ一覧。`tag_ids` を引くために使う |
|
|
46
|
+
| `list_contexts` | コンテキスト一覧。`context_id` を引くために使う |
|
|
47
|
+
| `list_routines` | ルーチン (繰り返しタスクテンプレート) 一覧。`frequency` は discriminated union (`{type: 'daily'}` / `{type: 'weekly', weekdays: [...]}` 等) のまま |
|
|
48
|
+
| `list_time_blocks` | タイムブロック (時間帯テンプレート) 一覧 |
|
|
49
|
+
| `list_recent_tasks` | 過去 N 日 (既定 90) のタスク。`query` で fuzzy match (ひらがな↔カタカナ正規化込み) 検索可能 |
|
|
50
|
+
| `create_project` / `update_project` / `archive_project` / `delete_project` | プロジェクト CRUD。`update` は partial / null クリア。`archive` はタスクの project_id を維持、`delete` は NULL 化 (ON DELETE SET NULL) |
|
|
51
|
+
| `create_tag` / `update_tag` / `archive_tag` / `delete_tag` | タグ CRUD。`delete` は tag_ids 配列に孤児 id を残すので注意 (UI 表示時に「不明なタグ」扱い) |
|
|
52
|
+
| `create_context` / `update_context` / `archive_context` / `delete_context` | コンテキスト CRUD。`delete` はタスクの context_id を NULL 化 |
|
|
53
|
+
| `create_routine` / `update_routine` / `archive_routine` / `delete_routine` | ルーチン CRUD。`update_routine` は **未来 planned task に自動伝播** (title / estimated / project / context / tags / scheduled_start_time)。手動編集も上書きされる点に注意。`propagated_task_count` を返す |
|
|
54
|
+
| `create_time_block` / `update_time_block` / `archive_time_block` / `delete_time_block` | タイムブロック CRUD。`weekdays` は 0=日曜~6=土曜の配列 (空不可) |
|
|
55
|
+
| `set_actual_times` | 実績開始/終了時刻を直接指定 (Play 押し忘れリカバリ用)。start+end → completed、start のみ → running、両方 null → planned リセット、end < start → 日跨ぎセッション |
|
|
56
|
+
| `interrupt_task` | 走行中タスクを「割り込み」で分割。1 呼び出しで 3 タスクを生成 (元タスク完了 / 割り込み running / 残作業 planned) + split note 3 件自動記録 |
|
|
57
|
+
| `add_parallel_task` | 進行中タスクと並行する新規タスクを追加。既定で running 状態 + 即計測開始 |
|
|
58
|
+
| `reorder_task` | 並び順 (order_index) と所属タイムブロックを直接指定 (慣習 10 単位) |
|
|
50
59
|
|
|
51
60
|
入力 `id` は full UUID。`list_tasks` の結果から取って各 tool に渡す想定。
|
|
52
61
|
`estimated_seconds` は秒数の整数 (CLI と違って文字列パースはしない)。
|
|
@@ -98,9 +107,7 @@ cp .mcp.json.example .mcp.json # repo root で
|
|
|
98
107
|
"mcpServers": {
|
|
99
108
|
"taskchute": {
|
|
100
109
|
"command": "node",
|
|
101
|
-
"args": [
|
|
102
|
-
"/path/to/TaskChute/packages/mcp/dist/index.js"
|
|
103
|
-
],
|
|
110
|
+
"args": ["/path/to/TaskChute/packages/mcp/dist/index.js"],
|
|
104
111
|
"env": {
|
|
105
112
|
"TASKCHUTE_SUPABASE_URL": "https://YOUR_PROJECT.supabase.co",
|
|
106
113
|
"TASKCHUTE_SUPABASE_PUBLISHABLE_KEY": "sb_publishable_..."
|
package/dist/index.js
CHANGED
|
@@ -23,9 +23,7 @@ function readSupabaseConfigFromEnv() {
|
|
|
23
23
|
const url = process.env.TASKCHUTE_SUPABASE_URL;
|
|
24
24
|
const publishableKey = process.env.TASKCHUTE_SUPABASE_PUBLISHABLE_KEY;
|
|
25
25
|
if (!url || !publishableKey) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
`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
|
+
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`);
|
|
29
27
|
}
|
|
30
28
|
return { url, publishableKey };
|
|
31
29
|
}
|
|
@@ -204,13 +202,10 @@ async function deriveKeyMaterial(password, salt, params = DEFAULT_ARGON2ID_PARAM
|
|
|
204
202
|
return copyToArrayBuffer(hash);
|
|
205
203
|
}
|
|
206
204
|
async function importKEK(material) {
|
|
207
|
-
return crypto.subtle.importKey(
|
|
208
|
-
"
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
false,
|
|
212
|
-
["wrapKey", "unwrapKey"]
|
|
213
|
-
);
|
|
205
|
+
return crypto.subtle.importKey("raw", material, { name: "AES-GCM" }, false, [
|
|
206
|
+
"wrapKey",
|
|
207
|
+
"unwrapKey"
|
|
208
|
+
]);
|
|
214
209
|
}
|
|
215
210
|
async function deriveKEK(password, salt, params = DEFAULT_ARGON2ID_PARAMS) {
|
|
216
211
|
const material = await deriveKeyMaterial(password, salt, params);
|
|
@@ -414,11 +409,7 @@ async function unlockMasterKey(snapshot, method) {
|
|
|
414
409
|
snapshot.passphrase.kdfParams
|
|
415
410
|
);
|
|
416
411
|
try {
|
|
417
|
-
return await unwrapMasterKey(
|
|
418
|
-
snapshot.passphrase.wrapped,
|
|
419
|
-
snapshot.passphrase.iv,
|
|
420
|
-
kek
|
|
421
|
-
);
|
|
412
|
+
return await unwrapMasterKey(snapshot.passphrase.wrapped, snapshot.passphrase.iv, kek);
|
|
422
413
|
} catch {
|
|
423
414
|
throw new UnlockError("Wrong passphrase", "wrong-key");
|
|
424
415
|
}
|
|
@@ -434,11 +425,7 @@ async function unlockMasterKey(snapshot, method) {
|
|
|
434
425
|
);
|
|
435
426
|
const kek = await importKEK(material);
|
|
436
427
|
try {
|
|
437
|
-
return await unwrapMasterKey(
|
|
438
|
-
snapshot.recovery.wrapped,
|
|
439
|
-
snapshot.recovery.iv,
|
|
440
|
-
kek
|
|
441
|
-
);
|
|
428
|
+
return await unwrapMasterKey(snapshot.recovery.wrapped, snapshot.recovery.iv, kek);
|
|
442
429
|
} catch {
|
|
443
430
|
throw new UnlockError("Wrong recovery key", "wrong-key");
|
|
444
431
|
}
|
|
@@ -492,13 +479,10 @@ function dateToIsoOrNull(date) {
|
|
|
492
479
|
// ../infra-supabase/src/supabase-context-repository.ts
|
|
493
480
|
async function rowToContext(row, cryptoSession) {
|
|
494
481
|
const [name, description] = await Promise.all([
|
|
495
|
-
decryptRequiredTextFromWire(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
row.key_id,
|
|
500
|
-
{ entityId: row.id, field: "name" }
|
|
501
|
-
),
|
|
482
|
+
decryptRequiredTextFromWire(cryptoSession, row.name_ciphertext, row.name_iv, row.key_id, {
|
|
483
|
+
entityId: row.id,
|
|
484
|
+
field: "name"
|
|
485
|
+
}),
|
|
502
486
|
decryptOptionalTextFromWire(
|
|
503
487
|
cryptoSession,
|
|
504
488
|
row.description_ciphertext,
|
|
@@ -615,13 +599,10 @@ var SupabaseContextRepository = class {
|
|
|
615
599
|
// ../infra-supabase/src/supabase-project-repository.ts
|
|
616
600
|
async function rowToProject(row, cryptoSession) {
|
|
617
601
|
const [name, description] = await Promise.all([
|
|
618
|
-
decryptRequiredTextFromWire(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
row.key_id,
|
|
623
|
-
{ entityId: row.id, field: "name" }
|
|
624
|
-
),
|
|
602
|
+
decryptRequiredTextFromWire(cryptoSession, row.name_ciphertext, row.name_iv, row.key_id, {
|
|
603
|
+
entityId: row.id,
|
|
604
|
+
field: "name"
|
|
605
|
+
}),
|
|
625
606
|
decryptOptionalTextFromWire(
|
|
626
607
|
cryptoSession,
|
|
627
608
|
row.description_ciphertext,
|
|
@@ -775,13 +756,10 @@ var SupabaseRoutineExpansionRepository = class {
|
|
|
775
756
|
// ../infra-supabase/src/supabase-routine-repository.ts
|
|
776
757
|
async function rowToRoutine(row, cryptoSession) {
|
|
777
758
|
const [title, description] = await Promise.all([
|
|
778
|
-
decryptRequiredTextFromWire(
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
row.key_id,
|
|
783
|
-
{ entityId: row.id, field: "title" }
|
|
784
|
-
),
|
|
759
|
+
decryptRequiredTextFromWire(cryptoSession, row.title_ciphertext, row.title_iv, row.key_id, {
|
|
760
|
+
entityId: row.id,
|
|
761
|
+
field: "title"
|
|
762
|
+
}),
|
|
785
763
|
decryptOptionalTextFromWire(
|
|
786
764
|
cryptoSession,
|
|
787
765
|
row.description_ciphertext,
|
|
@@ -922,13 +900,10 @@ var SupabaseRoutineRepository = class {
|
|
|
922
900
|
// ../infra-supabase/src/supabase-tag-repository.ts
|
|
923
901
|
async function rowToTag(row, cryptoSession) {
|
|
924
902
|
const [name, description] = await Promise.all([
|
|
925
|
-
decryptRequiredTextFromWire(
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
row.key_id,
|
|
930
|
-
{ entityId: row.id, field: "name" }
|
|
931
|
-
),
|
|
903
|
+
decryptRequiredTextFromWire(cryptoSession, row.name_ciphertext, row.name_iv, row.key_id, {
|
|
904
|
+
entityId: row.id,
|
|
905
|
+
field: "name"
|
|
906
|
+
}),
|
|
932
907
|
decryptOptionalTextFromWire(
|
|
933
908
|
cryptoSession,
|
|
934
909
|
row.description_ciphertext,
|
|
@@ -1039,13 +1014,10 @@ var SupabaseTagRepository = class {
|
|
|
1039
1014
|
// ../infra-supabase/src/supabase-task-note-repository.ts
|
|
1040
1015
|
async function rowToTaskNote(row, cryptoSession) {
|
|
1041
1016
|
const [body, metaJson, historyJson] = await Promise.all([
|
|
1042
|
-
decryptRequiredTextFromWire(
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
row.key_id,
|
|
1047
|
-
{ entityId: row.id, field: "body" }
|
|
1048
|
-
),
|
|
1017
|
+
decryptRequiredTextFromWire(cryptoSession, row.body_ciphertext, row.body_iv, row.key_id, {
|
|
1018
|
+
entityId: row.id,
|
|
1019
|
+
field: "body"
|
|
1020
|
+
}),
|
|
1049
1021
|
decryptOptionalTextFromWire(
|
|
1050
1022
|
cryptoSession,
|
|
1051
1023
|
row.metadata_ciphertext,
|
|
@@ -1136,9 +1108,7 @@ async function encryptTaskNoteFieldsInto(row, entityId, patch, cryptoSession) {
|
|
|
1136
1108
|
if (touched) {
|
|
1137
1109
|
const keyId = cryptoSession.getCurrentKeyId();
|
|
1138
1110
|
if (keyId === null) {
|
|
1139
|
-
throw new Error(
|
|
1140
|
-
"SupabaseTaskNoteRepository: session became locked mid-write"
|
|
1141
|
-
);
|
|
1111
|
+
throw new Error("SupabaseTaskNoteRepository: session became locked mid-write");
|
|
1142
1112
|
}
|
|
1143
1113
|
row.key_id = keyId;
|
|
1144
1114
|
}
|
|
@@ -1165,9 +1135,7 @@ var SupabaseTaskNoteRepository = class {
|
|
|
1165
1135
|
if (taskIds.length === 0) return result;
|
|
1166
1136
|
const { data, error } = await this.supabase.from("task_notes").select("*").in("task_id", taskIds).order("created_at", { ascending: true });
|
|
1167
1137
|
if (error) throw error;
|
|
1168
|
-
const decoded = await Promise.all(
|
|
1169
|
-
data.map((row) => rowToTaskNote(row, this.cryptoSession))
|
|
1170
|
-
);
|
|
1138
|
+
const decoded = await Promise.all(data.map((row) => rowToTaskNote(row, this.cryptoSession)));
|
|
1171
1139
|
for (const note of decoded) {
|
|
1172
1140
|
const bucket = result.get(note.taskId);
|
|
1173
1141
|
if (bucket) bucket.push(note);
|
|
@@ -1216,13 +1184,10 @@ var SupabaseTaskNoteRepository = class {
|
|
|
1216
1184
|
// ../infra-supabase/src/supabase-task-repository.ts
|
|
1217
1185
|
async function rowToTask(row, cryptoSession) {
|
|
1218
1186
|
const [title, description] = await Promise.all([
|
|
1219
|
-
decryptRequiredTextFromWire(
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
row.key_id,
|
|
1224
|
-
{ entityId: row.id, field: "title" }
|
|
1225
|
-
),
|
|
1187
|
+
decryptRequiredTextFromWire(cryptoSession, row.title_ciphertext, row.title_iv, row.key_id, {
|
|
1188
|
+
entityId: row.id,
|
|
1189
|
+
field: "title"
|
|
1190
|
+
}),
|
|
1226
1191
|
decryptOptionalTextFromWire(
|
|
1227
1192
|
cryptoSession,
|
|
1228
1193
|
row.description_ciphertext,
|
|
@@ -1666,20 +1631,41 @@ async function startTask(repo, taskId) {
|
|
|
1666
1631
|
return repo.update(taskId, patch);
|
|
1667
1632
|
}
|
|
1668
1633
|
async function pauseTask(repo, taskId) {
|
|
1669
|
-
const
|
|
1670
|
-
if (!
|
|
1671
|
-
if (!
|
|
1634
|
+
const target = await repo.findById(taskId);
|
|
1635
|
+
if (!target) throw new Error(`Task not found: ${taskId}`);
|
|
1636
|
+
if (!target.currentSession) {
|
|
1672
1637
|
throw new Error(`Task is not currently running: ${taskId}`);
|
|
1673
1638
|
}
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
|
|
1639
|
+
const now = /* @__PURE__ */ new Date();
|
|
1640
|
+
const closedSession = {
|
|
1641
|
+
startedAt: target.currentSession.startedAt,
|
|
1642
|
+
finishedAt: now
|
|
1677
1643
|
};
|
|
1678
|
-
|
|
1679
|
-
status: "
|
|
1680
|
-
sessions: [...
|
|
1644
|
+
const finishedOriginal = await repo.update(target.id, {
|
|
1645
|
+
status: "completed",
|
|
1646
|
+
sessions: [...target.sessions, closedSession],
|
|
1681
1647
|
currentSession: void 0
|
|
1682
1648
|
});
|
|
1649
|
+
const elapsed = calculateActualSeconds(finishedOriginal);
|
|
1650
|
+
const continuation = await repo.create({
|
|
1651
|
+
date: target.date,
|
|
1652
|
+
title: target.title,
|
|
1653
|
+
description: target.description,
|
|
1654
|
+
estimatedSeconds: Math.max(0, Math.floor(target.estimatedSeconds - elapsed)),
|
|
1655
|
+
sessions: [],
|
|
1656
|
+
status: "planned",
|
|
1657
|
+
orderIndex: target.orderIndex + 1,
|
|
1658
|
+
splitFromTaskId: target.id,
|
|
1659
|
+
projectId: target.projectId,
|
|
1660
|
+
contextId: target.contextId,
|
|
1661
|
+
// continuation は「中断したタスクの残り」。中断時刻を予定時刻にすることで
|
|
1662
|
+
// ソート上は元タスクの直後 (effectiveTime 同値 + orderIndex 順) に並び、
|
|
1663
|
+
// TimeBlock 境界をまたいだ場合も自然に新しいブロックへ追随する。
|
|
1664
|
+
scheduledStartTime: toTimeString(now),
|
|
1665
|
+
tagIds: target.tagIds,
|
|
1666
|
+
isBackground: target.isBackground
|
|
1667
|
+
});
|
|
1668
|
+
return { finishedOriginal, continuation };
|
|
1683
1669
|
}
|
|
1684
1670
|
async function skipTask(repo, taskId) {
|
|
1685
1671
|
const task = await repo.findById(taskId);
|
|
@@ -1834,19 +1820,13 @@ function buildSetActualTimesPatch(task, input) {
|
|
|
1834
1820
|
// ../domain/src/task-reorder.ts
|
|
1835
1821
|
function computeNextOrderIndex(existingTasks) {
|
|
1836
1822
|
if (existingTasks.length === 0) return 10;
|
|
1837
|
-
const max = existingTasks.reduce(
|
|
1838
|
-
(acc, t) => t.orderIndex > acc ? t.orderIndex : acc,
|
|
1839
|
-
0
|
|
1840
|
-
);
|
|
1823
|
+
const max = existingTasks.reduce((acc, t) => t.orderIndex > acc ? t.orderIndex : acc, 0);
|
|
1841
1824
|
return max + 10;
|
|
1842
1825
|
}
|
|
1843
1826
|
|
|
1844
1827
|
// ../domain/src/task-suggest.ts
|
|
1845
1828
|
function normalizeForSearch(str) {
|
|
1846
|
-
return str.trim().toLowerCase().replace(
|
|
1847
|
-
/[ぁ-ゖ]/g,
|
|
1848
|
-
(ch) => String.fromCharCode(ch.charCodeAt(0) + 96)
|
|
1849
|
-
);
|
|
1829
|
+
return str.trim().toLowerCase().replace(/[ぁ-ゖ]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) + 96));
|
|
1850
1830
|
}
|
|
1851
1831
|
function fuzzyScore(query, target) {
|
|
1852
1832
|
const q = query;
|
|
@@ -1909,7 +1889,6 @@ function findSimilarTasks(query, tasks, limit = 5) {
|
|
|
1909
1889
|
function describeStatusTransition(from, to) {
|
|
1910
1890
|
if (from === "planned" && to === "running") return "\u958B\u59CB";
|
|
1911
1891
|
if (from === "paused" && to === "running") return "\u518D\u958B";
|
|
1912
|
-
if (from === "running" && to === "paused") return "\u4E00\u6642\u505C\u6B62";
|
|
1913
1892
|
if (to === "completed") return "\u5B8C\u4E86";
|
|
1914
1893
|
if (to === "skipped") return "\u30B9\u30AD\u30C3\u30D7";
|
|
1915
1894
|
if (to === "planned") return "\u672A\u7740\u624B\u306B\u623B\u3059";
|
|
@@ -2159,6 +2138,42 @@ async function interruptTaskWithAudit(taskRepo, taskNoteRepo, params) {
|
|
|
2159
2138
|
}
|
|
2160
2139
|
return result;
|
|
2161
2140
|
}
|
|
2141
|
+
async function pauseTaskWithAudit(taskRepo, taskNoteRepo, taskId) {
|
|
2142
|
+
const result = await pauseTask(taskRepo, taskId);
|
|
2143
|
+
const notes = buildPauseSplitNoteInputs(result.finishedOriginal, result.continuation);
|
|
2144
|
+
try {
|
|
2145
|
+
await Promise.all(notes.map((input) => taskNoteRepo.create(input)));
|
|
2146
|
+
} catch (err) {
|
|
2147
|
+
console.error("[audit] failed to record pause split notes", {
|
|
2148
|
+
sourceTaskId: result.finishedOriginal.id,
|
|
2149
|
+
err
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
return result;
|
|
2153
|
+
}
|
|
2154
|
+
function buildPauseSplitNoteInputs(source, continuation) {
|
|
2155
|
+
const sourceTitle = titleOrPlaceholder(source);
|
|
2156
|
+
return [
|
|
2157
|
+
{
|
|
2158
|
+
taskId: source.id,
|
|
2159
|
+
type: "split",
|
|
2160
|
+
body: `\u4E2D\u65AD\u3057\u3066\u6B8B\u4F5C\u696D\u3092\u5206\u5272\u3002\u7D9A\u304D\u306F\u5225\u30BF\u30B9\u30AF\u3067\u7D99\u7D9A\u3002`,
|
|
2161
|
+
metadata: {
|
|
2162
|
+
event: "pause-source",
|
|
2163
|
+
continuationTaskId: continuation.id
|
|
2164
|
+
}
|
|
2165
|
+
},
|
|
2166
|
+
{
|
|
2167
|
+
taskId: continuation.id,
|
|
2168
|
+
type: "split",
|
|
2169
|
+
body: `\u300C${sourceTitle}\u300D\u306E\u6B8B\u4F5C\u696D\u3068\u3057\u3066\u7D99\u7D9A\u3002`,
|
|
2170
|
+
metadata: {
|
|
2171
|
+
event: "pause-continuation",
|
|
2172
|
+
sourceTaskId: source.id
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
];
|
|
2176
|
+
}
|
|
2162
2177
|
function buildSplitNoteInputs(source, interruption, continuation) {
|
|
2163
2178
|
const sourceTitle = titleOrPlaceholder(source);
|
|
2164
2179
|
const interruptionTitle = titleOrPlaceholder(interruption);
|
|
@@ -2195,8 +2210,6 @@ function buildSplitNoteInputs(source, interruption, continuation) {
|
|
|
2195
2210
|
}
|
|
2196
2211
|
];
|
|
2197
2212
|
}
|
|
2198
|
-
|
|
2199
|
-
// ../domain/src/routine-expansion.ts
|
|
2200
2213
|
function buildRoutineTaskPatch(routine) {
|
|
2201
2214
|
return {
|
|
2202
2215
|
title: routine.title,
|
|
@@ -2518,7 +2531,10 @@ async function addTaskHandler(ctx, input) {
|
|
|
2518
2531
|
}
|
|
2519
2532
|
if (input.tag_ids && input.tag_ids.length > 0) {
|
|
2520
2533
|
const checked = await Promise.all(
|
|
2521
|
-
input.tag_ids.map(async (id) => ({
|
|
2534
|
+
input.tag_ids.map(async (id) => ({
|
|
2535
|
+
id,
|
|
2536
|
+
exists: await ctx.repos.tag.findById(id) !== null
|
|
2537
|
+
}))
|
|
2522
2538
|
);
|
|
2523
2539
|
const missing = checked.filter((c) => !c.exists).map((c) => c.id);
|
|
2524
2540
|
if (missing.length > 0) {
|
|
@@ -2719,9 +2735,7 @@ var createContextInputSchema = z.object({
|
|
|
2719
2735
|
description: z.string().optional(),
|
|
2720
2736
|
icon: z.string().optional().describe("\u7D75\u6587\u5B57\u307E\u305F\u306F\u30A2\u30A4\u30B3\u30F3\u540D (\u4F8B: \u{1F3E0} / mdi-home)"),
|
|
2721
2737
|
color: z.string().optional().describe("UI \u8272 (\u4F8B: #ff6b6b)"),
|
|
2722
|
-
notify_at_scheduled_start: z.boolean().optional().describe(
|
|
2723
|
-
"true \u306A\u3089\u3001\u3053\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u4ED8\u3051\u305F\u65B0\u898F/\u66F4\u65B0\u30BF\u30B9\u30AF\u306E\u901A\u77E5\u3092 ON \u306B\u81EA\u52D5\u7D99\u627F\u3059\u308B"
|
|
2724
|
-
)
|
|
2738
|
+
notify_at_scheduled_start: z.boolean().optional().describe("true \u306A\u3089\u3001\u3053\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u4ED8\u3051\u305F\u65B0\u898F/\u66F4\u65B0\u30BF\u30B9\u30AF\u306E\u901A\u77E5\u3092 ON \u306B\u81EA\u52D5\u7D99\u627F\u3059\u308B")
|
|
2725
2739
|
});
|
|
2726
2740
|
async function createContextHandler(ctx, input) {
|
|
2727
2741
|
const createInput = {
|
|
@@ -2772,12 +2786,23 @@ var frequencySchema = z.discriminatedUnion("type", [
|
|
|
2772
2786
|
z.object({ type: z.literal("daily") }),
|
|
2773
2787
|
z.object({
|
|
2774
2788
|
type: z.literal("weekly"),
|
|
2775
|
-
weekdays: z.array(z.number().int().min(0).max(6)).min(1, "at least 1 weekday")
|
|
2789
|
+
weekdays: z.array(z.number().int().min(0).max(6)).min(1, "at least 1 weekday"),
|
|
2790
|
+
intervalWeeks: z.number().int().min(1).max(4).optional().describe("1=\u6BCE\u9031 (default) / 2=\u9694\u9031 / 3+ \u3082\u540C\u69D8\u3002anchorDate \u3068\u7D44\u3067\u4F7F\u3046"),
|
|
2791
|
+
anchorDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("intervalWeeks>=2 \u306E\u57FA\u6E96\u9031\u3092\u542B\u3080\u65E5\u4ED8\u3002\u4F8B: 2026-06-22")
|
|
2776
2792
|
}),
|
|
2777
2793
|
z.object({
|
|
2778
2794
|
type: z.literal("monthly"),
|
|
2779
2795
|
days: z.array(z.number().int().min(1).max(31)).min(1, "at least 1 day")
|
|
2780
2796
|
}),
|
|
2797
|
+
z.object({
|
|
2798
|
+
type: z.literal("monthly-nth-weekday"),
|
|
2799
|
+
occurrences: z.array(
|
|
2800
|
+
z.object({
|
|
2801
|
+
nth: z.number().int().refine((n) => n >= 1 && n <= 5 || n === -1, "nth must be 1-5 or -1 (last)"),
|
|
2802
|
+
weekday: z.number().int().min(0).max(6)
|
|
2803
|
+
})
|
|
2804
|
+
).min(1, "at least 1 occurrence").describe("\u4F8B: [{nth:2,weekday:5},{nth:4,weekday:5}] \u3067\u7B2C2\u30FB\u7B2C4\u91D1\u66DC\u3002nth=-1 \u306F\u6700\u7D42\u9031")
|
|
2805
|
+
}),
|
|
2781
2806
|
z.object({ type: z.literal("custom"), cron: z.string().min(1) })
|
|
2782
2807
|
]);
|
|
2783
2808
|
var createRoutineInputSchema = z.object({
|
|
@@ -3128,12 +3153,13 @@ var pauseTaskInputSchema = z.object({
|
|
|
3128
3153
|
id: z.string().uuid({ message: "id must be a UUID" })
|
|
3129
3154
|
});
|
|
3130
3155
|
async function pauseTaskHandler(ctx, input) {
|
|
3131
|
-
const
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3156
|
+
const result = await wrapDomainErrors(
|
|
3157
|
+
() => pauseTaskWithAudit(ctx.repos.task, ctx.repos.taskNote, input.id)
|
|
3158
|
+
);
|
|
3159
|
+
return {
|
|
3160
|
+
finished_original: taskSummaryFrom(result.finishedOriginal),
|
|
3161
|
+
continuation: taskSummaryFrom(result.continuation)
|
|
3162
|
+
};
|
|
3137
3163
|
}
|
|
3138
3164
|
var reopenTaskInputSchema = z.object({
|
|
3139
3165
|
id: z.string().uuid({ message: "id must be a UUID" })
|
|
@@ -3216,9 +3242,7 @@ var updateContextInputSchema = z.object({
|
|
|
3216
3242
|
icon: z.union([z.string(), z.null()]).optional(),
|
|
3217
3243
|
color: z.union([z.string(), z.null()]).optional(),
|
|
3218
3244
|
status: z.enum(["active", "archived"]).optional().describe("archived \u5316\u306F archive_context tool \u63A8\u5968"),
|
|
3219
|
-
notify_at_scheduled_start: z.boolean().optional().describe(
|
|
3220
|
-
"true \u306A\u3089\u3001\u3053\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u4ED8\u3051\u305F\u65B0\u898F/\u66F4\u65B0\u30BF\u30B9\u30AF\u306E\u901A\u77E5\u3092 ON \u306B\u81EA\u52D5\u7D99\u627F\u3059\u308B"
|
|
3221
|
-
)
|
|
3245
|
+
notify_at_scheduled_start: z.boolean().optional().describe("true \u306A\u3089\u3001\u3053\u306E\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u4ED8\u3051\u305F\u65B0\u898F/\u66F4\u65B0\u30BF\u30B9\u30AF\u306E\u901A\u77E5\u3092 ON \u306B\u81EA\u52D5\u7D99\u627F\u3059\u308B")
|
|
3222
3246
|
});
|
|
3223
3247
|
async function updateContextHandler(ctx, input) {
|
|
3224
3248
|
const existing = await ctx.repos.context.findById(input.id);
|
|
@@ -3296,12 +3320,23 @@ var frequencySchema2 = z.discriminatedUnion("type", [
|
|
|
3296
3320
|
z.object({ type: z.literal("daily") }),
|
|
3297
3321
|
z.object({
|
|
3298
3322
|
type: z.literal("weekly"),
|
|
3299
|
-
weekdays: z.array(z.number().int().min(0).max(6)).min(1)
|
|
3323
|
+
weekdays: z.array(z.number().int().min(0).max(6)).min(1),
|
|
3324
|
+
intervalWeeks: z.number().int().min(1).max(4).optional(),
|
|
3325
|
+
anchorDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
|
|
3300
3326
|
}),
|
|
3301
3327
|
z.object({
|
|
3302
3328
|
type: z.literal("monthly"),
|
|
3303
3329
|
days: z.array(z.number().int().min(1).max(31)).min(1)
|
|
3304
3330
|
}),
|
|
3331
|
+
z.object({
|
|
3332
|
+
type: z.literal("monthly-nth-weekday"),
|
|
3333
|
+
occurrences: z.array(
|
|
3334
|
+
z.object({
|
|
3335
|
+
nth: z.number().int().refine((n) => n >= 1 && n <= 5 || n === -1, "nth must be 1-5 or -1 (last)"),
|
|
3336
|
+
weekday: z.number().int().min(0).max(6)
|
|
3337
|
+
})
|
|
3338
|
+
).min(1)
|
|
3339
|
+
}),
|
|
3305
3340
|
z.object({ type: z.literal("custom"), cron: z.string().min(1) })
|
|
3306
3341
|
]);
|
|
3307
3342
|
var updateRoutineInputSchema = z.object({
|
|
@@ -3936,7 +3971,7 @@ function createMcpServer(opts) {
|
|
|
3936
3971
|
|
|
3937
3972
|
// src/index.ts
|
|
3938
3973
|
async function main() {
|
|
3939
|
-
const version = "0.
|
|
3974
|
+
const version = "0.6.0" ;
|
|
3940
3975
|
const server = createMcpServer({ version });
|
|
3941
3976
|
const transport = new StdioServerTransport();
|
|
3942
3977
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fukuchang/taskchute-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "TaskChute MCP server — Model Context Protocol 経由で E2EE 暗号化された TaskChute タスクを Claude / 各種 MCP クライアントから操作する",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "fukuchang",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"e2ee"
|
|
24
24
|
],
|
|
25
25
|
"engines": {
|
|
26
|
-
"node": "
|
|
26
|
+
"node": "^22.23.4 || ^24.22.1 || ^26.12.1"
|
|
27
27
|
},
|
|
28
28
|
"type": "module",
|
|
29
29
|
"main": "./dist/index.js",
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
"zod": "^4.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^24.12.3",
|
|
52
|
-
"tsup": "^8.5.1",
|
|
53
|
-
"tsx": "^4.20.0",
|
|
54
|
-
"typescript": "~6.0.2",
|
|
55
51
|
"@taskchute/crypto": "0.0.0",
|
|
56
52
|
"@taskchute/domain": "0.0.0",
|
|
57
53
|
"@taskchute/infra-supabase": "0.0.0",
|
|
58
|
-
"@taskchute/node-shared": "0.0.0"
|
|
54
|
+
"@taskchute/node-shared": "0.0.0",
|
|
55
|
+
"@types/node": "^24.12.3",
|
|
56
|
+
"tsup": "^8.5.1",
|
|
57
|
+
"tsx": "^4.20.0",
|
|
58
|
+
"typescript": "~6.0.2"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"start": "tsx src/index.ts",
|