@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/CHANGELOG.md +180 -0
- package/README.md +38 -4
- package/dist/index.js +1600 -145
- package/package.json +3 -2
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 {
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
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/
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
if (!
|
|
1922
|
-
throw new
|
|
1923
|
-
`
|
|
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
|
|
2306
|
+
return await promptPassphrase();
|
|
1927
2307
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
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
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
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
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
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/
|
|
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
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
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
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
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
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
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
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
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/
|
|
2074
|
-
function
|
|
2075
|
-
|
|
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
|
|
2078
|
-
const
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
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
|
|
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(
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
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/
|
|
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
|
|
3095
|
+
var MIN_PREFIX_LENGTH2 = 4;
|
|
2118
3096
|
function resolveTaskIdByPrefix(tasks, prefix) {
|
|
2119
|
-
if (prefix.length <
|
|
3097
|
+
if (prefix.length < MIN_PREFIX_LENGTH2) {
|
|
2120
3098
|
throw new CliError(
|
|
2121
|
-
`Task id prefix must be at least ${
|
|
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/
|
|
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.
|
|
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);
|