@fukuchang/taskchute-cli 0.1.2 → 0.3.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/dist/index.js CHANGED
@@ -1,12 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { Entry } from '@napi-rs/keyring';
4
3
  import { input, password } from '@inquirer/prompts';
5
- import { createClient } from '@supabase/supabase-js';
4
+ import { Entry } from '@napi-rs/keyring';
6
5
  import { argon2id } from 'hash-wasm';
7
6
  import '@scure/bip39';
8
7
  import '@scure/bip39/wordlists/english.js';
8
+ import { createClient } from '@supabase/supabase-js';
9
+
10
+ // ../node-shared/src/app-context-error.ts
11
+ var AppContextError = class extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "AppContextError";
15
+ }
16
+ };
9
17
 
18
+ // ../node-shared/src/config.ts
19
+ var ENV_URL = "TASKCHUTE_SUPABASE_URL";
20
+ var ENV_KEY = "TASKCHUTE_SUPABASE_PUBLISHABLE_KEY";
21
+ function readSupabaseConfigFromEnv() {
22
+ const url = process.env.TASKCHUTE_SUPABASE_URL;
23
+ const publishableKey = process.env.TASKCHUTE_SUPABASE_PUBLISHABLE_KEY;
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
+ );
28
+ }
29
+ return { url, publishableKey };
30
+ }
10
31
  var KEYRING_SERVICE = "taskchute-cli";
11
32
  var KEYRING_ACCOUNT = "passphrase";
12
33
  var KeyringPassphraseStore = class {
@@ -30,37 +51,28 @@ var KeyringPassphraseStore = class {
30
51
  }
31
52
  }
32
53
  };
33
-
34
- // src/commands/lock.ts
35
- function lockCommand() {
36
- return new Command("lock").description("keyring \u304B\u3089 E2EE passphrase \u3092\u524A\u9664\u3059\u308B (session \u306F\u7DAD\u6301)").action(() => {
37
- new KeyringPassphraseStore().clear();
38
- console.log("locked");
39
- });
40
- }
41
- function createNodeSupabaseClient(config, options) {
42
- return createClient(config.url, config.publishableKey, {
43
- auth: {
44
- storage: options.storage,
45
- persistSession: true,
46
- autoRefreshToken: true,
47
- detectSessionInUrl: false
54
+ var KEYRING_SERVICE2 = "taskchute-cli";
55
+ var KeyringSessionStore = class {
56
+ getItem(key) {
57
+ try {
58
+ const entry = new Entry(KEYRING_SERVICE2, key);
59
+ return entry.getPassword();
60
+ } catch {
61
+ return null;
48
62
  }
49
- });
50
- }
51
-
52
- // ../infra-supabase/src/supabase-utils.ts
53
- async function getCurrentUserId(supabase) {
54
- const { data, error } = await supabase.auth.getUser();
55
- if (error) throw error;
56
- if (!data.user) {
57
- throw new Error("Not authenticated. Sign in to access this resource.");
58
63
  }
59
- return data.user.id;
60
- }
61
- function dateToIsoOrNull(date) {
62
- return date ? date.toISOString() : null;
63
- }
64
+ setItem(key, value) {
65
+ const entry = new Entry(KEYRING_SERVICE2, key);
66
+ entry.setPassword(value);
67
+ }
68
+ removeItem(key) {
69
+ try {
70
+ const entry = new Entry(KEYRING_SERVICE2, key);
71
+ entry.deletePassword();
72
+ } catch {
73
+ }
74
+ }
75
+ };
64
76
 
65
77
  // ../crypto/src/bytes.ts
66
78
  function allocBytes(length) {
@@ -452,6 +464,29 @@ async function unlockMasterKey(snapshot, method) {
452
464
  function findPasskeyWrap(wraps, credentialId) {
453
465
  return wraps.find((w) => w.credentialId === credentialId);
454
466
  }
467
+ function createNodeSupabaseClient(config, options) {
468
+ return createClient(config.url, config.publishableKey, {
469
+ auth: {
470
+ storage: options.storage,
471
+ persistSession: true,
472
+ autoRefreshToken: true,
473
+ detectSessionInUrl: false
474
+ }
475
+ });
476
+ }
477
+
478
+ // ../infra-supabase/src/supabase-utils.ts
479
+ async function getCurrentUserId(supabase) {
480
+ const { data, error } = await supabase.auth.getUser();
481
+ if (error) throw error;
482
+ if (!data.user) {
483
+ throw new Error("Not authenticated. Sign in to access this resource.");
484
+ }
485
+ return data.user.id;
486
+ }
487
+ function dateToIsoOrNull(date) {
488
+ return date ? date.toISOString() : null;
489
+ }
455
490
 
456
491
  // ../infra-supabase/src/supabase-context-repository.ts
457
492
  async function rowToContext(row, cryptoSession) {
@@ -699,6 +734,39 @@ var SupabaseProjectRepository = class {
699
734
  }
700
735
  };
701
736
 
737
+ // ../infra-supabase/src/supabase-routine-expansion-repository.ts
738
+ function rowToExpansion(row) {
739
+ return {
740
+ routineId: row.routine_id,
741
+ date: row.date,
742
+ status: row.status
743
+ };
744
+ }
745
+ var SupabaseRoutineExpansionRepository = class {
746
+ supabase;
747
+ constructor(supabase) {
748
+ this.supabase = supabase;
749
+ }
750
+ async findByDate(date) {
751
+ const { data, error } = await this.supabase.from("routine_expansions").select("*").eq("date", date);
752
+ if (error) throw error;
753
+ return data.map(rowToExpansion);
754
+ }
755
+ async recordExpanded(routineId, date) {
756
+ const userId = await getCurrentUserId(this.supabase);
757
+ const { error } = await this.supabase.from("routine_expansions").upsert(
758
+ {
759
+ user_id: userId,
760
+ routine_id: routineId,
761
+ date,
762
+ status: "expanded"
763
+ },
764
+ { onConflict: "routine_id,date", ignoreDuplicates: true }
765
+ );
766
+ if (error) throw error;
767
+ }
768
+ };
769
+
702
770
  // ../infra-supabase/src/supabase-routine-repository.ts
703
771
  async function rowToRoutine(row, cryptoSession) {
704
772
  const [title, description] = await Promise.all([
@@ -736,7 +804,6 @@ async function rowToRoutine(row, cryptoSession) {
736
804
  }
737
805
  if (row.start_date !== null) routine.startDate = row.start_date;
738
806
  if (row.end_date !== null) routine.endDate = row.end_date;
739
- if (row.last_expanded_date !== null) routine.lastExpandedDate = row.last_expanded_date;
740
807
  return routine;
741
808
  }
742
809
  function nonEncryptedFieldsToRow3(input2) {
@@ -758,9 +825,6 @@ function nonEncryptedFieldsToRow3(input2) {
758
825
  if ("autoExpand" in input2 && input2.autoExpand !== void 0) {
759
826
  row.auto_expand = input2.autoExpand;
760
827
  }
761
- if ("lastExpandedDate" in input2) {
762
- row.last_expanded_date = input2.lastExpandedDate ?? null;
763
- }
764
828
  if ("status" in input2 && input2.status !== void 0) row.status = input2.status;
765
829
  return row;
766
830
  }
@@ -1317,6 +1381,13 @@ var SupabaseTaskRepository = class {
1317
1381
  const { error } = await this.supabase.from("tasks").delete().eq("id", id);
1318
1382
  if (error) throw error;
1319
1383
  }
1384
+ async deleteWithRoutineSkip(id, today) {
1385
+ const { error } = await this.supabase.rpc("delete_task_and_skip_routine", {
1386
+ p_task_id: id,
1387
+ p_today: today
1388
+ });
1389
+ if (error) throw error;
1390
+ }
1320
1391
  };
1321
1392
 
1322
1393
  // ../infra-supabase/src/supabase-time-block-repository.ts
@@ -1526,6 +1597,13 @@ function toDateString(date) {
1526
1597
  const day = String(date.getDate()).padStart(2, "0");
1527
1598
  return `${year}-${month}-${day}`;
1528
1599
  }
1600
+ function fromDateString(dateString) {
1601
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateString);
1602
+ if (!match || !match[1] || !match[2] || !match[3]) {
1603
+ throw new Error(`Invalid date string: ${dateString}`);
1604
+ }
1605
+ return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
1606
+ }
1529
1607
  function todayDateString() {
1530
1608
  return toDateString(/* @__PURE__ */ new Date());
1531
1609
  }
@@ -1534,8 +1612,40 @@ function toTimeString(date) {
1534
1612
  const minutes = String(date.getMinutes()).padStart(2, "0");
1535
1613
  return `${hours}:${minutes}`;
1536
1614
  }
1615
+ function toTimeStringWithSeconds(date) {
1616
+ const hours = String(date.getHours()).padStart(2, "0");
1617
+ const minutes = String(date.getMinutes()).padStart(2, "0");
1618
+ const seconds = String(date.getSeconds()).padStart(2, "0");
1619
+ return `${hours}:${minutes}:${seconds}`;
1620
+ }
1621
+
1622
+ // ../domain/src/task-utils.ts
1623
+ function sessionDurationSeconds(session) {
1624
+ return (session.finishedAt.getTime() - session.startedAt.getTime()) / 1e3;
1625
+ }
1626
+ function completedSessionsTotalSeconds(task) {
1627
+ return task.sessions.reduce((sum, s) => sum + sessionDurationSeconds(s), 0);
1628
+ }
1629
+ function calculateActualSeconds(task, now = /* @__PURE__ */ new Date()) {
1630
+ const completed = completedSessionsTotalSeconds(task);
1631
+ if (task.currentSession) {
1632
+ return completed + (now.getTime() - task.currentSession.startedAt.getTime()) / 1e3;
1633
+ }
1634
+ return completed;
1635
+ }
1537
1636
 
1538
1637
  // ../domain/src/task-operations.ts
1638
+ var TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/;
1639
+ function parseTimeOnDate(dateString, timeString) {
1640
+ const m = TIME_RE.exec(timeString);
1641
+ if (!m || !m[1] || !m[2]) {
1642
+ throw new Error(`Invalid time string: ${timeString}`);
1643
+ }
1644
+ const base = fromDateString(dateString);
1645
+ const seconds = m[3] ? Number(m[3]) : 0;
1646
+ base.setHours(Number(m[1]), Number(m[2]), seconds, 0);
1647
+ return base;
1648
+ }
1539
1649
  async function startTask(repo, taskId) {
1540
1650
  const task = await repo.findById(taskId);
1541
1651
  if (!task) throw new Error(`Task not found: ${taskId}`);
@@ -1546,7 +1656,7 @@ async function startTask(repo, taskId) {
1546
1656
  currentSession: { startedAt: now }
1547
1657
  };
1548
1658
  if (!task.actualStartTime) {
1549
- patch.actualStartTime = toTimeString(now);
1659
+ patch.actualStartTime = toTimeStringWithSeconds(now);
1550
1660
  }
1551
1661
  return repo.update(taskId, patch);
1552
1662
  }
@@ -1566,6 +1676,30 @@ async function pauseTask(repo, taskId) {
1566
1676
  currentSession: void 0
1567
1677
  });
1568
1678
  }
1679
+ async function skipTask(repo, taskId) {
1680
+ const task = await repo.findById(taskId);
1681
+ if (!task) throw new Error(`Task not found: ${taskId}`);
1682
+ const updates = { status: "skipped" };
1683
+ if (task.currentSession) {
1684
+ updates.sessions = [
1685
+ ...task.sessions,
1686
+ {
1687
+ startedAt: task.currentSession.startedAt,
1688
+ finishedAt: /* @__PURE__ */ new Date()
1689
+ }
1690
+ ];
1691
+ updates.currentSession = void 0;
1692
+ }
1693
+ return repo.update(taskId, updates);
1694
+ }
1695
+ async function reopenTask(repo, taskId) {
1696
+ const task = await repo.findById(taskId);
1697
+ if (!task) throw new Error(`Task not found: ${taskId}`);
1698
+ if (task.status !== "completed" && task.status !== "skipped") {
1699
+ return task;
1700
+ }
1701
+ return repo.update(taskId, { status: "planned" });
1702
+ }
1569
1703
  async function completeTask(repo, taskId) {
1570
1704
  const task = await repo.findById(taskId);
1571
1705
  if (!task) throw new Error(`Task not found: ${taskId}`);
@@ -1582,6 +1716,115 @@ async function completeTask(repo, taskId) {
1582
1716
  }
1583
1717
  return repo.update(taskId, updates);
1584
1718
  }
1719
+ async function interruptTask(repo, params) {
1720
+ const target = await repo.findById(params.targetTaskId);
1721
+ if (!target) throw new Error(`Task not found: ${params.targetTaskId}`);
1722
+ if (!target.currentSession) {
1723
+ throw new Error(`Target task is not currently running`);
1724
+ }
1725
+ const now = /* @__PURE__ */ new Date();
1726
+ const closedSession = {
1727
+ startedAt: target.currentSession.startedAt,
1728
+ finishedAt: now
1729
+ };
1730
+ const finishedOriginal = await repo.update(target.id, {
1731
+ status: "completed",
1732
+ sessions: [...target.sessions, closedSession],
1733
+ currentSession: void 0
1734
+ });
1735
+ const elapsed = calculateActualSeconds(finishedOriginal);
1736
+ const interruptionStartedAt = /* @__PURE__ */ new Date();
1737
+ const interruption = await repo.create({
1738
+ date: target.date,
1739
+ title: params.interruption.title,
1740
+ description: params.interruption.description,
1741
+ estimatedSeconds: params.interruption.estimatedSeconds,
1742
+ sessions: [],
1743
+ currentSession: { startedAt: interruptionStartedAt },
1744
+ status: "running",
1745
+ orderIndex: target.orderIndex + 1,
1746
+ actualStartTime: toTimeStringWithSeconds(interruptionStartedAt),
1747
+ projectId: params.interruption.projectId,
1748
+ contextId: params.interruption.contextId,
1749
+ tagIds: params.interruption.tagIds ?? []
1750
+ });
1751
+ const continuation = await repo.create({
1752
+ date: target.date,
1753
+ title: target.title,
1754
+ description: target.description,
1755
+ estimatedSeconds: Math.max(0, Math.floor(target.estimatedSeconds - elapsed)),
1756
+ sessions: [],
1757
+ status: "planned",
1758
+ orderIndex: target.orderIndex + 2,
1759
+ splitFromTaskId: target.id,
1760
+ projectId: target.projectId,
1761
+ contextId: target.contextId,
1762
+ // continuation は「割り込みが片付いた後にやる残作業」。割り込み発生時刻を
1763
+ // 予定時刻にすることで、(1) interruption と同じ effectiveTime になり、
1764
+ // ソート上 orderIndex で interruption の次に並ぶ、(2) 割り込みが TimeBlock
1765
+ // の境界をまたいだ場合は continuation も新しいブロックに移動する、という
1766
+ // 自然な挙動になる。
1767
+ scheduledStartTime: toTimeString(interruptionStartedAt),
1768
+ tagIds: target.tagIds,
1769
+ isBackground: target.isBackground
1770
+ });
1771
+ return { finishedOriginal, interruption, continuation };
1772
+ }
1773
+ async function addParallelTask(repo, input2) {
1774
+ const startImmediately = input2.startImmediately ?? true;
1775
+ const existing = await repo.findByDate(input2.date);
1776
+ const maxOrder = existing.reduce((max, t) => t.orderIndex > max ? t.orderIndex : max, 0);
1777
+ const startedAt = startImmediately ? /* @__PURE__ */ new Date() : void 0;
1778
+ return repo.create({
1779
+ date: input2.date,
1780
+ title: input2.title,
1781
+ description: input2.description,
1782
+ estimatedSeconds: input2.estimatedSeconds,
1783
+ sessions: [],
1784
+ currentSession: startedAt ? { startedAt } : void 0,
1785
+ status: startImmediately ? "running" : "planned",
1786
+ orderIndex: maxOrder + 10,
1787
+ actualStartTime: startedAt ? toTimeStringWithSeconds(startedAt) : void 0,
1788
+ projectId: input2.projectId,
1789
+ contextId: input2.contextId,
1790
+ tagIds: input2.tagIds ?? [],
1791
+ isBackground: input2.isBackground
1792
+ });
1793
+ }
1794
+ function buildSetActualTimesPatch(task, input2) {
1795
+ const startTime = input2.startTime?.trim() ? input2.startTime.trim() : null;
1796
+ const endTime = input2.endTime?.trim() ? input2.endTime.trim() : null;
1797
+ if (startTime === null && endTime === null) {
1798
+ return {
1799
+ status: "planned",
1800
+ currentSession: void 0,
1801
+ sessions: [],
1802
+ actualStartTime: void 0
1803
+ };
1804
+ }
1805
+ if (startTime === null && endTime !== null) {
1806
+ throw new Error("endTime requires startTime");
1807
+ }
1808
+ const startedAt = parseTimeOnDate(task.date, startTime);
1809
+ if (endTime === null) {
1810
+ return {
1811
+ status: "running",
1812
+ currentSession: { startedAt },
1813
+ sessions: [],
1814
+ actualStartTime: startTime
1815
+ };
1816
+ }
1817
+ let finishedAt = parseTimeOnDate(task.date, endTime);
1818
+ if (finishedAt.getTime() < startedAt.getTime()) {
1819
+ finishedAt = new Date(finishedAt.getTime() + 24 * 60 * 60 * 1e3);
1820
+ }
1821
+ return {
1822
+ status: "completed",
1823
+ currentSession: void 0,
1824
+ sessions: [{ startedAt, finishedAt }],
1825
+ actualStartTime: startTime
1826
+ };
1827
+ }
1585
1828
 
1586
1829
  // ../domain/src/task-reorder.ts
1587
1830
  function computeNextOrderIndex(existingTasks) {
@@ -1593,6 +1836,70 @@ function computeNextOrderIndex(existingTasks) {
1593
1836
  return max + 10;
1594
1837
  }
1595
1838
 
1839
+ // ../domain/src/task-suggest.ts
1840
+ function normalizeForSearch(str) {
1841
+ return str.trim().toLowerCase().replace(
1842
+ /[ぁ-ゖ]/g,
1843
+ (ch) => String.fromCharCode(ch.charCodeAt(0) + 96)
1844
+ );
1845
+ }
1846
+ function fuzzyScore(query, target) {
1847
+ const q = query;
1848
+ const t = target;
1849
+ if (q.length === 0) return 0;
1850
+ if (t.length === 0) return null;
1851
+ let qi = 0;
1852
+ let score = 0;
1853
+ let lastMatchIndex = -2;
1854
+ let consecutiveRun = 0;
1855
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
1856
+ if (t[ti] === q[qi]) {
1857
+ if (lastMatchIndex === ti - 1) {
1858
+ consecutiveRun += 1;
1859
+ score += 5 + consecutiveRun * 3;
1860
+ } else {
1861
+ consecutiveRun = 0;
1862
+ score += 2;
1863
+ }
1864
+ if (qi === 0) {
1865
+ score += Math.max(0, 8 - ti);
1866
+ }
1867
+ lastMatchIndex = ti;
1868
+ qi += 1;
1869
+ }
1870
+ }
1871
+ if (qi < q.length) return null;
1872
+ score -= Math.max(0, t.length - q.length) / 4;
1873
+ return score;
1874
+ }
1875
+ function findSimilarTasks(query, tasks, limit = 5) {
1876
+ const q = normalizeForSearch(query);
1877
+ if (q.length === 0) return [];
1878
+ const bestByTitle = /* @__PURE__ */ new Map();
1879
+ for (const task of tasks) {
1880
+ const titleTrimmed = task.title.trim();
1881
+ if (titleTrimmed.length === 0) continue;
1882
+ const titleNormalized = normalizeForSearch(titleTrimmed);
1883
+ const score = fuzzyScore(q, titleNormalized);
1884
+ if (score === null) continue;
1885
+ const existing = bestByTitle.get(titleNormalized);
1886
+ if (!existing) {
1887
+ bestByTitle.set(titleNormalized, { task, score });
1888
+ continue;
1889
+ }
1890
+ const existingNewer = existing.task.updatedAt.getTime();
1891
+ const candidateNewer = task.updatedAt.getTime();
1892
+ if (candidateNewer > existingNewer) {
1893
+ bestByTitle.set(titleNormalized, { task, score });
1894
+ }
1895
+ }
1896
+ const sorted = Array.from(bestByTitle.values()).sort((a, b) => {
1897
+ if (b.score !== a.score) return b.score - a.score;
1898
+ return b.task.updatedAt.getTime() - a.task.updatedAt.getTime();
1899
+ });
1900
+ return sorted.slice(0, limit).map((s) => s.task);
1901
+ }
1902
+
1596
1903
  // ../domain/src/task-change-log.ts
1597
1904
  function describeStatusTransition(from, to) {
1598
1905
  if (from === "planned" && to === "running") return "\u958B\u59CB";
@@ -1827,6 +2134,80 @@ function diffTasksToNoteInputs(before, after, lookups) {
1827
2134
  }
1828
2135
  return result;
1829
2136
  }
2137
+ function titleOrPlaceholder(task) {
2138
+ return task.title || "\u7121\u984C\u306E\u30BF\u30B9\u30AF";
2139
+ }
2140
+ async function interruptTaskWithAudit(taskRepo, taskNoteRepo, params) {
2141
+ const result = await interruptTask(taskRepo, params);
2142
+ const notes = buildSplitNoteInputs(
2143
+ result.finishedOriginal,
2144
+ result.interruption,
2145
+ result.continuation
2146
+ );
2147
+ try {
2148
+ await Promise.all(notes.map((input2) => taskNoteRepo.create(input2)));
2149
+ } catch (err) {
2150
+ console.error("[audit] failed to record split notes", {
2151
+ sourceTaskId: result.finishedOriginal.id,
2152
+ err
2153
+ });
2154
+ }
2155
+ return result;
2156
+ }
2157
+ function buildSplitNoteInputs(source, interruption, continuation) {
2158
+ const sourceTitle = titleOrPlaceholder(source);
2159
+ const interruptionTitle = titleOrPlaceholder(interruption);
2160
+ return [
2161
+ {
2162
+ taskId: source.id,
2163
+ type: "split",
2164
+ body: `\u300C${interruptionTitle}\u300D\u306E\u5272\u308A\u8FBC\u307F\u3067\u5206\u5272\u3002\u6B8B\u4F5C\u696D\u306F\u5225\u30BF\u30B9\u30AF\u3067\u7D99\u7D9A\u3002`,
2165
+ metadata: {
2166
+ event: "split-source",
2167
+ interruptionTaskId: interruption.id,
2168
+ continuationTaskId: continuation.id
2169
+ }
2170
+ },
2171
+ {
2172
+ taskId: interruption.id,
2173
+ type: "split",
2174
+ body: `\u300C${sourceTitle}\u300D\u306E\u4F5C\u696D\u4E2D\u306B\u5272\u308A\u8FBC\u307F\u3068\u3057\u3066\u8D77\u7968\u3002`,
2175
+ metadata: {
2176
+ event: "split-interruption",
2177
+ sourceTaskId: source.id,
2178
+ continuationTaskId: continuation.id
2179
+ }
2180
+ },
2181
+ {
2182
+ taskId: continuation.id,
2183
+ type: "split",
2184
+ body: `\u300C${sourceTitle}\u300D\u306E\u6B8B\u4F5C\u696D\u3068\u3057\u3066\u7D99\u7D9A\u3002`,
2185
+ metadata: {
2186
+ event: "split-continuation",
2187
+ sourceTaskId: source.id,
2188
+ interruptionTaskId: interruption.id
2189
+ }
2190
+ }
2191
+ ];
2192
+ }
2193
+
2194
+ // ../domain/src/routine-expansion.ts
2195
+ function buildRoutineTaskPatch(routine) {
2196
+ return {
2197
+ title: routine.title,
2198
+ description: routine.description,
2199
+ estimatedSeconds: routine.estimatedSeconds,
2200
+ projectId: routine.projectId,
2201
+ contextId: routine.contextId,
2202
+ tagIds: routine.tagIds,
2203
+ scheduledStartTime: routine.defaultScheduledStartTime
2204
+ };
2205
+ }
2206
+ function pickPropagationTargets(routine, tasks, today) {
2207
+ return tasks.filter(
2208
+ (t) => t.routineId === routine.id && t.date >= today && t.status === "planned"
2209
+ );
2210
+ }
1830
2211
 
1831
2212
  // ../infra-supabase/src/audit-task-repository.ts
1832
2213
  var AuditingTaskRepository = class {
@@ -1894,17 +2275,23 @@ var AuditingTaskRepository = class {
1894
2275
  delete(id) {
1895
2276
  return this.inner.delete(id);
1896
2277
  }
2278
+ deleteWithRoutineSkip(id, today) {
2279
+ return this.inner.deleteWithRoutineSkip(id, today);
2280
+ }
1897
2281
  };
1898
2282
 
1899
2283
  // ../infra-supabase/src/composition-root.ts
1900
2284
  function buildRepositories(supabaseClient, cryptoSession, getLookups) {
1901
2285
  const taskNote = new SupabaseTaskNoteRepository(supabaseClient, cryptoSession);
1902
2286
  const rawTask = new SupabaseTaskRepository(supabaseClient, cryptoSession);
1903
- const task = getLookups ? new AuditingTaskRepository(rawTask, taskNote, getLookups) : rawTask;
2287
+ const auditingTask = getLookups ? new AuditingTaskRepository(rawTask, taskNote, getLookups) : void 0;
2288
+ const task = auditingTask ?? rawTask;
1904
2289
  return {
1905
2290
  task,
2291
+ auditingTask,
1906
2292
  taskNote,
1907
2293
  routine: new SupabaseRoutineRepository(supabaseClient, cryptoSession),
2294
+ routineExpansion: new SupabaseRoutineExpansionRepository(supabaseClient),
1908
2295
  project: new SupabaseProjectRepository(supabaseClient, cryptoSession),
1909
2296
  tag: new SupabaseTagRepository(supabaseClient, cryptoSession),
1910
2297
  context: new SupabaseContextRepository(supabaseClient, cryptoSession),
@@ -1912,43 +2299,245 @@ function buildRepositories(supabaseClient, cryptoSession, getLookups) {
1912
2299
  };
1913
2300
  }
1914
2301
 
1915
- // src/config.ts
1916
- var ENV_URL = "TASKCHUTE_SUPABASE_URL";
1917
- var ENV_KEY = "TASKCHUTE_SUPABASE_PUBLISHABLE_KEY";
1918
- function readSupabaseConfigFromEnv() {
1919
- const url = process.env.TASKCHUTE_SUPABASE_URL;
1920
- const publishableKey = process.env.TASKCHUTE_SUPABASE_PUBLISHABLE_KEY;
1921
- if (!url || !publishableKey) {
1922
- throw new Error(
1923
- `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`
2302
+ // ../node-shared/src/create-node-app-context.ts
2303
+ async function resolvePassphrase(store, promptPassphrase) {
2304
+ const envPw = process.env.TASKCHUTE_PASSPHRASE;
2305
+ if (envPw) return envPw;
2306
+ const stored = store.get();
2307
+ if (stored) return stored;
2308
+ if (!promptPassphrase) {
2309
+ throw new AppContextError(
2310
+ "No passphrase available. Set TASKCHUTE_PASSPHRASE env or run `taskchute unlock` from the CLI first."
1924
2311
  );
1925
2312
  }
1926
- return { url, publishableKey };
2313
+ return await promptPassphrase();
1927
2314
  }
1928
- var KEYRING_SERVICE2 = "taskchute-cli";
1929
- var KeyringSessionStore = class {
1930
- getItem(key) {
1931
- try {
1932
- const entry = new Entry(KEYRING_SERVICE2, key);
1933
- return entry.getPassword();
1934
- } catch {
1935
- return null;
1936
- }
1937
- }
1938
- setItem(key, value) {
1939
- const entry = new Entry(KEYRING_SERVICE2, key);
1940
- entry.setPassword(value);
2315
+ async function createNodeAppContext(opts = {}) {
2316
+ const passphraseStore = opts.passphraseStore ?? new KeyringPassphraseStore();
2317
+ const sessionStorage = opts.sessionStorage ?? new KeyringSessionStore();
2318
+ const supabase = createNodeSupabaseClient(readSupabaseConfigFromEnv(), {
2319
+ storage: sessionStorage
2320
+ });
2321
+ const {
2322
+ data: { user },
2323
+ error: authError
2324
+ } = await supabase.auth.getUser();
2325
+ if (authError || !user || !user.email) {
2326
+ throw new AppContextError(
2327
+ "Not logged in. Run `taskchute login` from the CLI first (session is shared via OS keyring)."
2328
+ );
1941
2329
  }
1942
- removeItem(key) {
1943
- try {
1944
- const entry = new Entry(KEYRING_SERVICE2, key);
1945
- entry.deletePassword();
1946
- } catch {
1947
- }
2330
+ const userKeysRepo = new SupabaseUserKeysRepository(supabase);
2331
+ const snapshot = await userKeysRepo.getOwn();
2332
+ if (!snapshot) {
2333
+ throw new AppContextError(
2334
+ "E2EE not set up. Complete setup from the web app first (Settings \u2192 \u30C7\u30FC\u30BF\u306E\u6697\u53F7\u5316)."
2335
+ );
1948
2336
  }
1949
- };
1950
-
1951
- // src/commands/login.ts
2337
+ const passphrase = await resolvePassphrase(passphraseStore, opts.promptPassphrase);
2338
+ let masterKey;
2339
+ try {
2340
+ masterKey = await unlockMasterKey(snapshot, { kind: "passphrase", passphrase });
2341
+ } catch (err) {
2342
+ if (err instanceof UnlockError) {
2343
+ throw new AppContextError(`Unlock failed: ${err.message}`);
2344
+ }
2345
+ throw err;
2346
+ }
2347
+ const cryptoSession = new CryptoSession();
2348
+ cryptoSession.setMasterKey(masterKey, snapshot.currentKeyId);
2349
+ const lookupsRef = {
2350
+ current: { projects: [], tags: [], contexts: [] }
2351
+ };
2352
+ const repos = buildRepositories(supabase, cryptoSession, () => lookupsRef.current);
2353
+ const refreshLookups = async () => {
2354
+ const [projects, tags, contexts] = await Promise.all([
2355
+ repos.project.findAll(),
2356
+ repos.tag.findAll(),
2357
+ repos.context.findAll()
2358
+ ]);
2359
+ lookupsRef.current = { projects, tags, contexts };
2360
+ };
2361
+ try {
2362
+ await refreshLookups();
2363
+ } catch (err) {
2364
+ console.error("[createNodeAppContext] initial refreshLookups failed:", err);
2365
+ }
2366
+ return {
2367
+ supabase,
2368
+ cryptoSession,
2369
+ repos,
2370
+ userId: user.id,
2371
+ email: user.email,
2372
+ refreshLookups
2373
+ };
2374
+ }
2375
+
2376
+ // src/crypto-context.ts
2377
+ var CliError = class extends Error {
2378
+ exitCode;
2379
+ constructor(message, exitCode = 1) {
2380
+ super(message);
2381
+ this.name = "CliError";
2382
+ this.exitCode = exitCode;
2383
+ }
2384
+ };
2385
+ async function wrapDomainErrors(op) {
2386
+ try {
2387
+ return await op();
2388
+ } catch (err) {
2389
+ if (err instanceof CliError) throw err;
2390
+ if (err instanceof Error) throw new CliError(err.message);
2391
+ throw err;
2392
+ }
2393
+ }
2394
+ async function createAppContext(opts = {}) {
2395
+ try {
2396
+ return await createNodeAppContext({
2397
+ passphraseStore: opts.passphraseStore,
2398
+ promptPassphrase: opts.allowPrompt === false ? void 0 : () => password({ message: "Passphrase:", mask: "*" })
2399
+ });
2400
+ } catch (err) {
2401
+ if (err instanceof AppContextError) {
2402
+ throw new CliError(err.message);
2403
+ }
2404
+ throw err;
2405
+ }
2406
+ }
2407
+
2408
+ // src/format/task-row.ts
2409
+ function shortenId(id, length = 8) {
2410
+ return id.slice(0, length);
2411
+ }
2412
+ function formatEstimated(seconds) {
2413
+ const totalMinutes = Math.round(seconds / 60);
2414
+ if (totalMinutes < 60) return `${totalMinutes}m`;
2415
+ const h = Math.floor(totalMinutes / 60);
2416
+ const m = totalMinutes % 60;
2417
+ return m === 0 ? `${h}h` : `${h}h${m}m`;
2418
+ }
2419
+ function formatTaskRow(task) {
2420
+ return {
2421
+ id: shortenId(task.id),
2422
+ status: task.status,
2423
+ scheduled: task.scheduledStartTime ?? "-",
2424
+ estimated: formatEstimated(task.estimatedSeconds),
2425
+ title: task.title
2426
+ };
2427
+ }
2428
+
2429
+ // src/resolve-resource-id.ts
2430
+ var MIN_PREFIX_LENGTH = 4;
2431
+ function pickLabel(r) {
2432
+ return "name" in r ? r.name : r.title;
2433
+ }
2434
+ function resolveResourceIdByPrefix(resources, prefix, resourceLabel) {
2435
+ if (prefix.length < MIN_PREFIX_LENGTH) {
2436
+ throw new CliError(
2437
+ `${resourceLabel} id prefix must be at least ${MIN_PREFIX_LENGTH} chars to avoid ambiguity.`
2438
+ );
2439
+ }
2440
+ const matches = resources.filter((r) => r.id.startsWith(prefix));
2441
+ if (matches.length === 0) {
2442
+ throw new CliError(`No ${resourceLabel} matched prefix "${prefix}".`);
2443
+ }
2444
+ if (matches.length > 1) {
2445
+ const candidates = matches.map((r) => ` ${shortenId(r.id)} ${pickLabel(r)}`).join("\n");
2446
+ throw new CliError(`Ambiguous ${resourceLabel} prefix "${prefix}":
2447
+ ${candidates}`);
2448
+ }
2449
+ return matches[0].id;
2450
+ }
2451
+
2452
+ // src/commands/contexts/archive.ts
2453
+ function contextsArchiveCommand() {
2454
+ return new Command("archive").description("\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u30A2\u30FC\u30AB\u30A4\u30D6\u3059\u308B (\u30BF\u30B9\u30AF\u306E context_id \u306F\u7DAD\u6301)").argument("<id-prefix>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
2455
+ const ctx = await createAppContext();
2456
+ const all = await ctx.repos.context.findAll();
2457
+ const id = resolveResourceIdByPrefix(all, prefix, "context");
2458
+ const archived = await ctx.repos.context.archive(id);
2459
+ console.log(`\u{1F4E6} Archived context: ${shortenId(archived.id)} ${archived.name}`);
2460
+ });
2461
+ }
2462
+ 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) => {
2464
+ const ctx = await createAppContext();
2465
+ const input2 = { name, status: "active" };
2466
+ if (opts.description !== void 0) input2.description = opts.description;
2467
+ if (opts.icon !== void 0) input2.icon = opts.icon;
2468
+ if (opts.color !== void 0) input2.color = opts.color;
2469
+ const context = await ctx.repos.context.create(input2);
2470
+ console.log(`\u2713 Created context: ${shortenId(context.id)} ${context.name}`);
2471
+ });
2472
+ }
2473
+ function contextsDeleteCommand() {
2474
+ return new Command("delete").alias("rm").description("\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u7269\u7406\u524A\u9664\u3059\u308B (\u30BF\u30B9\u30AF\u306E context_id \u306F NULL \u5316)").argument("<id-prefix>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
2475
+ const ctx = await createAppContext();
2476
+ const all = await ctx.repos.context.findAll();
2477
+ const id = resolveResourceIdByPrefix(all, prefix, "context");
2478
+ const target = all.find((c) => c.id === id);
2479
+ await ctx.repos.context.delete(id);
2480
+ console.log(`\u2717 Deleted context: ${shortenId(id)} ${target.name}`);
2481
+ });
2482
+ }
2483
+
2484
+ // src/format/context-row.ts
2485
+ function formatContextRow(context) {
2486
+ return {
2487
+ id: shortenId(context.id),
2488
+ name: context.name,
2489
+ status: context.status,
2490
+ icon: context.icon ?? "-",
2491
+ color: context.color ?? "-"
2492
+ };
2493
+ }
2494
+
2495
+ // src/commands/contexts/list.ts
2496
+ function contextsListCommand() {
2497
+ return new Command("list").alias("ls").description("\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u4E00\u89A7\u3092\u8868\u793A\u3059\u308B (\u65E2\u5B9A: active \u306E\u307F)").option("--archived", "archived \u306E\u307F\u8868\u793A").option("--all", "active + archived \u4E21\u65B9\u8868\u793A").action(async (opts) => {
2498
+ const ctx = await createAppContext();
2499
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : { status: "active" };
2500
+ const contexts = await ctx.repos.context.findAll(filter);
2501
+ if (contexts.length === 0) {
2502
+ console.log("No contexts.");
2503
+ return;
2504
+ }
2505
+ console.table(contexts.map(formatContextRow));
2506
+ });
2507
+ }
2508
+ 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) => {
2510
+ const ctx = await createAppContext();
2511
+ const all = await ctx.repos.context.findAll();
2512
+ const id = resolveResourceIdByPrefix(all, prefix, "context");
2513
+ const patch = {};
2514
+ if (opts.name !== void 0) patch.name = opts.name;
2515
+ if (opts.clearDescription) patch.description = void 0;
2516
+ else if (opts.description !== void 0) patch.description = opts.description;
2517
+ if (opts.clearIcon) patch.icon = void 0;
2518
+ else if (opts.icon !== void 0) patch.icon = opts.icon;
2519
+ if (opts.clearColor) patch.color = void 0;
2520
+ else if (opts.color !== void 0) patch.color = opts.color;
2521
+ if (Object.keys(patch).length === 0) {
2522
+ throw new CliError(
2523
+ "No field to update. Specify at least one of --name / --description / --icon / --color / --clear-*."
2524
+ );
2525
+ }
2526
+ const updated = await ctx.repos.context.update(id, patch);
2527
+ console.log(`\u270E Updated context: ${shortenId(updated.id)} ${updated.name}`);
2528
+ });
2529
+ }
2530
+
2531
+ // src/commands/contexts/index.ts
2532
+ function contextsCommand() {
2533
+ return new Command("contexts").description("\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E\u4E00\u89A7/\u8FFD\u52A0/\u64CD\u4F5C").addCommand(contextsListCommand()).addCommand(contextsCreateCommand()).addCommand(contextsUpdateCommand()).addCommand(contextsArchiveCommand()).addCommand(contextsDeleteCommand());
2534
+ }
2535
+ function lockCommand() {
2536
+ return new Command("lock").description("keyring \u304B\u3089 E2EE passphrase \u3092\u524A\u9664\u3059\u308B (session \u306F\u7DAD\u6301)").action(() => {
2537
+ new KeyringPassphraseStore().clear();
2538
+ console.log("locked");
2539
+ });
2540
+ }
1952
2541
  function loginCommand() {
1953
2542
  return new Command("login").description("Supabase \u306B email + password \u3067\u30ED\u30B0\u30A4\u30F3\u3057\u3066 session \u3092 keyring \u306B\u4FDD\u5B58\u3059\u308B").action(async () => {
1954
2543
  const config = readSupabaseConfigFromEnv();
@@ -1981,68 +2570,129 @@ function logoutCommand() {
1981
2570
  console.log("logged out \u{1F44B}");
1982
2571
  });
1983
2572
  }
1984
- var CliError = class extends Error {
1985
- exitCode;
1986
- constructor(message, exitCode = 1) {
1987
- super(message);
1988
- this.name = "CliError";
1989
- this.exitCode = exitCode;
1990
- }
1991
- };
1992
- async function resolvePassphrase(store, allowPrompt = true) {
1993
- const envPw = process.env.TASKCHUTE_PASSPHRASE;
1994
- if (envPw) return envPw;
1995
- const stored = store.get();
1996
- if (stored) return stored;
1997
- if (!allowPrompt) {
1998
- throw new CliError(
1999
- "No passphrase available. Set TASKCHUTE_PASSPHRASE env or run `taskchute unlock` first."
2000
- );
2001
- }
2002
- return await password({ message: "Passphrase:", mask: "*" });
2003
- }
2004
- async function createAppContext(opts = {}) {
2005
- const passphraseStore = opts.passphraseStore ?? new KeyringPassphraseStore();
2006
- const allowPrompt = opts.allowPrompt ?? true;
2007
- const supabase = createNodeSupabaseClient(readSupabaseConfigFromEnv(), {
2008
- storage: new KeyringSessionStore()
2573
+ function projectsArchiveCommand() {
2574
+ return new Command("archive").description("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u30A2\u30FC\u30AB\u30A4\u30D6\u3059\u308B (\u30BF\u30B9\u30AF\u306E project_id \u306F\u7DAD\u6301)").argument("<id-prefix>", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
2575
+ const ctx = await createAppContext();
2576
+ const all = await ctx.repos.project.findAll();
2577
+ const id = resolveResourceIdByPrefix(all, prefix, "project");
2578
+ const archived = await ctx.repos.project.archive(id);
2579
+ console.log(`\u{1F4E6} Archived project: ${shortenId(archived.id)} ${archived.name}`);
2009
2580
  });
2010
- const {
2011
- data: { user },
2012
- error: authError
2013
- } = await supabase.auth.getUser();
2014
- if (authError || !user || !user.email) {
2015
- throw new CliError("Not logged in. Run `taskchute login` first.");
2016
- }
2017
- const userKeysRepo = new SupabaseUserKeysRepository(supabase);
2018
- const snapshot = await userKeysRepo.getOwn();
2019
- if (!snapshot) {
2020
- throw new CliError(
2021
- "E2EE not set up. Complete setup from the web app first (Settings \u2192 \u30C7\u30FC\u30BF\u306E\u6697\u53F7\u5316)."
2022
- );
2023
- }
2024
- const passphrase = await resolvePassphrase(passphraseStore, allowPrompt);
2025
- let masterKey;
2026
- try {
2027
- masterKey = await unlockMasterKey(snapshot, { kind: "passphrase", passphrase });
2028
- } catch (err) {
2029
- if (err instanceof UnlockError) {
2030
- throw new CliError(`Unlock failed: ${err.message}`);
2581
+ }
2582
+ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
2583
+ function projectsCreateCommand() {
2584
+ return new Command("create").description("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u65B0\u898F\u4F5C\u6210\u3059\u308B").argument("<name>", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--color <color>", "UI \u8272 (\u4F8B: #ff6b6b)").option("--parent <id-prefix>", "\u89AA\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id (prefix)").option("--started-at <date>", "YYYY-MM-DD \u5F62\u5F0F\u306E\u958B\u59CB\u65E5").option("--target-end <date>", "YYYY-MM-DD \u5F62\u5F0F\u306E\u76EE\u6A19\u7D42\u4E86\u65E5").action(async (name, opts) => {
2585
+ const ctx = await createAppContext();
2586
+ let parentProjectId;
2587
+ if (opts.parent !== void 0) {
2588
+ const all = await ctx.repos.project.findAll();
2589
+ parentProjectId = resolveResourceIdByPrefix(all, opts.parent, "project");
2031
2590
  }
2032
- throw err;
2033
- }
2034
- const cryptoSession = new CryptoSession();
2035
- cryptoSession.setMasterKey(masterKey, snapshot.currentKeyId);
2036
- const repos = buildRepositories(supabase, cryptoSession);
2591
+ if (opts.startedAt !== void 0 && !DATE_RE.test(opts.startedAt)) {
2592
+ throw new CliError(`--started-at must be YYYY-MM-DD (got "${opts.startedAt}").`);
2593
+ }
2594
+ if (opts.targetEnd !== void 0 && !DATE_RE.test(opts.targetEnd)) {
2595
+ throw new CliError(`--target-end must be YYYY-MM-DD (got "${opts.targetEnd}").`);
2596
+ }
2597
+ const input2 = { name, status: "active" };
2598
+ if (opts.description !== void 0) input2.description = opts.description;
2599
+ if (opts.color !== void 0) input2.color = opts.color;
2600
+ if (parentProjectId !== void 0) input2.parentProjectId = parentProjectId;
2601
+ 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`);
2603
+ const project = await ctx.repos.project.create(input2);
2604
+ console.log(`\u2713 Created project: ${shortenId(project.id)} ${project.name}`);
2605
+ });
2606
+ }
2607
+ function projectsDeleteCommand() {
2608
+ return new Command("delete").alias("rm").description("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u7269\u7406\u524A\u9664\u3059\u308B (\u30BF\u30B9\u30AF\u306E project_id \u306F NULL \u5316)").argument("<id-prefix>", "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
2609
+ const ctx = await createAppContext();
2610
+ const all = await ctx.repos.project.findAll();
2611
+ const id = resolveResourceIdByPrefix(all, prefix, "project");
2612
+ const target = all.find((p) => p.id === id);
2613
+ await ctx.repos.project.delete(id);
2614
+ console.log(`\u2717 Deleted project: ${shortenId(id)} ${target.name}`);
2615
+ });
2616
+ }
2617
+
2618
+ // src/format/project-row.ts
2619
+ function formatProjectRow(project) {
2037
2620
  return {
2038
- supabase,
2039
- cryptoSession,
2040
- repos,
2041
- userId: user.id,
2042
- email: user.email
2621
+ id: shortenId(project.id),
2622
+ name: project.name,
2623
+ status: project.status,
2624
+ color: project.color ?? "-",
2625
+ parent: project.parentProjectId ? shortenId(project.parentProjectId) : "-"
2043
2626
  };
2044
2627
  }
2045
2628
 
2629
+ // src/commands/projects/list.ts
2630
+ function projectsListCommand() {
2631
+ return new Command("list").alias("ls").description("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u4E00\u89A7\u3092\u8868\u793A\u3059\u308B (\u65E2\u5B9A: active \u306E\u307F)").option("--archived", "archived \u306E\u307F\u8868\u793A").option("--all", "active + archived \u4E21\u65B9\u8868\u793A").action(async (opts) => {
2632
+ const ctx = await createAppContext();
2633
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : { status: "active" };
2634
+ const projects = await ctx.repos.project.findAll(filter);
2635
+ if (projects.length === 0) {
2636
+ console.log("No projects.");
2637
+ return;
2638
+ }
2639
+ console.table(projects.map(formatProjectRow));
2640
+ });
2641
+ }
2642
+ var DATE_RE2 = /^\d{4}-\d{2}-\d{2}$/;
2643
+ function projectsUpdateCommand() {
2644
+ return new Command("update").description("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u90E8\u5206\u66F4\u65B0\u3059\u308B").argument("<id-prefix>", "\u30D7\u30ED\u30B8\u30A7\u30AF\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("--color <color>", "UI \u8272").option("--clear-color", "\u8272\u3092\u5916\u3059").option("--parent <id-prefix>", "\u89AA\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 id (prefix)").option("--clear-parent", "\u89AA\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u7D10\u4ED8\u3051\u3092\u5916\u3059").option("--started-at <date>", "YYYY-MM-DD").option("--clear-started-at", "\u958B\u59CB\u65E5\u3092\u5916\u3059").option("--target-end <date>", "YYYY-MM-DD").option("--clear-target-end", "\u76EE\u6A19\u7D42\u4E86\u65E5\u3092\u5916\u3059").action(async (prefix, opts) => {
2645
+ const ctx = await createAppContext();
2646
+ const all = await ctx.repos.project.findAll();
2647
+ const id = resolveResourceIdByPrefix(all, prefix, "project");
2648
+ const patch = {};
2649
+ if (opts.name !== void 0) patch.name = opts.name;
2650
+ if (opts.clearDescription) patch.description = void 0;
2651
+ else if (opts.description !== void 0) patch.description = opts.description;
2652
+ if (opts.clearColor) patch.color = void 0;
2653
+ else if (opts.color !== void 0) patch.color = opts.color;
2654
+ if (opts.clearParent) patch.parentProjectId = void 0;
2655
+ else if (opts.parent !== void 0) {
2656
+ const parentId = resolveResourceIdByPrefix(all, opts.parent, "project");
2657
+ if (parentId === id) throw new CliError("A project cannot be its own parent.");
2658
+ patch.parentProjectId = parentId;
2659
+ }
2660
+ if (opts.clearStartedAt) patch.startedAt = void 0;
2661
+ else if (opts.startedAt !== void 0) {
2662
+ if (!DATE_RE2.test(opts.startedAt))
2663
+ throw new CliError(`--started-at must be YYYY-MM-DD (got "${opts.startedAt}").`);
2664
+ patch.startedAt = /* @__PURE__ */ new Date(`${opts.startedAt}T00:00:00Z`);
2665
+ }
2666
+ if (opts.clearTargetEnd) patch.targetEndDate = void 0;
2667
+ else if (opts.targetEnd !== void 0) {
2668
+ if (!DATE_RE2.test(opts.targetEnd))
2669
+ throw new CliError(`--target-end must be YYYY-MM-DD (got "${opts.targetEnd}").`);
2670
+ patch.targetEndDate = /* @__PURE__ */ new Date(`${opts.targetEnd}T00:00:00Z`);
2671
+ }
2672
+ if (Object.keys(patch).length === 0) {
2673
+ throw new CliError(
2674
+ "No field to update. Specify at least one of --name / --description / --color / --parent / --started-at / --target-end / --clear-*."
2675
+ );
2676
+ }
2677
+ const updated = await ctx.repos.project.update(id, patch);
2678
+ console.log(`\u270E Updated project: ${shortenId(updated.id)} ${updated.name}`);
2679
+ });
2680
+ }
2681
+
2682
+ // src/commands/projects/index.ts
2683
+ function projectsCommand() {
2684
+ return new Command("projects").description("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u4E00\u89A7/\u8FFD\u52A0/\u64CD\u4F5C").addCommand(projectsListCommand()).addCommand(projectsCreateCommand()).addCommand(projectsUpdateCommand()).addCommand(projectsArchiveCommand()).addCommand(projectsDeleteCommand());
2685
+ }
2686
+ function routinesArchiveCommand() {
2687
+ return new Command("archive").description("\u30EB\u30FC\u30C1\u30F3\u3092\u30A2\u30FC\u30AB\u30A4\u30D6\u3059\u308B (\u65E2\u5B58 task \u306E routine_id \u306F\u7DAD\u6301)").argument("<id-prefix>", "\u30EB\u30FC\u30C1\u30F3 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
2688
+ const ctx = await createAppContext();
2689
+ const all = await ctx.repos.routine.findAll();
2690
+ const id = resolveResourceIdByPrefix(all, prefix, "routine");
2691
+ const archived = await ctx.repos.routine.archive(id);
2692
+ console.log(`\u{1F4E6} Archived routine: ${shortenId(archived.id)} ${archived.title}`);
2693
+ });
2694
+ }
2695
+
2046
2696
  // src/format/parse-estimated.ts
2047
2697
  var HOURS_HEAD = /^(\d{1,4})h/;
2048
2698
  var MINUTES_TAIL = /^(\d{1,4})m$/;
@@ -2070,33 +2720,360 @@ function parseEstimated(input2) {
2070
2720
  return (hours * 60 + minutes) * 60;
2071
2721
  }
2072
2722
 
2073
- // src/format/task-row.ts
2074
- function shortenId(id, length = 8) {
2075
- return id.slice(0, length);
2723
+ // src/format/parse-frequency.ts
2724
+ function parseFrequencySpec(spec) {
2725
+ 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
+ );
2731
+ }
2732
+ if (typeKey === "weekly") {
2733
+ const weekdays = parseNumberList(rest, 0, 6);
2734
+ if (weekdays.length === 0) {
2735
+ throw new CliError("weekly frequency must have at least 1 weekday.");
2736
+ }
2737
+ return { type: "weekly", weekdays };
2738
+ }
2739
+ if (typeKey === "monthly") {
2740
+ const days = parseNumberList(rest, 1, 31);
2741
+ if (days.length === 0) {
2742
+ throw new CliError("monthly frequency must have at least 1 day.");
2743
+ }
2744
+ return { type: "monthly", days };
2745
+ }
2746
+ if (typeKey === "custom") {
2747
+ if (rest.length === 0) throw new CliError("custom frequency requires a cron string.");
2748
+ return { type: "custom", cron: rest };
2749
+ }
2750
+ throw new CliError(
2751
+ `Unknown frequency type "${typeKey}". Use one of: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING`
2752
+ );
2076
2753
  }
2077
- function formatEstimated(seconds) {
2078
- const totalMinutes = Math.round(seconds / 60);
2079
- if (totalMinutes < 60) return `${totalMinutes}m`;
2080
- const h = Math.floor(totalMinutes / 60);
2081
- const m = totalMinutes % 60;
2082
- return m === 0 ? `${h}h` : `${h}h${m}m`;
2754
+ function parseNumberList(csv, min, max) {
2755
+ const parts = csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
2756
+ return parts.map((s) => {
2757
+ const n = Number(s);
2758
+ if (!Number.isInteger(n) || n < min || n > max) {
2759
+ throw new CliError(`Invalid number "${s}" (must be integer in [${min}, ${max}]).`);
2760
+ }
2761
+ return n;
2762
+ });
2083
2763
  }
2084
- function formatTaskRow(task) {
2764
+ function parseWeekdaysList(csv) {
2765
+ const result = parseNumberList(csv, 0, 6);
2766
+ if (result.length === 0) throw new CliError("--weekdays must have at least 1 day.");
2767
+ return result;
2768
+ }
2769
+
2770
+ // src/commands/routines/create.ts
2771
+ var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
2772
+ var DATE_RE3 = /^\d{4}-\d{2}-\d{2}$/;
2773
+ function routinesCreateCommand() {
2774
+ 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
+ "--frequency <spec>",
2776
+ "\u983B\u5EA6: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING"
2777
+ ).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
+ const ctx = await createAppContext();
2779
+ const estimatedSeconds = parseEstimated(opts.estimated);
2780
+ const frequency = parseFrequencySpec(opts.frequency);
2781
+ if (opts.scheduled !== void 0 && !HHMM.test(opts.scheduled)) {
2782
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
2783
+ }
2784
+ if (opts.startDate !== void 0 && !DATE_RE3.test(opts.startDate)) {
2785
+ throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
2786
+ }
2787
+ if (opts.endDate !== void 0 && !DATE_RE3.test(opts.endDate)) {
2788
+ throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
2789
+ }
2790
+ let projectId;
2791
+ if (opts.project !== void 0) {
2792
+ const all = await ctx.repos.project.findAll();
2793
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
2794
+ }
2795
+ let contextId;
2796
+ if (opts.context !== void 0) {
2797
+ const all = await ctx.repos.context.findAll();
2798
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
2799
+ }
2800
+ const tagIds = [];
2801
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
2802
+ const allTags = await ctx.repos.tag.findAll();
2803
+ for (const prefix of opts.tag) {
2804
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
2805
+ }
2806
+ }
2807
+ const input2 = {
2808
+ title,
2809
+ estimatedSeconds,
2810
+ tagIds,
2811
+ frequency,
2812
+ autoExpand: opts.noAutoExpand !== true,
2813
+ status: "active"
2814
+ };
2815
+ if (opts.description !== void 0) input2.description = opts.description;
2816
+ if (projectId !== void 0) input2.projectId = projectId;
2817
+ if (contextId !== void 0) input2.contextId = contextId;
2818
+ if (opts.scheduled !== void 0) input2.defaultScheduledStartTime = opts.scheduled;
2819
+ if (opts.startDate !== void 0) input2.startDate = opts.startDate;
2820
+ if (opts.endDate !== void 0) input2.endDate = opts.endDate;
2821
+ const routine = await ctx.repos.routine.create(input2);
2822
+ console.log(`\u2713 Created routine: ${shortenId(routine.id)} ${routine.title}`);
2823
+ });
2824
+ }
2825
+ function collectTag(value, prev) {
2826
+ return [...prev, value];
2827
+ }
2828
+ function routinesDeleteCommand() {
2829
+ return new Command("delete").alias("rm").description("\u30EB\u30FC\u30C1\u30F3\u3092\u7269\u7406\u524A\u9664\u3059\u308B (task \u306E routine_id \u306F NULL \u5316)").argument("<id-prefix>", "\u30EB\u30FC\u30C1\u30F3 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
2830
+ const ctx = await createAppContext();
2831
+ const all = await ctx.repos.routine.findAll();
2832
+ const id = resolveResourceIdByPrefix(all, prefix, "routine");
2833
+ const target = all.find((r) => r.id === id);
2834
+ await ctx.repos.routine.delete(id);
2835
+ console.log(`\u2717 Deleted routine: ${shortenId(id)} ${target.title}`);
2836
+ });
2837
+ }
2838
+
2839
+ // src/format/routine-row.ts
2840
+ function formatFrequency(freq) {
2841
+ switch (freq.type) {
2842
+ case "daily":
2843
+ return "daily";
2844
+ case "weekly":
2845
+ return `weekly:${freq.weekdays.join(",")}`;
2846
+ case "monthly":
2847
+ return `monthly:${freq.days.join(",")}`;
2848
+ case "custom":
2849
+ return `custom:${freq.cron}`;
2850
+ }
2851
+ }
2852
+ function formatRoutineRow(routine) {
2085
2853
  return {
2086
- id: shortenId(task.id),
2087
- status: task.status,
2088
- scheduled: task.scheduledStartTime ?? "-",
2089
- estimated: formatEstimated(task.estimatedSeconds),
2090
- title: task.title
2854
+ id: shortenId(routine.id),
2855
+ title: routine.title,
2856
+ status: routine.status,
2857
+ frequency: formatFrequency(routine.frequency),
2858
+ estimated: formatEstimated(routine.estimatedSeconds),
2859
+ auto: routine.autoExpand ? "auto" : "-"
2860
+ };
2861
+ }
2862
+
2863
+ // src/commands/routines/list.ts
2864
+ function routinesListCommand() {
2865
+ return new Command("list").alias("ls").description("\u30EB\u30FC\u30C1\u30F3\u4E00\u89A7\u3092\u8868\u793A\u3059\u308B (\u65E2\u5B9A: active \u306E\u307F)").option("--paused", "paused \u306E\u307F\u8868\u793A (\u4E00\u6642\u505C\u6B62\u4E2D)").option("--archived", "archived \u306E\u307F\u8868\u793A").option("--all", "\u5168 status \u8868\u793A").action(async (opts) => {
2866
+ const ctx = await createAppContext();
2867
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : opts.paused ? { status: "paused" } : { status: "active" };
2868
+ const routines = await ctx.repos.routine.findAll(filter);
2869
+ if (routines.length === 0) {
2870
+ console.log("No routines.");
2871
+ return;
2872
+ }
2873
+ console.table(routines.map(formatRoutineRow));
2874
+ });
2875
+ }
2876
+ var HHMM2 = /^([01]\d|2[0-3]):[0-5]\d$/;
2877
+ var DATE_RE4 = /^\d{4}-\d{2}-\d{2}$/;
2878
+ var ROUTINE_STATUSES = ["active", "paused", "archived"];
2879
+ function isRoutineStatus(value) {
2880
+ return ROUTINE_STATUSES.includes(value);
2881
+ }
2882
+ 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) => {
2884
+ const ctx = await createAppContext();
2885
+ const all = await ctx.repos.routine.findAll();
2886
+ const id = resolveResourceIdByPrefix(all, prefix, "routine");
2887
+ if (opts.scheduled !== void 0 && !HHMM2.test(opts.scheduled)) {
2888
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
2889
+ }
2890
+ if (opts.startDate !== void 0 && !DATE_RE4.test(opts.startDate)) {
2891
+ throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
2892
+ }
2893
+ if (opts.endDate !== void 0 && !DATE_RE4.test(opts.endDate)) {
2894
+ throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
2895
+ }
2896
+ if (opts.status !== void 0 && !isRoutineStatus(opts.status)) {
2897
+ throw new CliError(
2898
+ `--status must be one of ${ROUTINE_STATUSES.join(" / ")} (got "${opts.status}").`
2899
+ );
2900
+ }
2901
+ const patch = {};
2902
+ if (opts.title !== void 0) patch.title = opts.title;
2903
+ if (opts.clearDescription) patch.description = void 0;
2904
+ else if (opts.description !== void 0) patch.description = opts.description;
2905
+ if (opts.estimated !== void 0) patch.estimatedSeconds = parseEstimated(opts.estimated);
2906
+ if (opts.frequency !== void 0) patch.frequency = parseFrequencySpec(opts.frequency);
2907
+ if (opts.clearProject) patch.projectId = void 0;
2908
+ else if (opts.project !== void 0) {
2909
+ const projects = await ctx.repos.project.findAll();
2910
+ patch.projectId = resolveResourceIdByPrefix(projects, opts.project, "project");
2911
+ }
2912
+ if (opts.clearContext) patch.contextId = void 0;
2913
+ else if (opts.context !== void 0) {
2914
+ const contexts = await ctx.repos.context.findAll();
2915
+ patch.contextId = resolveResourceIdByPrefix(contexts, opts.context, "context");
2916
+ }
2917
+ if (opts.noTags) patch.tagIds = [];
2918
+ else if (opts.tag !== void 0 && opts.tag.length > 0) {
2919
+ const tags = await ctx.repos.tag.findAll();
2920
+ const resolved = [];
2921
+ for (const p of opts.tag) {
2922
+ resolved.push(resolveResourceIdByPrefix(tags, p, "tag"));
2923
+ }
2924
+ patch.tagIds = resolved;
2925
+ }
2926
+ if (opts.clearScheduled) patch.defaultScheduledStartTime = void 0;
2927
+ else if (opts.scheduled !== void 0) patch.defaultScheduledStartTime = opts.scheduled;
2928
+ if (opts.autoExpand !== void 0) patch.autoExpand = opts.autoExpand;
2929
+ if (opts.clearStartDate) patch.startDate = void 0;
2930
+ else if (opts.startDate !== void 0) patch.startDate = opts.startDate;
2931
+ if (opts.clearEndDate) patch.endDate = void 0;
2932
+ else if (opts.endDate !== void 0) patch.endDate = opts.endDate;
2933
+ if (opts.status !== void 0 && isRoutineStatus(opts.status)) {
2934
+ patch.status = opts.status;
2935
+ }
2936
+ if (Object.keys(patch).length === 0) {
2937
+ throw new CliError(
2938
+ "No field to update. Specify at least one of --title / --estimated / --frequency / --project / --context / --tag / --scheduled / --auto-expand / --start-date / --end-date / --status / --clear-*."
2939
+ );
2940
+ }
2941
+ const updated = await ctx.repos.routine.update(id, patch);
2942
+ const today = todayDateString();
2943
+ const futureTasks = await ctx.repos.task.findByRoutineIdFromDate(id, today);
2944
+ const targets = pickPropagationTargets(updated, futureTasks, today);
2945
+ let propagated = 0;
2946
+ if (targets.length > 0) {
2947
+ const { auditingTask } = ctx.repos;
2948
+ if (!auditingTask) {
2949
+ throw new CliError(
2950
+ "auditingTask is not configured. Routine propagation requires audit lookups (this should not happen with createNodeAppContext)."
2951
+ );
2952
+ }
2953
+ const taskPatch = buildRoutineTaskPatch(updated);
2954
+ await Promise.all(
2955
+ targets.map(
2956
+ (task) => auditingTask.updateWithCause(task.id, taskPatch, "routine-propagation")
2957
+ )
2958
+ );
2959
+ propagated = targets.length;
2960
+ }
2961
+ const propagatedNote = propagated > 0 ? ` (propagated to ${propagated} future task(s))` : "";
2962
+ console.log(`\u270E Updated routine: ${shortenId(updated.id)} ${updated.title}${propagatedNote}`);
2963
+ });
2964
+ }
2965
+ function collectTag2(value, prev) {
2966
+ return [...prev, value];
2967
+ }
2968
+
2969
+ // src/commands/routines/index.ts
2970
+ function routinesCommand() {
2971
+ return new Command("routines").description("\u30EB\u30FC\u30C1\u30F3\u306E\u4E00\u89A7/\u8FFD\u52A0/\u64CD\u4F5C").addCommand(routinesListCommand()).addCommand(routinesCreateCommand()).addCommand(routinesUpdateCommand()).addCommand(routinesArchiveCommand()).addCommand(routinesDeleteCommand());
2972
+ }
2973
+ function tagsArchiveCommand() {
2974
+ return new Command("archive").description("\u30BF\u30B0\u3092\u30A2\u30FC\u30AB\u30A4\u30D6\u3059\u308B (\u30BF\u30B9\u30AF\u306E tag_ids \u914D\u5217\u306F\u7DAD\u6301)").argument("<id-prefix>", "\u30BF\u30B0 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
2975
+ const ctx = await createAppContext();
2976
+ const all = await ctx.repos.tag.findAll();
2977
+ const id = resolveResourceIdByPrefix(all, prefix, "tag");
2978
+ const archived = await ctx.repos.tag.archive(id);
2979
+ console.log(`\u{1F4E6} Archived tag: ${shortenId(archived.id)} ${archived.name}`);
2980
+ });
2981
+ }
2982
+ function tagsCreateCommand() {
2983
+ return new Command("create").description("\u30BF\u30B0\u3092\u65B0\u898F\u4F5C\u6210\u3059\u308B").argument("<name>", "\u30BF\u30B0\u540D").option("--description <text>", "\u8A73\u7D30\u8AAC\u660E").option("--color <color>", "UI \u8272 (\u4F8B: #ff6b6b)").action(async (name, opts) => {
2984
+ const ctx = await createAppContext();
2985
+ const input2 = { name, status: "active" };
2986
+ if (opts.description !== void 0) input2.description = opts.description;
2987
+ if (opts.color !== void 0) input2.color = opts.color;
2988
+ const tag = await ctx.repos.tag.create(input2);
2989
+ console.log(`\u2713 Created tag: ${shortenId(tag.id)} ${tag.name}`);
2990
+ });
2991
+ }
2992
+ function tagsDeleteCommand() {
2993
+ return new Command("delete").alias("rm").description("\u30BF\u30B0\u3092\u7269\u7406\u524A\u9664\u3059\u308B (\u30BF\u30B9\u30AF\u306E tag_ids \u914D\u5217\u306B\u5B64\u5150 id \u304C\u6B8B\u308B\u306E\u3067\u6CE8\u610F)").argument("<id-prefix>", "\u30BF\u30B0 id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
2994
+ const ctx = await createAppContext();
2995
+ const all = await ctx.repos.tag.findAll();
2996
+ const id = resolveResourceIdByPrefix(all, prefix, "tag");
2997
+ const target = all.find((t) => t.id === id);
2998
+ await ctx.repos.tag.delete(id);
2999
+ console.log(`\u2717 Deleted tag: ${shortenId(id)} ${target.name}`);
3000
+ });
3001
+ }
3002
+
3003
+ // src/format/tag-row.ts
3004
+ function formatTagRow(tag) {
3005
+ return {
3006
+ id: shortenId(tag.id),
3007
+ name: tag.name,
3008
+ status: tag.status,
3009
+ color: tag.color ?? "-"
2091
3010
  };
2092
3011
  }
2093
3012
 
2094
- // src/commands/tasks/add.ts
3013
+ // src/commands/tags/list.ts
3014
+ function tagsListCommand() {
3015
+ return new Command("list").alias("ls").description("\u30BF\u30B0\u4E00\u89A7\u3092\u8868\u793A\u3059\u308B (\u65E2\u5B9A: active \u306E\u307F)").option("--archived", "archived \u306E\u307F\u8868\u793A").option("--all", "active + archived \u4E21\u65B9\u8868\u793A").action(async (opts) => {
3016
+ const ctx = await createAppContext();
3017
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : { status: "active" };
3018
+ const tags = await ctx.repos.tag.findAll(filter);
3019
+ if (tags.length === 0) {
3020
+ console.log("No tags.");
3021
+ return;
3022
+ }
3023
+ console.table(tags.map(formatTagRow));
3024
+ });
3025
+ }
3026
+ function tagsUpdateCommand() {
3027
+ return new Command("update").description("\u30BF\u30B0\u3092\u90E8\u5206\u66F4\u65B0\u3059\u308B").argument("<id-prefix>", "\u30BF\u30B0 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("--color <color>", "UI \u8272").option("--clear-color", "\u8272\u3092\u5916\u3059").action(async (prefix, opts) => {
3028
+ const ctx = await createAppContext();
3029
+ const all = await ctx.repos.tag.findAll();
3030
+ const id = resolveResourceIdByPrefix(all, prefix, "tag");
3031
+ const patch = {};
3032
+ if (opts.name !== void 0) patch.name = opts.name;
3033
+ if (opts.clearDescription) patch.description = void 0;
3034
+ else if (opts.description !== void 0) patch.description = opts.description;
3035
+ if (opts.clearColor) patch.color = void 0;
3036
+ else if (opts.color !== void 0) patch.color = opts.color;
3037
+ if (Object.keys(patch).length === 0) {
3038
+ throw new CliError(
3039
+ "No field to update. Specify at least one of --name / --description / --color / --clear-*."
3040
+ );
3041
+ }
3042
+ const updated = await ctx.repos.tag.update(id, patch);
3043
+ console.log(`\u270E Updated tag: ${shortenId(updated.id)} ${updated.name}`);
3044
+ });
3045
+ }
3046
+
3047
+ // src/commands/tags/index.ts
3048
+ function tagsCommand() {
3049
+ return new Command("tags").description("\u30BF\u30B0\u306E\u4E00\u89A7/\u8FFD\u52A0/\u64CD\u4F5C").addCommand(tagsListCommand()).addCommand(tagsCreateCommand()).addCommand(tagsUpdateCommand()).addCommand(tagsArchiveCommand()).addCommand(tagsDeleteCommand());
3050
+ }
3051
+ var TIME_RE2 = /^([01]\d|2[0-3]):[0-5]\d$/;
2095
3052
  function tasksAddCommand() {
2096
- return new Command("add").description("\u30BF\u30B9\u30AF\u3092\u8FFD\u52A0\u3059\u308B").argument("<title>", "\u30BF\u30B9\u30AF\u306E\u30BF\u30A4\u30C8\u30EB").option("-d, --date <date>", "YYYY-MM-DD \u5F62\u5F0F\u306E\u65E5\u4ED8 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").option("-e, --estimated <duration>", '\u898B\u7A4D\u3082\u308A\u6642\u9593 ("30m" / "1h" / "1h30m") (\u30C7\u30D5\u30A9\u30EB\u30C8: 30m)').option("--description <text>", "\u30BF\u30B9\u30AF\u306E\u8A73\u7D30\u8AAC\u660E").action(async (title, opts) => {
3053
+ return new Command("add").description("\u30BF\u30B9\u30AF\u3092\u8FFD\u52A0\u3059\u308B").argument("<title>", "\u30BF\u30B9\u30AF\u306E\u30BF\u30A4\u30C8\u30EB").option("-d, --date <date>", "YYYY-MM-DD \u5F62\u5F0F\u306E\u65E5\u4ED8 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").option("-e, --estimated <duration>", '\u898B\u7A4D\u3082\u308A\u6642\u9593 ("30m" / "1h" / "1h30m") (\u30C7\u30D5\u30A9\u30EB\u30C8: 30m)').option("--description <text>", "\u30BF\u30B9\u30AF\u306E\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", collectTag3, []).option("--scheduled <HH:MM>", "\u4E88\u5B9A\u958B\u59CB\u6642\u523B").option("--notify", "\u4E88\u5B9A\u6642\u523B\u901A\u77E5\u3092 ON").option("--background", "\u30D0\u30C3\u30AF\u30B0\u30E9\u30A6\u30F3\u30C9\u30BF\u30B9\u30AF\u3068\u3057\u3066\u8FFD\u52A0").action(async (title, opts) => {
2097
3054
  const ctx = await createAppContext();
2098
3055
  const date = opts.date ?? todayDateString();
2099
3056
  const estimatedSeconds = opts.estimated ? parseEstimated(opts.estimated) : 30 * 60;
3057
+ let projectId;
3058
+ if (opts.project !== void 0) {
3059
+ const all = await ctx.repos.project.findAll();
3060
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3061
+ }
3062
+ let contextId;
3063
+ if (opts.context !== void 0) {
3064
+ const all = await ctx.repos.context.findAll();
3065
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3066
+ }
3067
+ const tagIds = [];
3068
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
3069
+ const allTags = await ctx.repos.tag.findAll();
3070
+ for (const prefix of opts.tag) {
3071
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3072
+ }
3073
+ }
3074
+ if (opts.scheduled !== void 0 && !TIME_RE2.test(opts.scheduled)) {
3075
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
3076
+ }
2100
3077
  const existing = await ctx.repos.task.findByDate(date);
2101
3078
  const orderIndex = computeNextOrderIndex(existing);
2102
3079
  const task = await ctx.repos.task.create({
@@ -2107,18 +3084,26 @@ function tasksAddCommand() {
2107
3084
  sessions: [],
2108
3085
  status: "planned",
2109
3086
  orderIndex,
2110
- tagIds: []
3087
+ tagIds,
3088
+ projectId,
3089
+ contextId,
3090
+ scheduledStartTime: opts.scheduled,
3091
+ notifyAtScheduledStart: opts.notify,
3092
+ isBackground: opts.background
2111
3093
  });
2112
3094
  console.log(`\u2713 Created: ${shortenId(task.id)} ${task.title}`);
2113
3095
  });
2114
3096
  }
3097
+ function collectTag3(value, prev) {
3098
+ return [...prev, value];
3099
+ }
2115
3100
 
2116
3101
  // src/resolve-task-id.ts
2117
- var MIN_PREFIX_LENGTH = 4;
3102
+ var MIN_PREFIX_LENGTH2 = 4;
2118
3103
  function resolveTaskIdByPrefix(tasks, prefix) {
2119
- if (prefix.length < MIN_PREFIX_LENGTH) {
3104
+ if (prefix.length < MIN_PREFIX_LENGTH2) {
2120
3105
  throw new CliError(
2121
- `Task id prefix must be at least ${MIN_PREFIX_LENGTH} chars to avoid ambiguity.`
3106
+ `Task id prefix must be at least ${MIN_PREFIX_LENGTH2} chars to avoid ambiguity.`
2122
3107
  );
2123
3108
  }
2124
3109
  const matches = tasks.filter((t) => t.id.startsWith(prefix));
@@ -2133,7 +3118,22 @@ ${candidates}`);
2133
3118
  return matches[0].id;
2134
3119
  }
2135
3120
 
2136
- // src/commands/tasks/done.ts
3121
+ // src/commands/tasks/delete.ts
3122
+ function tasksDeleteCommand() {
3123
+ return new Command("delete").alias("rm").description("\u30BF\u30B9\u30AF\u3092\u524A\u9664\u3059\u308B (Routine \u7531\u6765\u306F\u4ECA\u65E5\u4EE5\u964D\u306A\u3089\u81EA\u52D5 skip \u8A18\u9332)").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("--hard", "\u7269\u7406\u524A\u9664\u306E\u307F (routine_expansions \u306B skip \u8A18\u9332\u3057\u306A\u3044)").action(async (prefix, opts) => {
3124
+ const ctx = await createAppContext();
3125
+ const date = opts.date ?? todayDateString();
3126
+ const tasks = await ctx.repos.task.findByDate(date);
3127
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3128
+ const target = tasks.find((t) => t.id === taskId);
3129
+ if (opts.hard) {
3130
+ await ctx.repos.task.delete(taskId);
3131
+ } else {
3132
+ await ctx.repos.task.deleteWithRoutineSkip(taskId, todayDateString());
3133
+ }
3134
+ console.log(`\u2717 Deleted: ${shortenId(taskId)} ${target.title}`);
3135
+ });
3136
+ }
2137
3137
  function tasksDoneCommand() {
2138
3138
  return new Command("done").description("\u30BF\u30B9\u30AF\u3092\u5B8C\u4E86\u3059\u308B (status \u2192 completed)").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) => {
2139
3139
  const ctx = await createAppContext();
@@ -2144,6 +3144,52 @@ function tasksDoneCommand() {
2144
3144
  console.log(`\u2713 Done: ${shortenId(updated.id)} ${updated.title}`);
2145
3145
  });
2146
3146
  }
3147
+ function tasksInterruptCommand() {
3148
+ return new Command("interrupt").description("\u8D70\u884C\u4E2D\u30BF\u30B9\u30AF\u3092\u300C\u5272\u308A\u8FBC\u307F\u300D\u3067\u5206\u5272\u3059\u308B (3 \u30BF\u30B9\u30AF\u3092 1 \u64CD\u4F5C\u3067\u751F\u6210)").argument("<target-id-prefix>", "\u5272\u308A\u8FBC\u307F\u5BFE\u8C61 (running \u4E2D) \u306E\u30BF\u30B9\u30AF id prefix").argument("<title>", "\u5272\u308A\u8FBC\u307F\u30BF\u30B9\u30AF\u306E\u30BF\u30A4\u30C8\u30EB").requiredOption("-e, --estimated <duration>", "\u5272\u308A\u8FBC\u307F\u30BF\u30B9\u30AF\u306E\u898B\u7A4D\u3082\u308A (\u4F8B: 15m)").option("-d, --date <date>", "\u5BFE\u8C61\u65E5 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").option("--description <text>", "\u5272\u308A\u8FBC\u307F\u30BF\u30B9\u30AF\u306E\u8A73\u7D30").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", collectTag4, []).action(async (targetPrefix, title, opts) => {
3149
+ const ctx = await createAppContext();
3150
+ const date = opts.date ?? todayDateString();
3151
+ const tasks = await ctx.repos.task.findByDate(date);
3152
+ const targetTaskId = resolveTaskIdByPrefix(tasks, targetPrefix);
3153
+ const estimatedSeconds = parseEstimated(opts.estimated);
3154
+ let projectId;
3155
+ if (opts.project !== void 0) {
3156
+ const all = await ctx.repos.project.findAll();
3157
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3158
+ }
3159
+ let contextId;
3160
+ if (opts.context !== void 0) {
3161
+ const all = await ctx.repos.context.findAll();
3162
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3163
+ }
3164
+ const tagIds = [];
3165
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
3166
+ const allTags = await ctx.repos.tag.findAll();
3167
+ for (const prefix of opts.tag) {
3168
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3169
+ }
3170
+ }
3171
+ const result = await wrapDomainErrors(
3172
+ () => interruptTaskWithAudit(ctx.repos.task, ctx.repos.taskNote, {
3173
+ targetTaskId,
3174
+ interruption: {
3175
+ title,
3176
+ estimatedSeconds,
3177
+ ...opts.description !== void 0 && { description: opts.description },
3178
+ ...projectId !== void 0 && { projectId },
3179
+ ...contextId !== void 0 && { contextId },
3180
+ ...tagIds.length > 0 && { tagIds }
3181
+ }
3182
+ })
3183
+ );
3184
+ 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}`);
3188
+ });
3189
+ }
3190
+ function collectTag4(value, prev) {
3191
+ return [...prev, value];
3192
+ }
2147
3193
  function tasksListCommand() {
2148
3194
  return new Command("list").alias("ls").description("\u6307\u5B9A\u65E5 (\u30C7\u30D5\u30A9\u30EB\u30C8\u306F\u4ECA\u65E5) \u306E\u30BF\u30B9\u30AF\u4E00\u89A7\u3092\u8868\u793A\u3059\u308B").option("-d, --date <date>", "YYYY-MM-DD \u5F62\u5F0F\u306E\u65E5\u4ED8 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").action(async (opts) => {
2149
3195
  const ctx = await createAppContext();
@@ -2157,6 +3203,50 @@ function tasksListCommand() {
2157
3203
  console.table(tasks.map(formatTaskRow));
2158
3204
  });
2159
3205
  }
3206
+ function tasksParallelCommand() {
3207
+ return new Command("parallel").description("\u9032\u884C\u4E2D\u30BF\u30B9\u30AF\u3068\u4E26\u5217\u3059\u308B\u65B0\u898F\u30BF\u30B9\u30AF\u3092\u8FFD\u52A0\u3059\u308B (\u65E2\u5B9A\u3067\u5373\u8A08\u6E2C\u958B\u59CB)").argument("<title>", "\u30BF\u30B9\u30AF\u30BF\u30A4\u30C8\u30EB").requiredOption("-e, --estimated <duration>", "\u898B\u7A4D\u3082\u308A (\u4F8B: 30m, 1h)").option("-d, --date <date>", "\u5BFE\u8C61\u65E5 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").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", collectTag5, []).option("--background", "\u30D0\u30C3\u30AF\u30B0\u30E9\u30A6\u30F3\u30C9\u30BF\u30B9\u30AF\u3068\u3057\u3066\u8FFD\u52A0 (\u898B\u7A4D\u3082\u308A\u96C6\u8A08\u304B\u3089\u9664\u5916)").option("--no-start-immediately", "\u5373\u8A08\u6E2C\u958B\u59CB\u3057\u306A\u3044 (planned \u4F5C\u6210\u306E\u307F)").action(async (title, opts) => {
3208
+ const ctx = await createAppContext();
3209
+ const date = opts.date ?? todayDateString();
3210
+ const estimatedSeconds = parseEstimated(opts.estimated);
3211
+ let projectId;
3212
+ if (opts.project !== void 0) {
3213
+ const all = await ctx.repos.project.findAll();
3214
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3215
+ }
3216
+ let contextId;
3217
+ if (opts.context !== void 0) {
3218
+ const all = await ctx.repos.context.findAll();
3219
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3220
+ }
3221
+ const tagIds = [];
3222
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
3223
+ const allTags = await ctx.repos.tag.findAll();
3224
+ for (const prefix of opts.tag) {
3225
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3226
+ }
3227
+ }
3228
+ const task = await wrapDomainErrors(
3229
+ () => addParallelTask(ctx.repos.task, {
3230
+ date,
3231
+ title,
3232
+ estimatedSeconds,
3233
+ ...opts.description !== void 0 && { description: opts.description },
3234
+ ...projectId !== void 0 && { projectId },
3235
+ ...contextId !== void 0 && { contextId },
3236
+ ...tagIds.length > 0 && { tagIds },
3237
+ ...opts.background !== void 0 && { isBackground: opts.background },
3238
+ ...opts.startImmediately !== void 0 && {
3239
+ startImmediately: opts.startImmediately
3240
+ }
3241
+ })
3242
+ );
3243
+ const icon = task.status === "running" ? "\u25B6" : "+";
3244
+ console.log(`${icon} Parallel task: ${shortenId(task.id)} ${task.title} (status=${task.status})`);
3245
+ });
3246
+ }
3247
+ function collectTag5(value, prev) {
3248
+ return [...prev, value];
3249
+ }
2160
3250
  function tasksPauseCommand() {
2161
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) => {
2162
3252
  const ctx = await createAppContext();
@@ -2167,6 +3257,118 @@ function tasksPauseCommand() {
2167
3257
  console.log(`\u23F8 Paused: ${shortenId(updated.id)} ${updated.title}`);
2168
3258
  });
2169
3259
  }
3260
+ function tasksRecentCommand() {
3261
+ return new Command("recent").description("\u904E\u53BB N \u65E5\u306E\u30BF\u30B9\u30AF\u3092\u8868\u793A\u3059\u308B (fuzzy match \u5BFE\u5FDC)").option("--days <n>", "\u4ECA\u65E5\u304B\u3089\u9061\u308B\u65E5\u6570 (default: 90)").option("-q, --query <text>", "fuzzy match \u3067\u985E\u4F3C\u30BF\u30A4\u30C8\u30EB\u3092\u4E0A\u4F4D\u306B").option("--limit <n>", "\u8FD4\u5374\u4EF6\u6570\u306E\u4E0A\u9650 (default: 100)").action(async (opts) => {
3262
+ const ctx = await createAppContext();
3263
+ const days = opts.days ? Number(opts.days) : 90;
3264
+ const limit = opts.limit ? Number(opts.limit) : 100;
3265
+ const fromDate = (() => {
3266
+ const d = fromDateString(todayDateString());
3267
+ d.setDate(d.getDate() - days);
3268
+ return toDateString(d);
3269
+ })();
3270
+ const all = await ctx.repos.task.findFromDate(fromDate);
3271
+ const ordered = opts.query ? findSimilarTasks(opts.query, all, limit) : [...all].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
3272
+ const display = ordered.slice(0, limit);
3273
+ if (display.length === 0) {
3274
+ console.log("No recent tasks.");
3275
+ return;
3276
+ }
3277
+ console.table(display.map(formatTaskRow));
3278
+ if (ordered.length > limit) {
3279
+ console.log(`(${ordered.length - limit} more, use --limit to see them)`);
3280
+ }
3281
+ });
3282
+ }
3283
+ function tasksReopenCommand() {
3284
+ return new Command("reopen").description("completed / skipped \u306E\u30BF\u30B9\u30AF\u3092 planned \u306B\u623B\u3059").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) => {
3285
+ const ctx = await createAppContext();
3286
+ const date = opts.date ?? todayDateString();
3287
+ const tasks = await ctx.repos.task.findByDate(date);
3288
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3289
+ const updated = await reopenTask(ctx.repos.task, taskId);
3290
+ console.log(`\u21BB Reopened: ${shortenId(updated.id)} ${updated.title}`);
3291
+ });
3292
+ }
3293
+ var HHMM3 = /^([01]\d|2[0-3]):[0-5]\d$/;
3294
+ function tasksReorderCommand() {
3295
+ return new Command("reorder").description("\u30BF\u30B9\u30AF\u306E\u4E26\u3073\u9806 (order_index) \u3092\u76F4\u63A5\u6307\u5B9A\u3059\u308B").argument("<id-prefix>", "\u30BF\u30B9\u30AF id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").requiredOption("--order-index <n>", "orderIndex \u306E\u6574\u6570\u5024 (\u6163\u7FD2 10 \u5358\u4F4D)").option("-d, --date <date>", "\u5BFE\u8C61\u65E5 (\u30C7\u30D5\u30A9\u30EB\u30C8: \u4ECA\u65E5)").option("--scheduled <HH:MM>", "\u4E88\u5B9A\u958B\u59CB\u6642\u523B\u3092\u540C\u6642\u66F4\u65B0 (\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF\u79FB\u52D5)").option("--clear-scheduled", "\u4E88\u5B9A\u958B\u59CB\u6642\u523B\u3092\u30AF\u30EA\u30A2 (\u672A\u5272\u5F53\u30B0\u30EB\u30FC\u30D7\u306B\u79FB\u52D5)").action(async (prefix, opts) => {
3296
+ const ctx = await createAppContext();
3297
+ const date = opts.date ?? todayDateString();
3298
+ const tasks = await ctx.repos.task.findByDate(date);
3299
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3300
+ const orderIndex = Number(opts.orderIndex);
3301
+ if (!Number.isInteger(orderIndex)) {
3302
+ throw new CliError(`--order-index must be an integer (got "${opts.orderIndex}").`);
3303
+ }
3304
+ if (opts.scheduled !== void 0 && !HHMM3.test(opts.scheduled)) {
3305
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
3306
+ }
3307
+ const patch = { orderIndex };
3308
+ if (opts.clearScheduled) patch.scheduledStartTime = void 0;
3309
+ else if (opts.scheduled !== void 0) patch.scheduledStartTime = opts.scheduled;
3310
+ const updated = await ctx.repos.task.update(taskId, patch);
3311
+ console.log(`\u2195 Reordered: ${shortenId(updated.id)} ${updated.title} (order_index=${updated.orderIndex})`);
3312
+ });
3313
+ }
3314
+ var HHMMSS = /^([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/;
3315
+ function tasksSetActualTimesCommand() {
3316
+ return new Command("set-actual-times").description("\u5B9F\u7E3E\u958B\u59CB/\u7D42\u4E86\u6642\u523B\u3092\u76F4\u63A5\u6307\u5B9A\u3059\u308B (Play \u62BC\u3057\u5FD8\u308C\u30EA\u30AB\u30D0\u30EA\u7528)").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("--start <HH:MM[:SS]>", "\u5B9F\u7E3E\u958B\u59CB\u6642\u523B (\u79D2\u306F\u4EFB\u610F)").option("--end <HH:MM[:SS]>", "\u5B9F\u7E3E\u7D42\u4E86\u6642\u523B (\u79D2\u306F\u4EFB\u610F)").option("--clear-start", "\u958B\u59CB\u6642\u523B\u3092\u30AF\u30EA\u30A2 (end \u3082\u81EA\u52D5\u30AF\u30EA\u30A2)").option("--clear-end", "\u7D42\u4E86\u6642\u523B\u306E\u307F\u30AF\u30EA\u30A2 (status \u306F running \u306B)").option("--reset", "\u5B9F\u7E3E\u3092\u5168\u30AF\u30EA\u30A2 (status \u2192 planned)").action(async (prefix, opts) => {
3317
+ const ctx = await createAppContext();
3318
+ const date = opts.date ?? todayDateString();
3319
+ const tasks = await ctx.repos.task.findByDate(date);
3320
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3321
+ const task = tasks.find((t) => t.id === taskId);
3322
+ if (opts.start !== void 0 && !HHMMSS.test(opts.start)) {
3323
+ throw new CliError(`--start must be HH:MM or HH:MM:SS (got "${opts.start}").`);
3324
+ }
3325
+ if (opts.end !== void 0 && !HHMMSS.test(opts.end)) {
3326
+ throw new CliError(`--end must be HH:MM or HH:MM:SS (got "${opts.end}").`);
3327
+ }
3328
+ let startTime;
3329
+ let endTime;
3330
+ if (opts.reset || opts.clearStart) {
3331
+ startTime = null;
3332
+ endTime = null;
3333
+ } else if (opts.start !== void 0) {
3334
+ startTime = opts.start;
3335
+ endTime = opts.clearEnd ? null : opts.end ?? null;
3336
+ } else if (opts.clearEnd) {
3337
+ if (!task.actualStartTime) {
3338
+ throw new CliError(`--clear-end requires the task to already have an actual start time.`);
3339
+ }
3340
+ startTime = task.actualStartTime;
3341
+ endTime = null;
3342
+ } else if (opts.end !== void 0) {
3343
+ if (!task.actualStartTime) {
3344
+ throw new CliError(
3345
+ "--end alone requires the task to already have an actual start time (set --start as well)."
3346
+ );
3347
+ }
3348
+ startTime = task.actualStartTime;
3349
+ endTime = opts.end;
3350
+ } else {
3351
+ throw new CliError(
3352
+ "No actual-times change. Specify at least one of --start / --end / --clear-start / --clear-end / --reset."
3353
+ );
3354
+ }
3355
+ const patch = buildSetActualTimesPatch(task, { startTime, endTime });
3356
+ const updated = await ctx.repos.task.update(taskId, patch);
3357
+ console.log(
3358
+ `\u23F1 Set actual times: ${shortenId(updated.id)} ${updated.title} (status=${updated.status})`
3359
+ );
3360
+ });
3361
+ }
3362
+ function tasksSkipCommand() {
3363
+ return new Command("skip").description("\u30BF\u30B9\u30AF\u3092\u30B9\u30AD\u30C3\u30D7\u3059\u308B (status \u2192 skipped)").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) => {
3364
+ const ctx = await createAppContext();
3365
+ const date = opts.date ?? todayDateString();
3366
+ const tasks = await ctx.repos.task.findByDate(date);
3367
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3368
+ const updated = await skipTask(ctx.repos.task, taskId);
3369
+ console.log(`\u293C Skipped: ${shortenId(updated.id)} ${updated.title}`);
3370
+ });
3371
+ }
2170
3372
  function tasksStartCommand() {
2171
3373
  return new Command("start").description("\u30BF\u30B9\u30AF\u306E\u8A08\u6E2C\u3092\u958B\u59CB\u3059\u308B (status \u2192 running)").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) => {
2172
3374
  const ctx = await createAppContext();
@@ -2177,10 +3379,265 @@ function tasksStartCommand() {
2177
3379
  console.log(`\u25B6 Started: ${shortenId(updated.id)} ${updated.title}`);
2178
3380
  });
2179
3381
  }
3382
+ var TIME_RE3 = /^([01]\d|2[0-3]):[0-5]\d$/;
3383
+ async function buildPatch(ctx, opts) {
3384
+ const patch = {};
3385
+ if (opts.title !== void 0) patch.title = opts.title;
3386
+ if (opts.clearDescription) {
3387
+ patch.description = void 0;
3388
+ } else if (opts.description !== void 0) {
3389
+ patch.description = opts.description;
3390
+ }
3391
+ if (opts.estimated !== void 0) {
3392
+ patch.estimatedSeconds = parseEstimated(opts.estimated);
3393
+ }
3394
+ if (opts.clearProject) {
3395
+ patch.projectId = void 0;
3396
+ } else if (opts.project !== void 0) {
3397
+ const all = await ctx.repos.project.findAll();
3398
+ patch.projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3399
+ }
3400
+ if (opts.clearContext) {
3401
+ patch.contextId = void 0;
3402
+ } else if (opts.context !== void 0) {
3403
+ const all = await ctx.repos.context.findAll();
3404
+ patch.contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3405
+ }
3406
+ if (opts.noTags) {
3407
+ patch.tagIds = [];
3408
+ } else if (opts.tag !== void 0 && opts.tag.length > 0) {
3409
+ const allTags = await ctx.repos.tag.findAll();
3410
+ const resolvedIds = [];
3411
+ for (const prefix of opts.tag) {
3412
+ resolvedIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3413
+ }
3414
+ patch.tagIds = resolvedIds;
3415
+ }
3416
+ if (opts.clearScheduled) {
3417
+ patch.scheduledStartTime = void 0;
3418
+ } else if (opts.scheduled !== void 0) {
3419
+ if (!TIME_RE3.test(opts.scheduled)) {
3420
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
3421
+ }
3422
+ patch.scheduledStartTime = opts.scheduled;
3423
+ }
3424
+ if (opts.notify !== void 0) patch.notifyAtScheduledStart = opts.notify;
3425
+ if (opts.background !== void 0) patch.isBackground = opts.background;
3426
+ if (Object.keys(patch).length === 0) {
3427
+ throw new CliError(
3428
+ "No field to update. Specify at least one of --title / --description / --estimated / --project / --context / --tag / --scheduled / --notify / --background / --clear-*."
3429
+ );
3430
+ }
3431
+ return patch;
3432
+ }
3433
+ 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) => {
3435
+ const ctx = await createAppContext();
3436
+ const date = opts.date ?? todayDateString();
3437
+ const tasks = await ctx.repos.task.findByDate(date);
3438
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3439
+ const patch = await buildPatch(ctx, opts);
3440
+ const updated = await ctx.repos.task.update(taskId, patch);
3441
+ console.log(`\u270E Updated: ${shortenId(updated.id)} ${updated.title}`);
3442
+ });
3443
+ }
3444
+ function collectTag6(value, prev) {
3445
+ return [...prev, value];
3446
+ }
2180
3447
 
2181
3448
  // src/commands/tasks/index.ts
2182
3449
  function tasksCommand() {
2183
- return new Command("tasks").description("\u30BF\u30B9\u30AF\u306E\u4E00\u89A7/\u8FFD\u52A0/\u64CD\u4F5C").addCommand(tasksListCommand()).addCommand(tasksAddCommand()).addCommand(tasksStartCommand()).addCommand(tasksPauseCommand()).addCommand(tasksDoneCommand());
3450
+ return new Command("tasks").description("\u30BF\u30B9\u30AF\u306E\u4E00\u89A7/\u8FFD\u52A0/\u64CD\u4F5C").addCommand(tasksListCommand()).addCommand(tasksAddCommand()).addCommand(tasksUpdateCommand()).addCommand(tasksDeleteCommand()).addCommand(tasksRecentCommand()).addCommand(tasksStartCommand()).addCommand(tasksPauseCommand()).addCommand(tasksDoneCommand()).addCommand(tasksSkipCommand()).addCommand(tasksReopenCommand()).addCommand(tasksSetActualTimesCommand()).addCommand(tasksInterruptCommand()).addCommand(tasksParallelCommand()).addCommand(tasksReorderCommand());
3451
+ }
3452
+ function timeBlocksArchiveCommand() {
3453
+ return new Command("archive").description("\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF\u3092\u30A2\u30FC\u30AB\u30A4\u30D6\u3059\u308B").argument("<id-prefix>", "\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
3454
+ const ctx = await createAppContext();
3455
+ const all = await ctx.repos.timeBlock.findAll();
3456
+ const id = resolveResourceIdByPrefix(all, prefix, "time-block");
3457
+ const archived = await ctx.repos.timeBlock.archive(id);
3458
+ console.log(`\u{1F4E6} Archived time-block: ${shortenId(archived.id)} ${archived.name}`);
3459
+ });
3460
+ }
3461
+ var HHMM4 = /^([01]\d|2[0-3]):[0-5]\d$/;
3462
+ var DATE_RE5 = /^\d{4}-\d{2}-\d{2}$/;
3463
+ function timeBlocksCreateCommand() {
3464
+ 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
+ const ctx = await createAppContext();
3466
+ if (!HHMM4.test(opts.startTime)) {
3467
+ throw new CliError(`--start-time must be HH:MM (got "${opts.startTime}").`);
3468
+ }
3469
+ if (!HHMM4.test(opts.endTime)) {
3470
+ throw new CliError(`--end-time must be HH:MM (got "${opts.endTime}").`);
3471
+ }
3472
+ const weekdays = parseWeekdaysList(opts.weekdays);
3473
+ if (opts.startDate !== void 0 && !DATE_RE5.test(opts.startDate)) {
3474
+ throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
3475
+ }
3476
+ if (opts.endDate !== void 0 && !DATE_RE5.test(opts.endDate)) {
3477
+ throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
3478
+ }
3479
+ let projectId;
3480
+ if (opts.project !== void 0) {
3481
+ const all = await ctx.repos.project.findAll();
3482
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3483
+ }
3484
+ let contextId;
3485
+ if (opts.context !== void 0) {
3486
+ const all = await ctx.repos.context.findAll();
3487
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3488
+ }
3489
+ const tagIds = [];
3490
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
3491
+ const allTags = await ctx.repos.tag.findAll();
3492
+ for (const prefix of opts.tag) {
3493
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3494
+ }
3495
+ }
3496
+ const input2 = {
3497
+ name,
3498
+ startTime: opts.startTime,
3499
+ endTime: opts.endTime,
3500
+ weekdays,
3501
+ status: "active"
3502
+ };
3503
+ if (projectId !== void 0) input2.defaultProjectId = projectId;
3504
+ if (contextId !== void 0) input2.defaultContextId = contextId;
3505
+ if (tagIds.length > 0) input2.defaultTagIds = tagIds;
3506
+ if (opts.color !== void 0) input2.color = opts.color;
3507
+ if (opts.startDate !== void 0) input2.startDate = opts.startDate;
3508
+ if (opts.endDate !== void 0) input2.endDate = opts.endDate;
3509
+ const block = await ctx.repos.timeBlock.create(input2);
3510
+ console.log(`\u2713 Created time-block: ${shortenId(block.id)} ${block.name}`);
3511
+ });
3512
+ }
3513
+ function collectTag7(value, prev) {
3514
+ return [...prev, value];
3515
+ }
3516
+ function timeBlocksDeleteCommand() {
3517
+ return new Command("delete").alias("rm").description("\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF\u3092\u7269\u7406\u524A\u9664\u3059\u308B").argument("<id-prefix>", "\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").action(async (prefix) => {
3518
+ const ctx = await createAppContext();
3519
+ const all = await ctx.repos.timeBlock.findAll();
3520
+ const id = resolveResourceIdByPrefix(all, prefix, "time-block");
3521
+ const target = all.find((b) => b.id === id);
3522
+ await ctx.repos.timeBlock.delete(id);
3523
+ console.log(`\u2717 Deleted time-block: ${shortenId(id)} ${target.name}`);
3524
+ });
3525
+ }
3526
+
3527
+ // src/format/time-block-row.ts
3528
+ var WEEKDAY_LABELS = ["\u65E5", "\u6708", "\u706B", "\u6C34", "\u6728", "\u91D1", "\u571F"];
3529
+ function formatWeekdays(weekdays) {
3530
+ if (weekdays.length === 7) return "every";
3531
+ if (weekdays.length === 0) return "-";
3532
+ const labels = [];
3533
+ for (const w of weekdays) {
3534
+ if (w >= 0 && w <= 6) {
3535
+ labels.push(WEEKDAY_LABELS[w] ?? "?");
3536
+ }
3537
+ }
3538
+ return labels.join("");
3539
+ }
3540
+ function formatTimeBlockRow(block) {
3541
+ return {
3542
+ id: shortenId(block.id),
3543
+ name: block.name,
3544
+ status: block.status,
3545
+ time: `${block.startTime}-${block.endTime}`,
3546
+ weekdays: formatWeekdays(block.weekdays),
3547
+ color: block.color ?? "-"
3548
+ };
3549
+ }
3550
+
3551
+ // src/commands/time-blocks/list.ts
3552
+ function timeBlocksListCommand() {
3553
+ return new Command("list").alias("ls").description("\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF\u4E00\u89A7\u3092\u8868\u793A\u3059\u308B (\u65E2\u5B9A: active \u306E\u307F)").option("--archived", "archived \u306E\u307F\u8868\u793A").option("--all", "active + archived \u4E21\u65B9\u8868\u793A").action(async (opts) => {
3554
+ const ctx = await createAppContext();
3555
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : { status: "active" };
3556
+ const blocks = await ctx.repos.timeBlock.findAll(filter);
3557
+ if (blocks.length === 0) {
3558
+ console.log("No time blocks.");
3559
+ return;
3560
+ }
3561
+ console.table(blocks.map(formatTimeBlockRow));
3562
+ });
3563
+ }
3564
+ var HHMM5 = /^([01]\d|2[0-3]):[0-5]\d$/;
3565
+ var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
3566
+ var TIME_BLOCK_STATUSES = ["active", "archived"];
3567
+ function isTimeBlockStatus(value) {
3568
+ return TIME_BLOCK_STATUSES.includes(value);
3569
+ }
3570
+ function timeBlocksUpdateCommand() {
3571
+ return new Command("update").description("\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF\u3092\u90E8\u5206\u66F4\u65B0\u3059\u308B").argument("<id-prefix>", "\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF id \u306E\u5148\u982D 4 \u6587\u5B57\u4EE5\u4E0A").option("--name <text>", "\u540D\u524D\u5909\u66F4").option("--start-time <HH:MM>", "\u958B\u59CB\u6642\u523B").option("--end-time <HH:MM>", "\u7D42\u4E86\u6642\u523B").option("--weekdays <csv>", "\u5BFE\u8C61\u66DC\u65E5 (0-6) \u30AB\u30F3\u30DE\u533A\u5207\u308A").option("--project <id-prefix>", "\u30C7\u30D5\u30A9\u30EB\u30C8\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8").option("--clear-project", "\u30C7\u30D5\u30A9\u30EB\u30C8\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u5916\u3059").option("--context <id-prefix>", "\u30C7\u30D5\u30A9\u30EB\u30C8\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8").option("--clear-context", "\u30C7\u30D5\u30A9\u30EB\u30C8\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u5916\u3059").option("--tag <id-prefix>", "\u30C7\u30D5\u30A9\u30EB\u30C8\u30BF\u30B0 (\u7E70\u308A\u8FD4\u3057\u53EF)", collectTag8, []).option("--no-tags", "\u30C7\u30D5\u30A9\u30EB\u30C8\u30BF\u30B0\u3092\u5168\u524A\u9664").option("--color <color>", "UI \u8272").option("--clear-color", "\u8272\u3092\u5916\u3059").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 / archived)").action(async (prefix, opts) => {
3572
+ const ctx = await createAppContext();
3573
+ const all = await ctx.repos.timeBlock.findAll();
3574
+ const id = resolveResourceIdByPrefix(all, prefix, "time-block");
3575
+ if (opts.startTime !== void 0 && !HHMM5.test(opts.startTime)) {
3576
+ throw new CliError(`--start-time must be HH:MM (got "${opts.startTime}").`);
3577
+ }
3578
+ if (opts.endTime !== void 0 && !HHMM5.test(opts.endTime)) {
3579
+ throw new CliError(`--end-time must be HH:MM (got "${opts.endTime}").`);
3580
+ }
3581
+ if (opts.startDate !== void 0 && !DATE_RE6.test(opts.startDate)) {
3582
+ throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
3583
+ }
3584
+ if (opts.endDate !== void 0 && !DATE_RE6.test(opts.endDate)) {
3585
+ throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
3586
+ }
3587
+ if (opts.status !== void 0 && !isTimeBlockStatus(opts.status)) {
3588
+ throw new CliError(
3589
+ `--status must be one of ${TIME_BLOCK_STATUSES.join(" / ")} (got "${opts.status}").`
3590
+ );
3591
+ }
3592
+ const patch = {};
3593
+ if (opts.name !== void 0) patch.name = opts.name;
3594
+ if (opts.startTime !== void 0) patch.startTime = opts.startTime;
3595
+ if (opts.endTime !== void 0) patch.endTime = opts.endTime;
3596
+ if (opts.weekdays !== void 0) patch.weekdays = parseWeekdaysList(opts.weekdays);
3597
+ if (opts.clearProject) patch.defaultProjectId = void 0;
3598
+ else if (opts.project !== void 0) {
3599
+ const projects = await ctx.repos.project.findAll();
3600
+ patch.defaultProjectId = resolveResourceIdByPrefix(projects, opts.project, "project");
3601
+ }
3602
+ if (opts.clearContext) patch.defaultContextId = void 0;
3603
+ else if (opts.context !== void 0) {
3604
+ const contexts = await ctx.repos.context.findAll();
3605
+ patch.defaultContextId = resolveResourceIdByPrefix(contexts, opts.context, "context");
3606
+ }
3607
+ if (opts.noTags) patch.defaultTagIds = [];
3608
+ else if (opts.tag !== void 0 && opts.tag.length > 0) {
3609
+ const tags = await ctx.repos.tag.findAll();
3610
+ const resolved = [];
3611
+ for (const p of opts.tag) {
3612
+ resolved.push(resolveResourceIdByPrefix(tags, p, "tag"));
3613
+ }
3614
+ patch.defaultTagIds = resolved;
3615
+ }
3616
+ if (opts.clearColor) patch.color = void 0;
3617
+ else if (opts.color !== void 0) patch.color = opts.color;
3618
+ if (opts.clearStartDate) patch.startDate = void 0;
3619
+ else if (opts.startDate !== void 0) patch.startDate = opts.startDate;
3620
+ if (opts.clearEndDate) patch.endDate = void 0;
3621
+ else if (opts.endDate !== void 0) patch.endDate = opts.endDate;
3622
+ if (opts.status !== void 0 && isTimeBlockStatus(opts.status)) {
3623
+ patch.status = opts.status;
3624
+ }
3625
+ if (Object.keys(patch).length === 0) {
3626
+ throw new CliError(
3627
+ "No field to update. Specify at least one of --name / --start-time / --end-time / --weekdays / --project / --context / --tag / --color / --start-date / --end-date / --status / --clear-*."
3628
+ );
3629
+ }
3630
+ const updated = await ctx.repos.timeBlock.update(id, patch);
3631
+ console.log(`\u270E Updated time-block: ${shortenId(updated.id)} ${updated.name}`);
3632
+ });
3633
+ }
3634
+ function collectTag8(value, prev) {
3635
+ return [...prev, value];
3636
+ }
3637
+
3638
+ // src/commands/time-blocks/index.ts
3639
+ function timeBlocksCommand() {
3640
+ return new Command("time-blocks").description("\u30BF\u30A4\u30E0\u30D6\u30ED\u30C3\u30AF\u306E\u4E00\u89A7/\u8FFD\u52A0/\u64CD\u4F5C").addCommand(timeBlocksListCommand()).addCommand(timeBlocksCreateCommand()).addCommand(timeBlocksUpdateCommand()).addCommand(timeBlocksArchiveCommand()).addCommand(timeBlocksDeleteCommand());
2184
3641
  }
2185
3642
  function unlockCommand() {
2186
3643
  return new Command("unlock").description("E2EE passphrase \u3092 keyring \u306B\u4FDD\u5B58\u3057\u3066\u4EE5\u964D\u306E\u30B3\u30DE\u30F3\u30C9\u3092\u5BFE\u8A71\u306A\u3057\u306B\u8D70\u3089\u305B\u308B").action(async () => {
@@ -2233,13 +3690,18 @@ function whoamiCommand() {
2233
3690
 
2234
3691
  // src/index.ts
2235
3692
  var program = new Command();
2236
- 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.1.2" );
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" );
2237
3694
  program.addCommand(loginCommand());
2238
3695
  program.addCommand(whoamiCommand());
2239
3696
  program.addCommand(logoutCommand());
2240
3697
  program.addCommand(unlockCommand());
2241
3698
  program.addCommand(lockCommand());
2242
3699
  program.addCommand(tasksCommand());
3700
+ program.addCommand(projectsCommand());
3701
+ program.addCommand(tagsCommand());
3702
+ program.addCommand(contextsCommand());
3703
+ program.addCommand(routinesCommand());
3704
+ program.addCommand(timeBlocksCommand());
2243
3705
  program.parseAsync(process.argv).catch((err) => {
2244
3706
  if (err instanceof CliError) {
2245
3707
  console.error(err.message);