@fukuchang/taskchute-cli 0.1.1 → 0.2.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
  }
@@ -1535,7 +1613,32 @@ function toTimeString(date) {
1535
1613
  return `${hours}:${minutes}`;
1536
1614
  }
1537
1615
 
1616
+ // ../domain/src/task-utils.ts
1617
+ function sessionDurationSeconds(session) {
1618
+ return (session.finishedAt.getTime() - session.startedAt.getTime()) / 1e3;
1619
+ }
1620
+ function completedSessionsTotalSeconds(task) {
1621
+ return task.sessions.reduce((sum, s) => sum + sessionDurationSeconds(s), 0);
1622
+ }
1623
+ function calculateActualSeconds(task, now = /* @__PURE__ */ new Date()) {
1624
+ const completed = completedSessionsTotalSeconds(task);
1625
+ if (task.currentSession) {
1626
+ return completed + (now.getTime() - task.currentSession.startedAt.getTime()) / 1e3;
1627
+ }
1628
+ return completed;
1629
+ }
1630
+
1538
1631
  // ../domain/src/task-operations.ts
1632
+ var TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
1633
+ function parseTimeOnDate(dateString, timeString) {
1634
+ const m = TIME_RE.exec(timeString);
1635
+ if (!m || !m[1] || !m[2]) {
1636
+ throw new Error(`Invalid time string: ${timeString}`);
1637
+ }
1638
+ const base = fromDateString(dateString);
1639
+ base.setHours(Number(m[1]), Number(m[2]), 0, 0);
1640
+ return base;
1641
+ }
1539
1642
  async function startTask(repo, taskId) {
1540
1643
  const task = await repo.findById(taskId);
1541
1644
  if (!task) throw new Error(`Task not found: ${taskId}`);
@@ -1566,6 +1669,30 @@ async function pauseTask(repo, taskId) {
1566
1669
  currentSession: void 0
1567
1670
  });
1568
1671
  }
1672
+ async function skipTask(repo, taskId) {
1673
+ const task = await repo.findById(taskId);
1674
+ if (!task) throw new Error(`Task not found: ${taskId}`);
1675
+ const updates = { status: "skipped" };
1676
+ if (task.currentSession) {
1677
+ updates.sessions = [
1678
+ ...task.sessions,
1679
+ {
1680
+ startedAt: task.currentSession.startedAt,
1681
+ finishedAt: /* @__PURE__ */ new Date()
1682
+ }
1683
+ ];
1684
+ updates.currentSession = void 0;
1685
+ }
1686
+ return repo.update(taskId, updates);
1687
+ }
1688
+ async function reopenTask(repo, taskId) {
1689
+ const task = await repo.findById(taskId);
1690
+ if (!task) throw new Error(`Task not found: ${taskId}`);
1691
+ if (task.status !== "completed" && task.status !== "skipped") {
1692
+ return task;
1693
+ }
1694
+ return repo.update(taskId, { status: "planned" });
1695
+ }
1569
1696
  async function completeTask(repo, taskId) {
1570
1697
  const task = await repo.findById(taskId);
1571
1698
  if (!task) throw new Error(`Task not found: ${taskId}`);
@@ -1582,6 +1709,115 @@ async function completeTask(repo, taskId) {
1582
1709
  }
1583
1710
  return repo.update(taskId, updates);
1584
1711
  }
1712
+ async function interruptTask(repo, params) {
1713
+ const target = await repo.findById(params.targetTaskId);
1714
+ if (!target) throw new Error(`Task not found: ${params.targetTaskId}`);
1715
+ if (!target.currentSession) {
1716
+ throw new Error(`Target task is not currently running`);
1717
+ }
1718
+ const now = /* @__PURE__ */ new Date();
1719
+ const closedSession = {
1720
+ startedAt: target.currentSession.startedAt,
1721
+ finishedAt: now
1722
+ };
1723
+ const finishedOriginal = await repo.update(target.id, {
1724
+ status: "completed",
1725
+ sessions: [...target.sessions, closedSession],
1726
+ currentSession: void 0
1727
+ });
1728
+ const elapsed = calculateActualSeconds(finishedOriginal);
1729
+ const interruptionStartedAt = /* @__PURE__ */ new Date();
1730
+ const interruption = await repo.create({
1731
+ date: target.date,
1732
+ title: params.interruption.title,
1733
+ description: params.interruption.description,
1734
+ estimatedSeconds: params.interruption.estimatedSeconds,
1735
+ sessions: [],
1736
+ currentSession: { startedAt: interruptionStartedAt },
1737
+ status: "running",
1738
+ orderIndex: target.orderIndex + 1,
1739
+ actualStartTime: toTimeString(interruptionStartedAt),
1740
+ projectId: params.interruption.projectId,
1741
+ contextId: params.interruption.contextId,
1742
+ tagIds: params.interruption.tagIds ?? []
1743
+ });
1744
+ const continuation = await repo.create({
1745
+ date: target.date,
1746
+ title: target.title,
1747
+ description: target.description,
1748
+ estimatedSeconds: Math.max(0, Math.floor(target.estimatedSeconds - elapsed)),
1749
+ sessions: [],
1750
+ status: "planned",
1751
+ orderIndex: target.orderIndex + 2,
1752
+ splitFromTaskId: target.id,
1753
+ projectId: target.projectId,
1754
+ contextId: target.contextId,
1755
+ // continuation は「割り込みが片付いた後にやる残作業」。割り込み発生時刻を
1756
+ // 予定時刻にすることで、(1) interruption と同じ effectiveTime になり、
1757
+ // ソート上 orderIndex で interruption の次に並ぶ、(2) 割り込みが TimeBlock
1758
+ // の境界をまたいだ場合は continuation も新しいブロックに移動する、という
1759
+ // 自然な挙動になる。
1760
+ scheduledStartTime: toTimeString(interruptionStartedAt),
1761
+ tagIds: target.tagIds,
1762
+ isBackground: target.isBackground
1763
+ });
1764
+ return { finishedOriginal, interruption, continuation };
1765
+ }
1766
+ async function addParallelTask(repo, input2) {
1767
+ const startImmediately = input2.startImmediately ?? true;
1768
+ const existing = await repo.findByDate(input2.date);
1769
+ const maxOrder = existing.reduce((max, t) => t.orderIndex > max ? t.orderIndex : max, 0);
1770
+ const startedAt = startImmediately ? /* @__PURE__ */ new Date() : void 0;
1771
+ return repo.create({
1772
+ date: input2.date,
1773
+ title: input2.title,
1774
+ description: input2.description,
1775
+ estimatedSeconds: input2.estimatedSeconds,
1776
+ sessions: [],
1777
+ currentSession: startedAt ? { startedAt } : void 0,
1778
+ status: startImmediately ? "running" : "planned",
1779
+ orderIndex: maxOrder + 10,
1780
+ actualStartTime: startedAt ? toTimeString(startedAt) : void 0,
1781
+ projectId: input2.projectId,
1782
+ contextId: input2.contextId,
1783
+ tagIds: input2.tagIds ?? [],
1784
+ isBackground: input2.isBackground
1785
+ });
1786
+ }
1787
+ function buildSetActualTimesPatch(task, input2) {
1788
+ const startTime = input2.startTime?.trim() ? input2.startTime.trim() : null;
1789
+ const endTime = input2.endTime?.trim() ? input2.endTime.trim() : null;
1790
+ if (startTime === null && endTime === null) {
1791
+ return {
1792
+ status: "planned",
1793
+ currentSession: void 0,
1794
+ sessions: [],
1795
+ actualStartTime: void 0
1796
+ };
1797
+ }
1798
+ if (startTime === null && endTime !== null) {
1799
+ throw new Error("endTime requires startTime");
1800
+ }
1801
+ const startedAt = parseTimeOnDate(task.date, startTime);
1802
+ if (endTime === null) {
1803
+ return {
1804
+ status: "running",
1805
+ currentSession: { startedAt },
1806
+ sessions: [],
1807
+ actualStartTime: startTime
1808
+ };
1809
+ }
1810
+ let finishedAt = parseTimeOnDate(task.date, endTime);
1811
+ if (finishedAt.getTime() < startedAt.getTime()) {
1812
+ finishedAt = new Date(finishedAt.getTime() + 24 * 60 * 60 * 1e3);
1813
+ }
1814
+ return {
1815
+ status: "completed",
1816
+ currentSession: void 0,
1817
+ sessions: [{ startedAt, finishedAt }],
1818
+ actualStartTime: startTime
1819
+ };
1820
+ }
1585
1821
 
1586
1822
  // ../domain/src/task-reorder.ts
1587
1823
  function computeNextOrderIndex(existingTasks) {
@@ -1593,6 +1829,70 @@ function computeNextOrderIndex(existingTasks) {
1593
1829
  return max + 10;
1594
1830
  }
1595
1831
 
1832
+ // ../domain/src/task-suggest.ts
1833
+ function normalizeForSearch(str) {
1834
+ return str.trim().toLowerCase().replace(
1835
+ /[ぁ-ゖ]/g,
1836
+ (ch) => String.fromCharCode(ch.charCodeAt(0) + 96)
1837
+ );
1838
+ }
1839
+ function fuzzyScore(query, target) {
1840
+ const q = query;
1841
+ const t = target;
1842
+ if (q.length === 0) return 0;
1843
+ if (t.length === 0) return null;
1844
+ let qi = 0;
1845
+ let score = 0;
1846
+ let lastMatchIndex = -2;
1847
+ let consecutiveRun = 0;
1848
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
1849
+ if (t[ti] === q[qi]) {
1850
+ if (lastMatchIndex === ti - 1) {
1851
+ consecutiveRun += 1;
1852
+ score += 5 + consecutiveRun * 3;
1853
+ } else {
1854
+ consecutiveRun = 0;
1855
+ score += 2;
1856
+ }
1857
+ if (qi === 0) {
1858
+ score += Math.max(0, 8 - ti);
1859
+ }
1860
+ lastMatchIndex = ti;
1861
+ qi += 1;
1862
+ }
1863
+ }
1864
+ if (qi < q.length) return null;
1865
+ score -= Math.max(0, t.length - q.length) / 4;
1866
+ return score;
1867
+ }
1868
+ function findSimilarTasks(query, tasks, limit = 5) {
1869
+ const q = normalizeForSearch(query);
1870
+ if (q.length === 0) return [];
1871
+ const bestByTitle = /* @__PURE__ */ new Map();
1872
+ for (const task of tasks) {
1873
+ const titleTrimmed = task.title.trim();
1874
+ if (titleTrimmed.length === 0) continue;
1875
+ const titleNormalized = normalizeForSearch(titleTrimmed);
1876
+ const score = fuzzyScore(q, titleNormalized);
1877
+ if (score === null) continue;
1878
+ const existing = bestByTitle.get(titleNormalized);
1879
+ if (!existing) {
1880
+ bestByTitle.set(titleNormalized, { task, score });
1881
+ continue;
1882
+ }
1883
+ const existingNewer = existing.task.updatedAt.getTime();
1884
+ const candidateNewer = task.updatedAt.getTime();
1885
+ if (candidateNewer > existingNewer) {
1886
+ bestByTitle.set(titleNormalized, { task, score });
1887
+ }
1888
+ }
1889
+ const sorted = Array.from(bestByTitle.values()).sort((a, b) => {
1890
+ if (b.score !== a.score) return b.score - a.score;
1891
+ return b.task.updatedAt.getTime() - a.task.updatedAt.getTime();
1892
+ });
1893
+ return sorted.slice(0, limit).map((s) => s.task);
1894
+ }
1895
+
1596
1896
  // ../domain/src/task-change-log.ts
1597
1897
  function describeStatusTransition(from, to) {
1598
1898
  if (from === "planned" && to === "running") return "\u958B\u59CB";
@@ -1827,6 +2127,80 @@ function diffTasksToNoteInputs(before, after, lookups) {
1827
2127
  }
1828
2128
  return result;
1829
2129
  }
2130
+ function titleOrPlaceholder(task) {
2131
+ return task.title || "\u7121\u984C\u306E\u30BF\u30B9\u30AF";
2132
+ }
2133
+ async function interruptTaskWithAudit(taskRepo, taskNoteRepo, params) {
2134
+ const result = await interruptTask(taskRepo, params);
2135
+ const notes = buildSplitNoteInputs(
2136
+ result.finishedOriginal,
2137
+ result.interruption,
2138
+ result.continuation
2139
+ );
2140
+ try {
2141
+ await Promise.all(notes.map((input2) => taskNoteRepo.create(input2)));
2142
+ } catch (err) {
2143
+ console.error("[audit] failed to record split notes", {
2144
+ sourceTaskId: result.finishedOriginal.id,
2145
+ err
2146
+ });
2147
+ }
2148
+ return result;
2149
+ }
2150
+ function buildSplitNoteInputs(source, interruption, continuation) {
2151
+ const sourceTitle = titleOrPlaceholder(source);
2152
+ const interruptionTitle = titleOrPlaceholder(interruption);
2153
+ return [
2154
+ {
2155
+ taskId: source.id,
2156
+ type: "split",
2157
+ 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`,
2158
+ metadata: {
2159
+ event: "split-source",
2160
+ interruptionTaskId: interruption.id,
2161
+ continuationTaskId: continuation.id
2162
+ }
2163
+ },
2164
+ {
2165
+ taskId: interruption.id,
2166
+ type: "split",
2167
+ body: `\u300C${sourceTitle}\u300D\u306E\u4F5C\u696D\u4E2D\u306B\u5272\u308A\u8FBC\u307F\u3068\u3057\u3066\u8D77\u7968\u3002`,
2168
+ metadata: {
2169
+ event: "split-interruption",
2170
+ sourceTaskId: source.id,
2171
+ continuationTaskId: continuation.id
2172
+ }
2173
+ },
2174
+ {
2175
+ taskId: continuation.id,
2176
+ type: "split",
2177
+ body: `\u300C${sourceTitle}\u300D\u306E\u6B8B\u4F5C\u696D\u3068\u3057\u3066\u7D99\u7D9A\u3002`,
2178
+ metadata: {
2179
+ event: "split-continuation",
2180
+ sourceTaskId: source.id,
2181
+ interruptionTaskId: interruption.id
2182
+ }
2183
+ }
2184
+ ];
2185
+ }
2186
+
2187
+ // ../domain/src/routine-expansion.ts
2188
+ function buildRoutineTaskPatch(routine) {
2189
+ return {
2190
+ title: routine.title,
2191
+ description: routine.description,
2192
+ estimatedSeconds: routine.estimatedSeconds,
2193
+ projectId: routine.projectId,
2194
+ contextId: routine.contextId,
2195
+ tagIds: routine.tagIds,
2196
+ scheduledStartTime: routine.defaultScheduledStartTime
2197
+ };
2198
+ }
2199
+ function pickPropagationTargets(routine, tasks, today) {
2200
+ return tasks.filter(
2201
+ (t) => t.routineId === routine.id && t.date >= today && t.status === "planned"
2202
+ );
2203
+ }
1830
2204
 
1831
2205
  // ../infra-supabase/src/audit-task-repository.ts
1832
2206
  var AuditingTaskRepository = class {
@@ -1894,17 +2268,23 @@ var AuditingTaskRepository = class {
1894
2268
  delete(id) {
1895
2269
  return this.inner.delete(id);
1896
2270
  }
2271
+ deleteWithRoutineSkip(id, today) {
2272
+ return this.inner.deleteWithRoutineSkip(id, today);
2273
+ }
1897
2274
  };
1898
2275
 
1899
2276
  // ../infra-supabase/src/composition-root.ts
1900
2277
  function buildRepositories(supabaseClient, cryptoSession, getLookups) {
1901
2278
  const taskNote = new SupabaseTaskNoteRepository(supabaseClient, cryptoSession);
1902
2279
  const rawTask = new SupabaseTaskRepository(supabaseClient, cryptoSession);
1903
- const task = getLookups ? new AuditingTaskRepository(rawTask, taskNote, getLookups) : rawTask;
2280
+ const auditingTask = getLookups ? new AuditingTaskRepository(rawTask, taskNote, getLookups) : void 0;
2281
+ const task = auditingTask ?? rawTask;
1904
2282
  return {
1905
2283
  task,
2284
+ auditingTask,
1906
2285
  taskNote,
1907
2286
  routine: new SupabaseRoutineRepository(supabaseClient, cryptoSession),
2287
+ routineExpansion: new SupabaseRoutineExpansionRepository(supabaseClient),
1908
2288
  project: new SupabaseProjectRepository(supabaseClient, cryptoSession),
1909
2289
  tag: new SupabaseTagRepository(supabaseClient, cryptoSession),
1910
2290
  context: new SupabaseContextRepository(supabaseClient, cryptoSession),
@@ -1912,43 +2292,245 @@ function buildRepositories(supabaseClient, cryptoSession, getLookups) {
1912
2292
  };
1913
2293
  }
1914
2294
 
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`
2295
+ // ../node-shared/src/create-node-app-context.ts
2296
+ async function resolvePassphrase(store, promptPassphrase) {
2297
+ const envPw = process.env.TASKCHUTE_PASSPHRASE;
2298
+ if (envPw) return envPw;
2299
+ const stored = store.get();
2300
+ if (stored) return stored;
2301
+ if (!promptPassphrase) {
2302
+ throw new AppContextError(
2303
+ "No passphrase available. Set TASKCHUTE_PASSPHRASE env or run `taskchute unlock` from the CLI first."
1924
2304
  );
1925
2305
  }
1926
- return { url, publishableKey };
2306
+ return await promptPassphrase();
1927
2307
  }
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
- }
2308
+ async function createNodeAppContext(opts = {}) {
2309
+ const passphraseStore = opts.passphraseStore ?? new KeyringPassphraseStore();
2310
+ const sessionStorage = opts.sessionStorage ?? new KeyringSessionStore();
2311
+ const supabase = createNodeSupabaseClient(readSupabaseConfigFromEnv(), {
2312
+ storage: sessionStorage
2313
+ });
2314
+ const {
2315
+ data: { user },
2316
+ error: authError
2317
+ } = await supabase.auth.getUser();
2318
+ if (authError || !user || !user.email) {
2319
+ throw new AppContextError(
2320
+ "Not logged in. Run `taskchute login` from the CLI first (session is shared via OS keyring)."
2321
+ );
1937
2322
  }
1938
- setItem(key, value) {
1939
- const entry = new Entry(KEYRING_SERVICE2, key);
1940
- entry.setPassword(value);
2323
+ const userKeysRepo = new SupabaseUserKeysRepository(supabase);
2324
+ const snapshot = await userKeysRepo.getOwn();
2325
+ if (!snapshot) {
2326
+ throw new AppContextError(
2327
+ "E2EE not set up. Complete setup from the web app first (Settings \u2192 \u30C7\u30FC\u30BF\u306E\u6697\u53F7\u5316)."
2328
+ );
1941
2329
  }
1942
- removeItem(key) {
1943
- try {
1944
- const entry = new Entry(KEYRING_SERVICE2, key);
1945
- entry.deletePassword();
1946
- } catch {
2330
+ const passphrase = await resolvePassphrase(passphraseStore, opts.promptPassphrase);
2331
+ let masterKey;
2332
+ try {
2333
+ masterKey = await unlockMasterKey(snapshot, { kind: "passphrase", passphrase });
2334
+ } catch (err) {
2335
+ if (err instanceof UnlockError) {
2336
+ throw new AppContextError(`Unlock failed: ${err.message}`);
1947
2337
  }
2338
+ throw err;
1948
2339
  }
1949
- };
2340
+ const cryptoSession = new CryptoSession();
2341
+ cryptoSession.setMasterKey(masterKey, snapshot.currentKeyId);
2342
+ const lookupsRef = {
2343
+ current: { projects: [], tags: [], contexts: [] }
2344
+ };
2345
+ const repos = buildRepositories(supabase, cryptoSession, () => lookupsRef.current);
2346
+ const refreshLookups = async () => {
2347
+ const [projects, tags, contexts] = await Promise.all([
2348
+ repos.project.findAll(),
2349
+ repos.tag.findAll(),
2350
+ repos.context.findAll()
2351
+ ]);
2352
+ lookupsRef.current = { projects, tags, contexts };
2353
+ };
2354
+ try {
2355
+ await refreshLookups();
2356
+ } catch (err) {
2357
+ console.error("[createNodeAppContext] initial refreshLookups failed:", err);
2358
+ }
2359
+ return {
2360
+ supabase,
2361
+ cryptoSession,
2362
+ repos,
2363
+ userId: user.id,
2364
+ email: user.email,
2365
+ refreshLookups
2366
+ };
2367
+ }
2368
+
2369
+ // src/crypto-context.ts
2370
+ var CliError = class extends Error {
2371
+ exitCode;
2372
+ constructor(message, exitCode = 1) {
2373
+ super(message);
2374
+ this.name = "CliError";
2375
+ this.exitCode = exitCode;
2376
+ }
2377
+ };
2378
+ async function wrapDomainErrors(op) {
2379
+ try {
2380
+ return await op();
2381
+ } catch (err) {
2382
+ if (err instanceof CliError) throw err;
2383
+ if (err instanceof Error) throw new CliError(err.message);
2384
+ throw err;
2385
+ }
2386
+ }
2387
+ async function createAppContext(opts = {}) {
2388
+ try {
2389
+ return await createNodeAppContext({
2390
+ passphraseStore: opts.passphraseStore,
2391
+ promptPassphrase: opts.allowPrompt === false ? void 0 : () => password({ message: "Passphrase:", mask: "*" })
2392
+ });
2393
+ } catch (err) {
2394
+ if (err instanceof AppContextError) {
2395
+ throw new CliError(err.message);
2396
+ }
2397
+ throw err;
2398
+ }
2399
+ }
2400
+
2401
+ // src/format/task-row.ts
2402
+ function shortenId(id, length = 8) {
2403
+ return id.slice(0, length);
2404
+ }
2405
+ function formatEstimated(seconds) {
2406
+ const totalMinutes = Math.round(seconds / 60);
2407
+ if (totalMinutes < 60) return `${totalMinutes}m`;
2408
+ const h = Math.floor(totalMinutes / 60);
2409
+ const m = totalMinutes % 60;
2410
+ return m === 0 ? `${h}h` : `${h}h${m}m`;
2411
+ }
2412
+ function formatTaskRow(task) {
2413
+ return {
2414
+ id: shortenId(task.id),
2415
+ status: task.status,
2416
+ scheduled: task.scheduledStartTime ?? "-",
2417
+ estimated: formatEstimated(task.estimatedSeconds),
2418
+ title: task.title
2419
+ };
2420
+ }
2421
+
2422
+ // src/resolve-resource-id.ts
2423
+ var MIN_PREFIX_LENGTH = 4;
2424
+ function pickLabel(r) {
2425
+ return "name" in r ? r.name : r.title;
2426
+ }
2427
+ function resolveResourceIdByPrefix(resources, prefix, resourceLabel) {
2428
+ if (prefix.length < MIN_PREFIX_LENGTH) {
2429
+ throw new CliError(
2430
+ `${resourceLabel} id prefix must be at least ${MIN_PREFIX_LENGTH} chars to avoid ambiguity.`
2431
+ );
2432
+ }
2433
+ const matches = resources.filter((r) => r.id.startsWith(prefix));
2434
+ if (matches.length === 0) {
2435
+ throw new CliError(`No ${resourceLabel} matched prefix "${prefix}".`);
2436
+ }
2437
+ if (matches.length > 1) {
2438
+ const candidates = matches.map((r) => ` ${shortenId(r.id)} ${pickLabel(r)}`).join("\n");
2439
+ throw new CliError(`Ambiguous ${resourceLabel} prefix "${prefix}":
2440
+ ${candidates}`);
2441
+ }
2442
+ return matches[0].id;
2443
+ }
2444
+
2445
+ // src/commands/contexts/archive.ts
2446
+ function contextsArchiveCommand() {
2447
+ 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) => {
2448
+ const ctx = await createAppContext();
2449
+ const all = await ctx.repos.context.findAll();
2450
+ const id = resolveResourceIdByPrefix(all, prefix, "context");
2451
+ const archived = await ctx.repos.context.archive(id);
2452
+ console.log(`\u{1F4E6} Archived context: ${shortenId(archived.id)} ${archived.name}`);
2453
+ });
2454
+ }
2455
+ function contextsCreateCommand() {
2456
+ 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) => {
2457
+ const ctx = await createAppContext();
2458
+ const input2 = { name, status: "active" };
2459
+ if (opts.description !== void 0) input2.description = opts.description;
2460
+ if (opts.icon !== void 0) input2.icon = opts.icon;
2461
+ if (opts.color !== void 0) input2.color = opts.color;
2462
+ const context = await ctx.repos.context.create(input2);
2463
+ console.log(`\u2713 Created context: ${shortenId(context.id)} ${context.name}`);
2464
+ });
2465
+ }
2466
+ function contextsDeleteCommand() {
2467
+ 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) => {
2468
+ const ctx = await createAppContext();
2469
+ const all = await ctx.repos.context.findAll();
2470
+ const id = resolveResourceIdByPrefix(all, prefix, "context");
2471
+ const target = all.find((c) => c.id === id);
2472
+ await ctx.repos.context.delete(id);
2473
+ console.log(`\u2717 Deleted context: ${shortenId(id)} ${target.name}`);
2474
+ });
2475
+ }
1950
2476
 
1951
- // src/commands/login.ts
2477
+ // src/format/context-row.ts
2478
+ function formatContextRow(context) {
2479
+ return {
2480
+ id: shortenId(context.id),
2481
+ name: context.name,
2482
+ status: context.status,
2483
+ icon: context.icon ?? "-",
2484
+ color: context.color ?? "-"
2485
+ };
2486
+ }
2487
+
2488
+ // src/commands/contexts/list.ts
2489
+ function contextsListCommand() {
2490
+ 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) => {
2491
+ const ctx = await createAppContext();
2492
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : { status: "active" };
2493
+ const contexts = await ctx.repos.context.findAll(filter);
2494
+ if (contexts.length === 0) {
2495
+ console.log("No contexts.");
2496
+ return;
2497
+ }
2498
+ console.table(contexts.map(formatContextRow));
2499
+ });
2500
+ }
2501
+ function contextsUpdateCommand() {
2502
+ 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) => {
2503
+ const ctx = await createAppContext();
2504
+ const all = await ctx.repos.context.findAll();
2505
+ const id = resolveResourceIdByPrefix(all, prefix, "context");
2506
+ const patch = {};
2507
+ if (opts.name !== void 0) patch.name = opts.name;
2508
+ if (opts.clearDescription) patch.description = void 0;
2509
+ else if (opts.description !== void 0) patch.description = opts.description;
2510
+ if (opts.clearIcon) patch.icon = void 0;
2511
+ else if (opts.icon !== void 0) patch.icon = opts.icon;
2512
+ if (opts.clearColor) patch.color = void 0;
2513
+ else if (opts.color !== void 0) patch.color = opts.color;
2514
+ if (Object.keys(patch).length === 0) {
2515
+ throw new CliError(
2516
+ "No field to update. Specify at least one of --name / --description / --icon / --color / --clear-*."
2517
+ );
2518
+ }
2519
+ const updated = await ctx.repos.context.update(id, patch);
2520
+ console.log(`\u270E Updated context: ${shortenId(updated.id)} ${updated.name}`);
2521
+ });
2522
+ }
2523
+
2524
+ // src/commands/contexts/index.ts
2525
+ function contextsCommand() {
2526
+ 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());
2527
+ }
2528
+ function lockCommand() {
2529
+ return new Command("lock").description("keyring \u304B\u3089 E2EE passphrase \u3092\u524A\u9664\u3059\u308B (session \u306F\u7DAD\u6301)").action(() => {
2530
+ new KeyringPassphraseStore().clear();
2531
+ console.log("locked");
2532
+ });
2533
+ }
1952
2534
  function loginCommand() {
1953
2535
  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
2536
  const config = readSupabaseConfigFromEnv();
@@ -1981,68 +2563,129 @@ function logoutCommand() {
1981
2563
  console.log("logged out \u{1F44B}");
1982
2564
  });
1983
2565
  }
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()
2566
+ function projectsArchiveCommand() {
2567
+ 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) => {
2568
+ const ctx = await createAppContext();
2569
+ const all = await ctx.repos.project.findAll();
2570
+ const id = resolveResourceIdByPrefix(all, prefix, "project");
2571
+ const archived = await ctx.repos.project.archive(id);
2572
+ console.log(`\u{1F4E6} Archived project: ${shortenId(archived.id)} ${archived.name}`);
2009
2573
  });
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}`);
2574
+ }
2575
+ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
2576
+ function projectsCreateCommand() {
2577
+ 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) => {
2578
+ const ctx = await createAppContext();
2579
+ let parentProjectId;
2580
+ if (opts.parent !== void 0) {
2581
+ const all = await ctx.repos.project.findAll();
2582
+ parentProjectId = resolveResourceIdByPrefix(all, opts.parent, "project");
2031
2583
  }
2032
- throw err;
2033
- }
2034
- const cryptoSession = new CryptoSession();
2035
- cryptoSession.setMasterKey(masterKey, snapshot.currentKeyId);
2036
- const repos = buildRepositories(supabase, cryptoSession);
2584
+ if (opts.startedAt !== void 0 && !DATE_RE.test(opts.startedAt)) {
2585
+ throw new CliError(`--started-at must be YYYY-MM-DD (got "${opts.startedAt}").`);
2586
+ }
2587
+ if (opts.targetEnd !== void 0 && !DATE_RE.test(opts.targetEnd)) {
2588
+ throw new CliError(`--target-end must be YYYY-MM-DD (got "${opts.targetEnd}").`);
2589
+ }
2590
+ const input2 = { name, status: "active" };
2591
+ if (opts.description !== void 0) input2.description = opts.description;
2592
+ if (opts.color !== void 0) input2.color = opts.color;
2593
+ if (parentProjectId !== void 0) input2.parentProjectId = parentProjectId;
2594
+ if (opts.startedAt !== void 0) input2.startedAt = /* @__PURE__ */ new Date(`${opts.startedAt}T00:00:00Z`);
2595
+ if (opts.targetEnd !== void 0) input2.targetEndDate = /* @__PURE__ */ new Date(`${opts.targetEnd}T00:00:00Z`);
2596
+ const project = await ctx.repos.project.create(input2);
2597
+ console.log(`\u2713 Created project: ${shortenId(project.id)} ${project.name}`);
2598
+ });
2599
+ }
2600
+ function projectsDeleteCommand() {
2601
+ 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) => {
2602
+ const ctx = await createAppContext();
2603
+ const all = await ctx.repos.project.findAll();
2604
+ const id = resolveResourceIdByPrefix(all, prefix, "project");
2605
+ const target = all.find((p) => p.id === id);
2606
+ await ctx.repos.project.delete(id);
2607
+ console.log(`\u2717 Deleted project: ${shortenId(id)} ${target.name}`);
2608
+ });
2609
+ }
2610
+
2611
+ // src/format/project-row.ts
2612
+ function formatProjectRow(project) {
2037
2613
  return {
2038
- supabase,
2039
- cryptoSession,
2040
- repos,
2041
- userId: user.id,
2042
- email: user.email
2614
+ id: shortenId(project.id),
2615
+ name: project.name,
2616
+ status: project.status,
2617
+ color: project.color ?? "-",
2618
+ parent: project.parentProjectId ? shortenId(project.parentProjectId) : "-"
2043
2619
  };
2044
2620
  }
2045
2621
 
2622
+ // src/commands/projects/list.ts
2623
+ function projectsListCommand() {
2624
+ 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) => {
2625
+ const ctx = await createAppContext();
2626
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : { status: "active" };
2627
+ const projects = await ctx.repos.project.findAll(filter);
2628
+ if (projects.length === 0) {
2629
+ console.log("No projects.");
2630
+ return;
2631
+ }
2632
+ console.table(projects.map(formatProjectRow));
2633
+ });
2634
+ }
2635
+ var DATE_RE2 = /^\d{4}-\d{2}-\d{2}$/;
2636
+ function projectsUpdateCommand() {
2637
+ 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) => {
2638
+ const ctx = await createAppContext();
2639
+ const all = await ctx.repos.project.findAll();
2640
+ const id = resolveResourceIdByPrefix(all, prefix, "project");
2641
+ const patch = {};
2642
+ if (opts.name !== void 0) patch.name = opts.name;
2643
+ if (opts.clearDescription) patch.description = void 0;
2644
+ else if (opts.description !== void 0) patch.description = opts.description;
2645
+ if (opts.clearColor) patch.color = void 0;
2646
+ else if (opts.color !== void 0) patch.color = opts.color;
2647
+ if (opts.clearParent) patch.parentProjectId = void 0;
2648
+ else if (opts.parent !== void 0) {
2649
+ const parentId = resolveResourceIdByPrefix(all, opts.parent, "project");
2650
+ if (parentId === id) throw new CliError("A project cannot be its own parent.");
2651
+ patch.parentProjectId = parentId;
2652
+ }
2653
+ if (opts.clearStartedAt) patch.startedAt = void 0;
2654
+ else if (opts.startedAt !== void 0) {
2655
+ if (!DATE_RE2.test(opts.startedAt))
2656
+ throw new CliError(`--started-at must be YYYY-MM-DD (got "${opts.startedAt}").`);
2657
+ patch.startedAt = /* @__PURE__ */ new Date(`${opts.startedAt}T00:00:00Z`);
2658
+ }
2659
+ if (opts.clearTargetEnd) patch.targetEndDate = void 0;
2660
+ else if (opts.targetEnd !== void 0) {
2661
+ if (!DATE_RE2.test(opts.targetEnd))
2662
+ throw new CliError(`--target-end must be YYYY-MM-DD (got "${opts.targetEnd}").`);
2663
+ patch.targetEndDate = /* @__PURE__ */ new Date(`${opts.targetEnd}T00:00:00Z`);
2664
+ }
2665
+ if (Object.keys(patch).length === 0) {
2666
+ throw new CliError(
2667
+ "No field to update. Specify at least one of --name / --description / --color / --parent / --started-at / --target-end / --clear-*."
2668
+ );
2669
+ }
2670
+ const updated = await ctx.repos.project.update(id, patch);
2671
+ console.log(`\u270E Updated project: ${shortenId(updated.id)} ${updated.name}`);
2672
+ });
2673
+ }
2674
+
2675
+ // src/commands/projects/index.ts
2676
+ function projectsCommand() {
2677
+ 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());
2678
+ }
2679
+ function routinesArchiveCommand() {
2680
+ 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) => {
2681
+ const ctx = await createAppContext();
2682
+ const all = await ctx.repos.routine.findAll();
2683
+ const id = resolveResourceIdByPrefix(all, prefix, "routine");
2684
+ const archived = await ctx.repos.routine.archive(id);
2685
+ console.log(`\u{1F4E6} Archived routine: ${shortenId(archived.id)} ${archived.title}`);
2686
+ });
2687
+ }
2688
+
2046
2689
  // src/format/parse-estimated.ts
2047
2690
  var HOURS_HEAD = /^(\d{1,4})h/;
2048
2691
  var MINUTES_TAIL = /^(\d{1,4})m$/;
@@ -2070,33 +2713,360 @@ function parseEstimated(input2) {
2070
2713
  return (hours * 60 + minutes) * 60;
2071
2714
  }
2072
2715
 
2073
- // src/format/task-row.ts
2074
- function shortenId(id, length = 8) {
2075
- return id.slice(0, length);
2716
+ // src/format/parse-frequency.ts
2717
+ function parseFrequencySpec(spec) {
2718
+ if (spec === "daily") return { type: "daily" };
2719
+ const [typeKey, rest] = spec.split(":");
2720
+ if (!typeKey || rest === void 0) {
2721
+ throw new CliError(
2722
+ `Invalid --frequency "${spec}". Use one of: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING`
2723
+ );
2724
+ }
2725
+ if (typeKey === "weekly") {
2726
+ const weekdays = parseNumberList(rest, 0, 6);
2727
+ if (weekdays.length === 0) {
2728
+ throw new CliError("weekly frequency must have at least 1 weekday.");
2729
+ }
2730
+ return { type: "weekly", weekdays };
2731
+ }
2732
+ if (typeKey === "monthly") {
2733
+ const days = parseNumberList(rest, 1, 31);
2734
+ if (days.length === 0) {
2735
+ throw new CliError("monthly frequency must have at least 1 day.");
2736
+ }
2737
+ return { type: "monthly", days };
2738
+ }
2739
+ if (typeKey === "custom") {
2740
+ if (rest.length === 0) throw new CliError("custom frequency requires a cron string.");
2741
+ return { type: "custom", cron: rest };
2742
+ }
2743
+ throw new CliError(
2744
+ `Unknown frequency type "${typeKey}". Use one of: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING`
2745
+ );
2076
2746
  }
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`;
2747
+ function parseNumberList(csv, min, max) {
2748
+ const parts = csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
2749
+ return parts.map((s) => {
2750
+ const n = Number(s);
2751
+ if (!Number.isInteger(n) || n < min || n > max) {
2752
+ throw new CliError(`Invalid number "${s}" (must be integer in [${min}, ${max}]).`);
2753
+ }
2754
+ return n;
2755
+ });
2083
2756
  }
2084
- function formatTaskRow(task) {
2757
+ function parseWeekdaysList(csv) {
2758
+ const result = parseNumberList(csv, 0, 6);
2759
+ if (result.length === 0) throw new CliError("--weekdays must have at least 1 day.");
2760
+ return result;
2761
+ }
2762
+
2763
+ // src/commands/routines/create.ts
2764
+ var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
2765
+ var DATE_RE3 = /^\d{4}-\d{2}-\d{2}$/;
2766
+ function routinesCreateCommand() {
2767
+ 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(
2768
+ "--frequency <spec>",
2769
+ "\u983B\u5EA6: daily / weekly:1,3,5 / monthly:1,15 / custom:CRON_STRING"
2770
+ ).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) => {
2771
+ const ctx = await createAppContext();
2772
+ const estimatedSeconds = parseEstimated(opts.estimated);
2773
+ const frequency = parseFrequencySpec(opts.frequency);
2774
+ if (opts.scheduled !== void 0 && !HHMM.test(opts.scheduled)) {
2775
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
2776
+ }
2777
+ if (opts.startDate !== void 0 && !DATE_RE3.test(opts.startDate)) {
2778
+ throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
2779
+ }
2780
+ if (opts.endDate !== void 0 && !DATE_RE3.test(opts.endDate)) {
2781
+ throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
2782
+ }
2783
+ let projectId;
2784
+ if (opts.project !== void 0) {
2785
+ const all = await ctx.repos.project.findAll();
2786
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
2787
+ }
2788
+ let contextId;
2789
+ if (opts.context !== void 0) {
2790
+ const all = await ctx.repos.context.findAll();
2791
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
2792
+ }
2793
+ const tagIds = [];
2794
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
2795
+ const allTags = await ctx.repos.tag.findAll();
2796
+ for (const prefix of opts.tag) {
2797
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
2798
+ }
2799
+ }
2800
+ const input2 = {
2801
+ title,
2802
+ estimatedSeconds,
2803
+ tagIds,
2804
+ frequency,
2805
+ autoExpand: opts.noAutoExpand !== true,
2806
+ status: "active"
2807
+ };
2808
+ if (opts.description !== void 0) input2.description = opts.description;
2809
+ if (projectId !== void 0) input2.projectId = projectId;
2810
+ if (contextId !== void 0) input2.contextId = contextId;
2811
+ if (opts.scheduled !== void 0) input2.defaultScheduledStartTime = opts.scheduled;
2812
+ if (opts.startDate !== void 0) input2.startDate = opts.startDate;
2813
+ if (opts.endDate !== void 0) input2.endDate = opts.endDate;
2814
+ const routine = await ctx.repos.routine.create(input2);
2815
+ console.log(`\u2713 Created routine: ${shortenId(routine.id)} ${routine.title}`);
2816
+ });
2817
+ }
2818
+ function collectTag(value, prev) {
2819
+ return [...prev, value];
2820
+ }
2821
+ function routinesDeleteCommand() {
2822
+ 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) => {
2823
+ const ctx = await createAppContext();
2824
+ const all = await ctx.repos.routine.findAll();
2825
+ const id = resolveResourceIdByPrefix(all, prefix, "routine");
2826
+ const target = all.find((r) => r.id === id);
2827
+ await ctx.repos.routine.delete(id);
2828
+ console.log(`\u2717 Deleted routine: ${shortenId(id)} ${target.title}`);
2829
+ });
2830
+ }
2831
+
2832
+ // src/format/routine-row.ts
2833
+ function formatFrequency(freq) {
2834
+ switch (freq.type) {
2835
+ case "daily":
2836
+ return "daily";
2837
+ case "weekly":
2838
+ return `weekly:${freq.weekdays.join(",")}`;
2839
+ case "monthly":
2840
+ return `monthly:${freq.days.join(",")}`;
2841
+ case "custom":
2842
+ return `custom:${freq.cron}`;
2843
+ }
2844
+ }
2845
+ function formatRoutineRow(routine) {
2085
2846
  return {
2086
- id: shortenId(task.id),
2087
- status: task.status,
2088
- scheduled: task.scheduledStartTime ?? "-",
2089
- estimated: formatEstimated(task.estimatedSeconds),
2090
- title: task.title
2847
+ id: shortenId(routine.id),
2848
+ title: routine.title,
2849
+ status: routine.status,
2850
+ frequency: formatFrequency(routine.frequency),
2851
+ estimated: formatEstimated(routine.estimatedSeconds),
2852
+ auto: routine.autoExpand ? "auto" : "-"
2853
+ };
2854
+ }
2855
+
2856
+ // src/commands/routines/list.ts
2857
+ function routinesListCommand() {
2858
+ 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) => {
2859
+ const ctx = await createAppContext();
2860
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : opts.paused ? { status: "paused" } : { status: "active" };
2861
+ const routines = await ctx.repos.routine.findAll(filter);
2862
+ if (routines.length === 0) {
2863
+ console.log("No routines.");
2864
+ return;
2865
+ }
2866
+ console.table(routines.map(formatRoutineRow));
2867
+ });
2868
+ }
2869
+ var HHMM2 = /^([01]\d|2[0-3]):[0-5]\d$/;
2870
+ var DATE_RE4 = /^\d{4}-\d{2}-\d{2}$/;
2871
+ var ROUTINE_STATUSES = ["active", "paused", "archived"];
2872
+ function isRoutineStatus(value) {
2873
+ return ROUTINE_STATUSES.includes(value);
2874
+ }
2875
+ function routinesUpdateCommand() {
2876
+ 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) => {
2877
+ const ctx = await createAppContext();
2878
+ const all = await ctx.repos.routine.findAll();
2879
+ const id = resolveResourceIdByPrefix(all, prefix, "routine");
2880
+ if (opts.scheduled !== void 0 && !HHMM2.test(opts.scheduled)) {
2881
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
2882
+ }
2883
+ if (opts.startDate !== void 0 && !DATE_RE4.test(opts.startDate)) {
2884
+ throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
2885
+ }
2886
+ if (opts.endDate !== void 0 && !DATE_RE4.test(opts.endDate)) {
2887
+ throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
2888
+ }
2889
+ if (opts.status !== void 0 && !isRoutineStatus(opts.status)) {
2890
+ throw new CliError(
2891
+ `--status must be one of ${ROUTINE_STATUSES.join(" / ")} (got "${opts.status}").`
2892
+ );
2893
+ }
2894
+ const patch = {};
2895
+ if (opts.title !== void 0) patch.title = opts.title;
2896
+ if (opts.clearDescription) patch.description = void 0;
2897
+ else if (opts.description !== void 0) patch.description = opts.description;
2898
+ if (opts.estimated !== void 0) patch.estimatedSeconds = parseEstimated(opts.estimated);
2899
+ if (opts.frequency !== void 0) patch.frequency = parseFrequencySpec(opts.frequency);
2900
+ if (opts.clearProject) patch.projectId = void 0;
2901
+ else if (opts.project !== void 0) {
2902
+ const projects = await ctx.repos.project.findAll();
2903
+ patch.projectId = resolveResourceIdByPrefix(projects, opts.project, "project");
2904
+ }
2905
+ if (opts.clearContext) patch.contextId = void 0;
2906
+ else if (opts.context !== void 0) {
2907
+ const contexts = await ctx.repos.context.findAll();
2908
+ patch.contextId = resolveResourceIdByPrefix(contexts, opts.context, "context");
2909
+ }
2910
+ if (opts.noTags) patch.tagIds = [];
2911
+ else if (opts.tag !== void 0 && opts.tag.length > 0) {
2912
+ const tags = await ctx.repos.tag.findAll();
2913
+ const resolved = [];
2914
+ for (const p of opts.tag) {
2915
+ resolved.push(resolveResourceIdByPrefix(tags, p, "tag"));
2916
+ }
2917
+ patch.tagIds = resolved;
2918
+ }
2919
+ if (opts.clearScheduled) patch.defaultScheduledStartTime = void 0;
2920
+ else if (opts.scheduled !== void 0) patch.defaultScheduledStartTime = opts.scheduled;
2921
+ if (opts.autoExpand !== void 0) patch.autoExpand = opts.autoExpand;
2922
+ if (opts.clearStartDate) patch.startDate = void 0;
2923
+ else if (opts.startDate !== void 0) patch.startDate = opts.startDate;
2924
+ if (opts.clearEndDate) patch.endDate = void 0;
2925
+ else if (opts.endDate !== void 0) patch.endDate = opts.endDate;
2926
+ if (opts.status !== void 0 && isRoutineStatus(opts.status)) {
2927
+ patch.status = opts.status;
2928
+ }
2929
+ if (Object.keys(patch).length === 0) {
2930
+ throw new CliError(
2931
+ "No field to update. Specify at least one of --title / --estimated / --frequency / --project / --context / --tag / --scheduled / --auto-expand / --start-date / --end-date / --status / --clear-*."
2932
+ );
2933
+ }
2934
+ const updated = await ctx.repos.routine.update(id, patch);
2935
+ const today = todayDateString();
2936
+ const futureTasks = await ctx.repos.task.findByRoutineIdFromDate(id, today);
2937
+ const targets = pickPropagationTargets(updated, futureTasks, today);
2938
+ let propagated = 0;
2939
+ if (targets.length > 0) {
2940
+ const { auditingTask } = ctx.repos;
2941
+ if (!auditingTask) {
2942
+ throw new CliError(
2943
+ "auditingTask is not configured. Routine propagation requires audit lookups (this should not happen with createNodeAppContext)."
2944
+ );
2945
+ }
2946
+ const taskPatch = buildRoutineTaskPatch(updated);
2947
+ await Promise.all(
2948
+ targets.map(
2949
+ (task) => auditingTask.updateWithCause(task.id, taskPatch, "routine-propagation")
2950
+ )
2951
+ );
2952
+ propagated = targets.length;
2953
+ }
2954
+ const propagatedNote = propagated > 0 ? ` (propagated to ${propagated} future task(s))` : "";
2955
+ console.log(`\u270E Updated routine: ${shortenId(updated.id)} ${updated.title}${propagatedNote}`);
2956
+ });
2957
+ }
2958
+ function collectTag2(value, prev) {
2959
+ return [...prev, value];
2960
+ }
2961
+
2962
+ // src/commands/routines/index.ts
2963
+ function routinesCommand() {
2964
+ 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());
2965
+ }
2966
+ function tagsArchiveCommand() {
2967
+ 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) => {
2968
+ const ctx = await createAppContext();
2969
+ const all = await ctx.repos.tag.findAll();
2970
+ const id = resolveResourceIdByPrefix(all, prefix, "tag");
2971
+ const archived = await ctx.repos.tag.archive(id);
2972
+ console.log(`\u{1F4E6} Archived tag: ${shortenId(archived.id)} ${archived.name}`);
2973
+ });
2974
+ }
2975
+ function tagsCreateCommand() {
2976
+ 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) => {
2977
+ const ctx = await createAppContext();
2978
+ const input2 = { name, status: "active" };
2979
+ if (opts.description !== void 0) input2.description = opts.description;
2980
+ if (opts.color !== void 0) input2.color = opts.color;
2981
+ const tag = await ctx.repos.tag.create(input2);
2982
+ console.log(`\u2713 Created tag: ${shortenId(tag.id)} ${tag.name}`);
2983
+ });
2984
+ }
2985
+ function tagsDeleteCommand() {
2986
+ 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) => {
2987
+ const ctx = await createAppContext();
2988
+ const all = await ctx.repos.tag.findAll();
2989
+ const id = resolveResourceIdByPrefix(all, prefix, "tag");
2990
+ const target = all.find((t) => t.id === id);
2991
+ await ctx.repos.tag.delete(id);
2992
+ console.log(`\u2717 Deleted tag: ${shortenId(id)} ${target.name}`);
2993
+ });
2994
+ }
2995
+
2996
+ // src/format/tag-row.ts
2997
+ function formatTagRow(tag) {
2998
+ return {
2999
+ id: shortenId(tag.id),
3000
+ name: tag.name,
3001
+ status: tag.status,
3002
+ color: tag.color ?? "-"
2091
3003
  };
2092
3004
  }
2093
3005
 
2094
- // src/commands/tasks/add.ts
3006
+ // src/commands/tags/list.ts
3007
+ function tagsListCommand() {
3008
+ 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) => {
3009
+ const ctx = await createAppContext();
3010
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : { status: "active" };
3011
+ const tags = await ctx.repos.tag.findAll(filter);
3012
+ if (tags.length === 0) {
3013
+ console.log("No tags.");
3014
+ return;
3015
+ }
3016
+ console.table(tags.map(formatTagRow));
3017
+ });
3018
+ }
3019
+ function tagsUpdateCommand() {
3020
+ 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) => {
3021
+ const ctx = await createAppContext();
3022
+ const all = await ctx.repos.tag.findAll();
3023
+ const id = resolveResourceIdByPrefix(all, prefix, "tag");
3024
+ const patch = {};
3025
+ if (opts.name !== void 0) patch.name = opts.name;
3026
+ if (opts.clearDescription) patch.description = void 0;
3027
+ else if (opts.description !== void 0) patch.description = opts.description;
3028
+ if (opts.clearColor) patch.color = void 0;
3029
+ else if (opts.color !== void 0) patch.color = opts.color;
3030
+ if (Object.keys(patch).length === 0) {
3031
+ throw new CliError(
3032
+ "No field to update. Specify at least one of --name / --description / --color / --clear-*."
3033
+ );
3034
+ }
3035
+ const updated = await ctx.repos.tag.update(id, patch);
3036
+ console.log(`\u270E Updated tag: ${shortenId(updated.id)} ${updated.name}`);
3037
+ });
3038
+ }
3039
+
3040
+ // src/commands/tags/index.ts
3041
+ function tagsCommand() {
3042
+ return new Command("tags").description("\u30BF\u30B0\u306E\u4E00\u89A7/\u8FFD\u52A0/\u64CD\u4F5C").addCommand(tagsListCommand()).addCommand(tagsCreateCommand()).addCommand(tagsUpdateCommand()).addCommand(tagsArchiveCommand()).addCommand(tagsDeleteCommand());
3043
+ }
3044
+ var TIME_RE2 = /^([01]\d|2[0-3]):[0-5]\d$/;
2095
3045
  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) => {
3046
+ 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
3047
  const ctx = await createAppContext();
2098
3048
  const date = opts.date ?? todayDateString();
2099
3049
  const estimatedSeconds = opts.estimated ? parseEstimated(opts.estimated) : 30 * 60;
3050
+ let projectId;
3051
+ if (opts.project !== void 0) {
3052
+ const all = await ctx.repos.project.findAll();
3053
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3054
+ }
3055
+ let contextId;
3056
+ if (opts.context !== void 0) {
3057
+ const all = await ctx.repos.context.findAll();
3058
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3059
+ }
3060
+ const tagIds = [];
3061
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
3062
+ const allTags = await ctx.repos.tag.findAll();
3063
+ for (const prefix of opts.tag) {
3064
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3065
+ }
3066
+ }
3067
+ if (opts.scheduled !== void 0 && !TIME_RE2.test(opts.scheduled)) {
3068
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
3069
+ }
2100
3070
  const existing = await ctx.repos.task.findByDate(date);
2101
3071
  const orderIndex = computeNextOrderIndex(existing);
2102
3072
  const task = await ctx.repos.task.create({
@@ -2107,18 +3077,26 @@ function tasksAddCommand() {
2107
3077
  sessions: [],
2108
3078
  status: "planned",
2109
3079
  orderIndex,
2110
- tagIds: []
3080
+ tagIds,
3081
+ projectId,
3082
+ contextId,
3083
+ scheduledStartTime: opts.scheduled,
3084
+ notifyAtScheduledStart: opts.notify,
3085
+ isBackground: opts.background
2111
3086
  });
2112
3087
  console.log(`\u2713 Created: ${shortenId(task.id)} ${task.title}`);
2113
3088
  });
2114
3089
  }
3090
+ function collectTag3(value, prev) {
3091
+ return [...prev, value];
3092
+ }
2115
3093
 
2116
3094
  // src/resolve-task-id.ts
2117
- var MIN_PREFIX_LENGTH = 4;
3095
+ var MIN_PREFIX_LENGTH2 = 4;
2118
3096
  function resolveTaskIdByPrefix(tasks, prefix) {
2119
- if (prefix.length < MIN_PREFIX_LENGTH) {
3097
+ if (prefix.length < MIN_PREFIX_LENGTH2) {
2120
3098
  throw new CliError(
2121
- `Task id prefix must be at least ${MIN_PREFIX_LENGTH} chars to avoid ambiguity.`
3099
+ `Task id prefix must be at least ${MIN_PREFIX_LENGTH2} chars to avoid ambiguity.`
2122
3100
  );
2123
3101
  }
2124
3102
  const matches = tasks.filter((t) => t.id.startsWith(prefix));
@@ -2133,7 +3111,22 @@ ${candidates}`);
2133
3111
  return matches[0].id;
2134
3112
  }
2135
3113
 
2136
- // src/commands/tasks/done.ts
3114
+ // src/commands/tasks/delete.ts
3115
+ function tasksDeleteCommand() {
3116
+ 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) => {
3117
+ const ctx = await createAppContext();
3118
+ const date = opts.date ?? todayDateString();
3119
+ const tasks = await ctx.repos.task.findByDate(date);
3120
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3121
+ const target = tasks.find((t) => t.id === taskId);
3122
+ if (opts.hard) {
3123
+ await ctx.repos.task.delete(taskId);
3124
+ } else {
3125
+ await ctx.repos.task.deleteWithRoutineSkip(taskId, todayDateString());
3126
+ }
3127
+ console.log(`\u2717 Deleted: ${shortenId(taskId)} ${target.title}`);
3128
+ });
3129
+ }
2137
3130
  function tasksDoneCommand() {
2138
3131
  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
3132
  const ctx = await createAppContext();
@@ -2144,6 +3137,52 @@ function tasksDoneCommand() {
2144
3137
  console.log(`\u2713 Done: ${shortenId(updated.id)} ${updated.title}`);
2145
3138
  });
2146
3139
  }
3140
+ function tasksInterruptCommand() {
3141
+ 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) => {
3142
+ const ctx = await createAppContext();
3143
+ const date = opts.date ?? todayDateString();
3144
+ const tasks = await ctx.repos.task.findByDate(date);
3145
+ const targetTaskId = resolveTaskIdByPrefix(tasks, targetPrefix);
3146
+ const estimatedSeconds = parseEstimated(opts.estimated);
3147
+ let projectId;
3148
+ if (opts.project !== void 0) {
3149
+ const all = await ctx.repos.project.findAll();
3150
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3151
+ }
3152
+ let contextId;
3153
+ if (opts.context !== void 0) {
3154
+ const all = await ctx.repos.context.findAll();
3155
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3156
+ }
3157
+ const tagIds = [];
3158
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
3159
+ const allTags = await ctx.repos.tag.findAll();
3160
+ for (const prefix of opts.tag) {
3161
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3162
+ }
3163
+ }
3164
+ const result = await wrapDomainErrors(
3165
+ () => interruptTaskWithAudit(ctx.repos.task, ctx.repos.taskNote, {
3166
+ targetTaskId,
3167
+ interruption: {
3168
+ title,
3169
+ estimatedSeconds,
3170
+ ...opts.description !== void 0 && { description: opts.description },
3171
+ ...projectId !== void 0 && { projectId },
3172
+ ...contextId !== void 0 && { contextId },
3173
+ ...tagIds.length > 0 && { tagIds }
3174
+ }
3175
+ })
3176
+ );
3177
+ console.log(`\u26A1 Interrupted:`);
3178
+ console.log(` \u2713 finished: ${shortenId(result.finishedOriginal.id)} ${result.finishedOriginal.title}`);
3179
+ console.log(` \u25B6 interruption: ${shortenId(result.interruption.id)} ${result.interruption.title}`);
3180
+ console.log(` \u23F1 continuation: ${shortenId(result.continuation.id)} ${result.continuation.title}`);
3181
+ });
3182
+ }
3183
+ function collectTag4(value, prev) {
3184
+ return [...prev, value];
3185
+ }
2147
3186
  function tasksListCommand() {
2148
3187
  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
3188
  const ctx = await createAppContext();
@@ -2157,6 +3196,50 @@ function tasksListCommand() {
2157
3196
  console.table(tasks.map(formatTaskRow));
2158
3197
  });
2159
3198
  }
3199
+ function tasksParallelCommand() {
3200
+ 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) => {
3201
+ const ctx = await createAppContext();
3202
+ const date = opts.date ?? todayDateString();
3203
+ const estimatedSeconds = parseEstimated(opts.estimated);
3204
+ let projectId;
3205
+ if (opts.project !== void 0) {
3206
+ const all = await ctx.repos.project.findAll();
3207
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3208
+ }
3209
+ let contextId;
3210
+ if (opts.context !== void 0) {
3211
+ const all = await ctx.repos.context.findAll();
3212
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3213
+ }
3214
+ const tagIds = [];
3215
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
3216
+ const allTags = await ctx.repos.tag.findAll();
3217
+ for (const prefix of opts.tag) {
3218
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3219
+ }
3220
+ }
3221
+ const task = await wrapDomainErrors(
3222
+ () => addParallelTask(ctx.repos.task, {
3223
+ date,
3224
+ title,
3225
+ estimatedSeconds,
3226
+ ...opts.description !== void 0 && { description: opts.description },
3227
+ ...projectId !== void 0 && { projectId },
3228
+ ...contextId !== void 0 && { contextId },
3229
+ ...tagIds.length > 0 && { tagIds },
3230
+ ...opts.background !== void 0 && { isBackground: opts.background },
3231
+ ...opts.startImmediately !== void 0 && {
3232
+ startImmediately: opts.startImmediately
3233
+ }
3234
+ })
3235
+ );
3236
+ const icon = task.status === "running" ? "\u25B6" : "+";
3237
+ console.log(`${icon} Parallel task: ${shortenId(task.id)} ${task.title} (status=${task.status})`);
3238
+ });
3239
+ }
3240
+ function collectTag5(value, prev) {
3241
+ return [...prev, value];
3242
+ }
2160
3243
  function tasksPauseCommand() {
2161
3244
  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
3245
  const ctx = await createAppContext();
@@ -2167,6 +3250,118 @@ function tasksPauseCommand() {
2167
3250
  console.log(`\u23F8 Paused: ${shortenId(updated.id)} ${updated.title}`);
2168
3251
  });
2169
3252
  }
3253
+ function tasksRecentCommand() {
3254
+ 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) => {
3255
+ const ctx = await createAppContext();
3256
+ const days = opts.days ? Number(opts.days) : 90;
3257
+ const limit = opts.limit ? Number(opts.limit) : 100;
3258
+ const fromDate = (() => {
3259
+ const d = fromDateString(todayDateString());
3260
+ d.setDate(d.getDate() - days);
3261
+ return toDateString(d);
3262
+ })();
3263
+ const all = await ctx.repos.task.findFromDate(fromDate);
3264
+ const ordered = opts.query ? findSimilarTasks(opts.query, all, limit) : [...all].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
3265
+ const display = ordered.slice(0, limit);
3266
+ if (display.length === 0) {
3267
+ console.log("No recent tasks.");
3268
+ return;
3269
+ }
3270
+ console.table(display.map(formatTaskRow));
3271
+ if (ordered.length > limit) {
3272
+ console.log(`(${ordered.length - limit} more, use --limit to see them)`);
3273
+ }
3274
+ });
3275
+ }
3276
+ function tasksReopenCommand() {
3277
+ 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) => {
3278
+ const ctx = await createAppContext();
3279
+ const date = opts.date ?? todayDateString();
3280
+ const tasks = await ctx.repos.task.findByDate(date);
3281
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3282
+ const updated = await reopenTask(ctx.repos.task, taskId);
3283
+ console.log(`\u21BB Reopened: ${shortenId(updated.id)} ${updated.title}`);
3284
+ });
3285
+ }
3286
+ var HHMM3 = /^([01]\d|2[0-3]):[0-5]\d$/;
3287
+ function tasksReorderCommand() {
3288
+ 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) => {
3289
+ const ctx = await createAppContext();
3290
+ const date = opts.date ?? todayDateString();
3291
+ const tasks = await ctx.repos.task.findByDate(date);
3292
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3293
+ const orderIndex = Number(opts.orderIndex);
3294
+ if (!Number.isInteger(orderIndex)) {
3295
+ throw new CliError(`--order-index must be an integer (got "${opts.orderIndex}").`);
3296
+ }
3297
+ if (opts.scheduled !== void 0 && !HHMM3.test(opts.scheduled)) {
3298
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
3299
+ }
3300
+ const patch = { orderIndex };
3301
+ if (opts.clearScheduled) patch.scheduledStartTime = void 0;
3302
+ else if (opts.scheduled !== void 0) patch.scheduledStartTime = opts.scheduled;
3303
+ const updated = await ctx.repos.task.update(taskId, patch);
3304
+ console.log(`\u2195 Reordered: ${shortenId(updated.id)} ${updated.title} (order_index=${updated.orderIndex})`);
3305
+ });
3306
+ }
3307
+ var HHMM4 = /^([01]\d|2[0-3]):[0-5]\d$/;
3308
+ function tasksSetActualTimesCommand() {
3309
+ 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>", "\u5B9F\u7E3E\u958B\u59CB\u6642\u523B").option("--end <HH:MM>", "\u5B9F\u7E3E\u7D42\u4E86\u6642\u523B").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) => {
3310
+ const ctx = await createAppContext();
3311
+ const date = opts.date ?? todayDateString();
3312
+ const tasks = await ctx.repos.task.findByDate(date);
3313
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3314
+ const task = tasks.find((t) => t.id === taskId);
3315
+ if (opts.start !== void 0 && !HHMM4.test(opts.start)) {
3316
+ throw new CliError(`--start must be HH:MM (got "${opts.start}").`);
3317
+ }
3318
+ if (opts.end !== void 0 && !HHMM4.test(opts.end)) {
3319
+ throw new CliError(`--end must be HH:MM (got "${opts.end}").`);
3320
+ }
3321
+ let startTime;
3322
+ let endTime;
3323
+ if (opts.reset || opts.clearStart) {
3324
+ startTime = null;
3325
+ endTime = null;
3326
+ } else if (opts.start !== void 0) {
3327
+ startTime = opts.start;
3328
+ endTime = opts.clearEnd ? null : opts.end ?? null;
3329
+ } else if (opts.clearEnd) {
3330
+ if (!task.actualStartTime) {
3331
+ throw new CliError(`--clear-end requires the task to already have an actual start time.`);
3332
+ }
3333
+ startTime = task.actualStartTime;
3334
+ endTime = null;
3335
+ } else if (opts.end !== void 0) {
3336
+ if (!task.actualStartTime) {
3337
+ throw new CliError(
3338
+ "--end alone requires the task to already have an actual start time (set --start as well)."
3339
+ );
3340
+ }
3341
+ startTime = task.actualStartTime;
3342
+ endTime = opts.end;
3343
+ } else {
3344
+ throw new CliError(
3345
+ "No actual-times change. Specify at least one of --start / --end / --clear-start / --clear-end / --reset."
3346
+ );
3347
+ }
3348
+ const patch = buildSetActualTimesPatch(task, { startTime, endTime });
3349
+ const updated = await ctx.repos.task.update(taskId, patch);
3350
+ console.log(
3351
+ `\u23F1 Set actual times: ${shortenId(updated.id)} ${updated.title} (status=${updated.status})`
3352
+ );
3353
+ });
3354
+ }
3355
+ function tasksSkipCommand() {
3356
+ 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) => {
3357
+ const ctx = await createAppContext();
3358
+ const date = opts.date ?? todayDateString();
3359
+ const tasks = await ctx.repos.task.findByDate(date);
3360
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3361
+ const updated = await skipTask(ctx.repos.task, taskId);
3362
+ console.log(`\u293C Skipped: ${shortenId(updated.id)} ${updated.title}`);
3363
+ });
3364
+ }
2170
3365
  function tasksStartCommand() {
2171
3366
  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
3367
  const ctx = await createAppContext();
@@ -2177,10 +3372,265 @@ function tasksStartCommand() {
2177
3372
  console.log(`\u25B6 Started: ${shortenId(updated.id)} ${updated.title}`);
2178
3373
  });
2179
3374
  }
3375
+ var TIME_RE3 = /^([01]\d|2[0-3]):[0-5]\d$/;
3376
+ async function buildPatch(ctx, opts) {
3377
+ const patch = {};
3378
+ if (opts.title !== void 0) patch.title = opts.title;
3379
+ if (opts.clearDescription) {
3380
+ patch.description = void 0;
3381
+ } else if (opts.description !== void 0) {
3382
+ patch.description = opts.description;
3383
+ }
3384
+ if (opts.estimated !== void 0) {
3385
+ patch.estimatedSeconds = parseEstimated(opts.estimated);
3386
+ }
3387
+ if (opts.clearProject) {
3388
+ patch.projectId = void 0;
3389
+ } else if (opts.project !== void 0) {
3390
+ const all = await ctx.repos.project.findAll();
3391
+ patch.projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3392
+ }
3393
+ if (opts.clearContext) {
3394
+ patch.contextId = void 0;
3395
+ } else if (opts.context !== void 0) {
3396
+ const all = await ctx.repos.context.findAll();
3397
+ patch.contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3398
+ }
3399
+ if (opts.noTags) {
3400
+ patch.tagIds = [];
3401
+ } else if (opts.tag !== void 0 && opts.tag.length > 0) {
3402
+ const allTags = await ctx.repos.tag.findAll();
3403
+ const resolvedIds = [];
3404
+ for (const prefix of opts.tag) {
3405
+ resolvedIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3406
+ }
3407
+ patch.tagIds = resolvedIds;
3408
+ }
3409
+ if (opts.clearScheduled) {
3410
+ patch.scheduledStartTime = void 0;
3411
+ } else if (opts.scheduled !== void 0) {
3412
+ if (!TIME_RE3.test(opts.scheduled)) {
3413
+ throw new CliError(`--scheduled must be HH:MM (got "${opts.scheduled}").`);
3414
+ }
3415
+ patch.scheduledStartTime = opts.scheduled;
3416
+ }
3417
+ if (opts.notify !== void 0) patch.notifyAtScheduledStart = opts.notify;
3418
+ if (opts.background !== void 0) patch.isBackground = opts.background;
3419
+ if (Object.keys(patch).length === 0) {
3420
+ throw new CliError(
3421
+ "No field to update. Specify at least one of --title / --description / --estimated / --project / --context / --tag / --scheduled / --notify / --background / --clear-*."
3422
+ );
3423
+ }
3424
+ return patch;
3425
+ }
3426
+ function tasksUpdateCommand() {
3427
+ 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) => {
3428
+ const ctx = await createAppContext();
3429
+ const date = opts.date ?? todayDateString();
3430
+ const tasks = await ctx.repos.task.findByDate(date);
3431
+ const taskId = resolveTaskIdByPrefix(tasks, prefix);
3432
+ const patch = await buildPatch(ctx, opts);
3433
+ const updated = await ctx.repos.task.update(taskId, patch);
3434
+ console.log(`\u270E Updated: ${shortenId(updated.id)} ${updated.title}`);
3435
+ });
3436
+ }
3437
+ function collectTag6(value, prev) {
3438
+ return [...prev, value];
3439
+ }
2180
3440
 
2181
3441
  // src/commands/tasks/index.ts
2182
3442
  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());
3443
+ 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());
3444
+ }
3445
+ function timeBlocksArchiveCommand() {
3446
+ 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) => {
3447
+ const ctx = await createAppContext();
3448
+ const all = await ctx.repos.timeBlock.findAll();
3449
+ const id = resolveResourceIdByPrefix(all, prefix, "time-block");
3450
+ const archived = await ctx.repos.timeBlock.archive(id);
3451
+ console.log(`\u{1F4E6} Archived time-block: ${shortenId(archived.id)} ${archived.name}`);
3452
+ });
3453
+ }
3454
+ var HHMM5 = /^([01]\d|2[0-3]):[0-5]\d$/;
3455
+ var DATE_RE5 = /^\d{4}-\d{2}-\d{2}$/;
3456
+ function timeBlocksCreateCommand() {
3457
+ 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) => {
3458
+ const ctx = await createAppContext();
3459
+ if (!HHMM5.test(opts.startTime)) {
3460
+ throw new CliError(`--start-time must be HH:MM (got "${opts.startTime}").`);
3461
+ }
3462
+ if (!HHMM5.test(opts.endTime)) {
3463
+ throw new CliError(`--end-time must be HH:MM (got "${opts.endTime}").`);
3464
+ }
3465
+ const weekdays = parseWeekdaysList(opts.weekdays);
3466
+ if (opts.startDate !== void 0 && !DATE_RE5.test(opts.startDate)) {
3467
+ throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
3468
+ }
3469
+ if (opts.endDate !== void 0 && !DATE_RE5.test(opts.endDate)) {
3470
+ throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
3471
+ }
3472
+ let projectId;
3473
+ if (opts.project !== void 0) {
3474
+ const all = await ctx.repos.project.findAll();
3475
+ projectId = resolveResourceIdByPrefix(all, opts.project, "project");
3476
+ }
3477
+ let contextId;
3478
+ if (opts.context !== void 0) {
3479
+ const all = await ctx.repos.context.findAll();
3480
+ contextId = resolveResourceIdByPrefix(all, opts.context, "context");
3481
+ }
3482
+ const tagIds = [];
3483
+ if (opts.tag !== void 0 && opts.tag.length > 0) {
3484
+ const allTags = await ctx.repos.tag.findAll();
3485
+ for (const prefix of opts.tag) {
3486
+ tagIds.push(resolveResourceIdByPrefix(allTags, prefix, "tag"));
3487
+ }
3488
+ }
3489
+ const input2 = {
3490
+ name,
3491
+ startTime: opts.startTime,
3492
+ endTime: opts.endTime,
3493
+ weekdays,
3494
+ status: "active"
3495
+ };
3496
+ if (projectId !== void 0) input2.defaultProjectId = projectId;
3497
+ if (contextId !== void 0) input2.defaultContextId = contextId;
3498
+ if (tagIds.length > 0) input2.defaultTagIds = tagIds;
3499
+ if (opts.color !== void 0) input2.color = opts.color;
3500
+ if (opts.startDate !== void 0) input2.startDate = opts.startDate;
3501
+ if (opts.endDate !== void 0) input2.endDate = opts.endDate;
3502
+ const block = await ctx.repos.timeBlock.create(input2);
3503
+ console.log(`\u2713 Created time-block: ${shortenId(block.id)} ${block.name}`);
3504
+ });
3505
+ }
3506
+ function collectTag7(value, prev) {
3507
+ return [...prev, value];
3508
+ }
3509
+ function timeBlocksDeleteCommand() {
3510
+ 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) => {
3511
+ const ctx = await createAppContext();
3512
+ const all = await ctx.repos.timeBlock.findAll();
3513
+ const id = resolveResourceIdByPrefix(all, prefix, "time-block");
3514
+ const target = all.find((b) => b.id === id);
3515
+ await ctx.repos.timeBlock.delete(id);
3516
+ console.log(`\u2717 Deleted time-block: ${shortenId(id)} ${target.name}`);
3517
+ });
3518
+ }
3519
+
3520
+ // src/format/time-block-row.ts
3521
+ var WEEKDAY_LABELS = ["\u65E5", "\u6708", "\u706B", "\u6C34", "\u6728", "\u91D1", "\u571F"];
3522
+ function formatWeekdays(weekdays) {
3523
+ if (weekdays.length === 7) return "every";
3524
+ if (weekdays.length === 0) return "-";
3525
+ const labels = [];
3526
+ for (const w of weekdays) {
3527
+ if (w >= 0 && w <= 6) {
3528
+ labels.push(WEEKDAY_LABELS[w] ?? "?");
3529
+ }
3530
+ }
3531
+ return labels.join("");
3532
+ }
3533
+ function formatTimeBlockRow(block) {
3534
+ return {
3535
+ id: shortenId(block.id),
3536
+ name: block.name,
3537
+ status: block.status,
3538
+ time: `${block.startTime}-${block.endTime}`,
3539
+ weekdays: formatWeekdays(block.weekdays),
3540
+ color: block.color ?? "-"
3541
+ };
3542
+ }
3543
+
3544
+ // src/commands/time-blocks/list.ts
3545
+ function timeBlocksListCommand() {
3546
+ 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) => {
3547
+ const ctx = await createAppContext();
3548
+ const filter = opts.all ? void 0 : opts.archived ? { status: "archived" } : { status: "active" };
3549
+ const blocks = await ctx.repos.timeBlock.findAll(filter);
3550
+ if (blocks.length === 0) {
3551
+ console.log("No time blocks.");
3552
+ return;
3553
+ }
3554
+ console.table(blocks.map(formatTimeBlockRow));
3555
+ });
3556
+ }
3557
+ var HHMM6 = /^([01]\d|2[0-3]):[0-5]\d$/;
3558
+ var DATE_RE6 = /^\d{4}-\d{2}-\d{2}$/;
3559
+ var TIME_BLOCK_STATUSES = ["active", "archived"];
3560
+ function isTimeBlockStatus(value) {
3561
+ return TIME_BLOCK_STATUSES.includes(value);
3562
+ }
3563
+ function timeBlocksUpdateCommand() {
3564
+ 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) => {
3565
+ const ctx = await createAppContext();
3566
+ const all = await ctx.repos.timeBlock.findAll();
3567
+ const id = resolveResourceIdByPrefix(all, prefix, "time-block");
3568
+ if (opts.startTime !== void 0 && !HHMM6.test(opts.startTime)) {
3569
+ throw new CliError(`--start-time must be HH:MM (got "${opts.startTime}").`);
3570
+ }
3571
+ if (opts.endTime !== void 0 && !HHMM6.test(opts.endTime)) {
3572
+ throw new CliError(`--end-time must be HH:MM (got "${opts.endTime}").`);
3573
+ }
3574
+ if (opts.startDate !== void 0 && !DATE_RE6.test(opts.startDate)) {
3575
+ throw new CliError(`--start-date must be YYYY-MM-DD (got "${opts.startDate}").`);
3576
+ }
3577
+ if (opts.endDate !== void 0 && !DATE_RE6.test(opts.endDate)) {
3578
+ throw new CliError(`--end-date must be YYYY-MM-DD (got "${opts.endDate}").`);
3579
+ }
3580
+ if (opts.status !== void 0 && !isTimeBlockStatus(opts.status)) {
3581
+ throw new CliError(
3582
+ `--status must be one of ${TIME_BLOCK_STATUSES.join(" / ")} (got "${opts.status}").`
3583
+ );
3584
+ }
3585
+ const patch = {};
3586
+ if (opts.name !== void 0) patch.name = opts.name;
3587
+ if (opts.startTime !== void 0) patch.startTime = opts.startTime;
3588
+ if (opts.endTime !== void 0) patch.endTime = opts.endTime;
3589
+ if (opts.weekdays !== void 0) patch.weekdays = parseWeekdaysList(opts.weekdays);
3590
+ if (opts.clearProject) patch.defaultProjectId = void 0;
3591
+ else if (opts.project !== void 0) {
3592
+ const projects = await ctx.repos.project.findAll();
3593
+ patch.defaultProjectId = resolveResourceIdByPrefix(projects, opts.project, "project");
3594
+ }
3595
+ if (opts.clearContext) patch.defaultContextId = void 0;
3596
+ else if (opts.context !== void 0) {
3597
+ const contexts = await ctx.repos.context.findAll();
3598
+ patch.defaultContextId = resolveResourceIdByPrefix(contexts, opts.context, "context");
3599
+ }
3600
+ if (opts.noTags) patch.defaultTagIds = [];
3601
+ else if (opts.tag !== void 0 && opts.tag.length > 0) {
3602
+ const tags = await ctx.repos.tag.findAll();
3603
+ const resolved = [];
3604
+ for (const p of opts.tag) {
3605
+ resolved.push(resolveResourceIdByPrefix(tags, p, "tag"));
3606
+ }
3607
+ patch.defaultTagIds = resolved;
3608
+ }
3609
+ if (opts.clearColor) patch.color = void 0;
3610
+ else if (opts.color !== void 0) patch.color = opts.color;
3611
+ if (opts.clearStartDate) patch.startDate = void 0;
3612
+ else if (opts.startDate !== void 0) patch.startDate = opts.startDate;
3613
+ if (opts.clearEndDate) patch.endDate = void 0;
3614
+ else if (opts.endDate !== void 0) patch.endDate = opts.endDate;
3615
+ if (opts.status !== void 0 && isTimeBlockStatus(opts.status)) {
3616
+ patch.status = opts.status;
3617
+ }
3618
+ if (Object.keys(patch).length === 0) {
3619
+ throw new CliError(
3620
+ "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-*."
3621
+ );
3622
+ }
3623
+ const updated = await ctx.repos.timeBlock.update(id, patch);
3624
+ console.log(`\u270E Updated time-block: ${shortenId(updated.id)} ${updated.name}`);
3625
+ });
3626
+ }
3627
+ function collectTag8(value, prev) {
3628
+ return [...prev, value];
3629
+ }
3630
+
3631
+ // src/commands/time-blocks/index.ts
3632
+ function timeBlocksCommand() {
3633
+ 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
3634
  }
2185
3635
  function unlockCommand() {
2186
3636
  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 +3683,18 @@ function whoamiCommand() {
2233
3683
 
2234
3684
  // src/index.ts
2235
3685
  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.0.0");
3686
+ 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.2.0" );
2237
3687
  program.addCommand(loginCommand());
2238
3688
  program.addCommand(whoamiCommand());
2239
3689
  program.addCommand(logoutCommand());
2240
3690
  program.addCommand(unlockCommand());
2241
3691
  program.addCommand(lockCommand());
2242
3692
  program.addCommand(tasksCommand());
3693
+ program.addCommand(projectsCommand());
3694
+ program.addCommand(tagsCommand());
3695
+ program.addCommand(contextsCommand());
3696
+ program.addCommand(routinesCommand());
3697
+ program.addCommand(timeBlocksCommand());
2243
3698
  program.parseAsync(process.argv).catch((err) => {
2244
3699
  if (err instanceof CliError) {
2245
3700
  console.error(err.message);