donebear 0.4.4 → 0.5.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/AGENTS.md +8 -8
- package/LICENSE.md +21 -0
- package/README.md +11 -6
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +623 -204
- package/dist/cli.mjs.map +1 -1
- package/dist/context-store-BuhPB0I9.mjs +2 -0
- package/dist/{context-store-BbZMKY0F.mjs → context-store-ByUXl7Cn.mjs} +17 -3
- package/dist/context-store-ByUXl7Cn.mjs.map +1 -0
- package/dist/{prompt-B66eG-Zm.mjs → prompt-Bv9qkjNw.mjs} +1 -1
- package/dist/{prompt-B66eG-Zm.mjs.map → prompt-Bv9qkjNw.mjs.map} +1 -1
- package/dist/prompt-ujp1V6r9.mjs +2 -0
- package/package.json +20 -18
- package/dist/context-store-BbZMKY0F.mjs.map +0 -1
- package/dist/context-store-D8zfa5LS.mjs +0 -3
- package/dist/prompt--P7Y1FBL.mjs +0 -3
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
3
|
-
import { n as readStdin, t as promptForValue } from "./prompt-
|
|
2
|
+
import { S as DONEBEAR_TOKEN_ENV, _ as DONEBEAR_API_URL_ENV, a as SyncMutationRejectedError, c as CLI_NAME, f as DEFAULT_APP_URL, i as EXIT_CODES, m as DEFAULT_OAUTH_CALLBACK_PORT, n as setCurrentWorkspace, o as toCliError, p as DEFAULT_OAUTH_CALLBACK_PATH, r as CliError, s as AUTH_FILE_PATH, t as ensureLocalContext, u as CONFIG_DIR } from "./context-store-ByUXl7Cn.mjs";
|
|
3
|
+
import { n as readStdin, t as promptForValue } from "./prompt-Bv9qkjNw.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { basename, join, resolve } from "node:path";
|
|
@@ -14,8 +14,8 @@ import { accessSync, constants, existsSync, readdirSync, statSync } from "node:f
|
|
|
14
14
|
import { config } from "dotenv";
|
|
15
15
|
import { Command, InvalidArgumentError } from "commander";
|
|
16
16
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
18
|
import { createServer } from "node:http";
|
|
18
|
-
import { fileURLToPath } from "node:url";
|
|
19
19
|
import Database from "better-sqlite3";
|
|
20
20
|
//#region \0rolldown/runtime.js
|
|
21
21
|
var __create = Object.create;
|
|
@@ -35,7 +35,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
35
35
|
}
|
|
36
36
|
return to;
|
|
37
37
|
};
|
|
38
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
38
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
39
39
|
value: mod,
|
|
40
40
|
enumerable: true
|
|
41
41
|
}) : target, mod));
|
|
@@ -104,9 +104,7 @@ const writeOutput = (context, data) => {
|
|
|
104
104
|
case "json":
|
|
105
105
|
writeJson(result);
|
|
106
106
|
break;
|
|
107
|
-
default:
|
|
108
|
-
writeJson(result);
|
|
109
|
-
break;
|
|
107
|
+
default: writeJson(result);
|
|
110
108
|
}
|
|
111
109
|
if (context.copy) copyToClipboard(JSON.stringify(result, null, 2));
|
|
112
110
|
};
|
|
@@ -175,9 +173,10 @@ const tokenize = (input) => {
|
|
|
175
173
|
let current = "";
|
|
176
174
|
let inQuote = false;
|
|
177
175
|
let quoteChar = "";
|
|
178
|
-
for (const char of input) if (inQuote)
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
for (const char of input) if (inQuote) {
|
|
177
|
+
if (char === quoteChar) inQuote = false;
|
|
178
|
+
else current += char;
|
|
179
|
+
} else if (char === "\"" || char === "'") {
|
|
181
180
|
inQuote = true;
|
|
182
181
|
quoteChar = char;
|
|
183
182
|
} else if (char === " ") {
|
|
@@ -229,7 +228,7 @@ const runInteractiveMode = async (context, program) => {
|
|
|
229
228
|
rl.close();
|
|
230
229
|
if (sessionEntries.length > 0) try {
|
|
231
230
|
const payload = {
|
|
232
|
-
entries: [...await loadHistory(HISTORY_FILE), ...sessionEntries].slice(-
|
|
231
|
+
entries: [...await loadHistory(HISTORY_FILE), ...sessionEntries].slice(-200),
|
|
233
232
|
version: 1
|
|
234
233
|
};
|
|
235
234
|
await mkdir(CONFIG_DIR, {
|
|
@@ -366,7 +365,8 @@ const parseAuthFile = (payload) => {
|
|
|
366
365
|
};
|
|
367
366
|
const readStoredAuthSession = async () => {
|
|
368
367
|
try {
|
|
369
|
-
|
|
368
|
+
const raw = await readFile(AUTH_FILE_PATH, "utf-8");
|
|
369
|
+
return parseAuthFile(raw).session;
|
|
370
370
|
} catch (error) {
|
|
371
371
|
if (isRecord$1(error) && error.code === "ENOENT") return null;
|
|
372
372
|
if (error instanceof CliError) throw error;
|
|
@@ -521,6 +521,8 @@ const resolveTokenStatus = (token) => {
|
|
|
521
521
|
*/
|
|
522
522
|
const TASK_FIELDS = {
|
|
523
523
|
bootstrapFields: [
|
|
524
|
+
"agentAssigneeId",
|
|
525
|
+
"revision",
|
|
524
526
|
"id",
|
|
525
527
|
"number",
|
|
526
528
|
"title",
|
|
@@ -554,6 +556,7 @@ const TASK_FIELDS = {
|
|
|
554
556
|
"repeatNextAt"
|
|
555
557
|
],
|
|
556
558
|
insertFields: [
|
|
559
|
+
"agentAssigneeId",
|
|
557
560
|
"archivedAt",
|
|
558
561
|
"assigneeId",
|
|
559
562
|
"completedAt",
|
|
@@ -574,6 +577,7 @@ const TASK_FIELDS = {
|
|
|
574
577
|
"repeatStatus",
|
|
575
578
|
"repeatTemplateId",
|
|
576
579
|
"repeatType",
|
|
580
|
+
"revision",
|
|
577
581
|
"sortOrder",
|
|
578
582
|
"start",
|
|
579
583
|
"startBucket",
|
|
@@ -586,6 +590,8 @@ const TASK_FIELDS = {
|
|
|
586
590
|
"workspaceId"
|
|
587
591
|
],
|
|
588
592
|
iosKeys: [
|
|
593
|
+
"agentAssigneeId",
|
|
594
|
+
"revision",
|
|
589
595
|
"id",
|
|
590
596
|
"number",
|
|
591
597
|
"title",
|
|
@@ -638,11 +644,14 @@ const TASK_FIELDS = {
|
|
|
638
644
|
"projectId",
|
|
639
645
|
"teamId",
|
|
640
646
|
"assigneeId",
|
|
647
|
+
"agentAssigneeId",
|
|
648
|
+
"revision",
|
|
641
649
|
"headingId",
|
|
642
650
|
"repeatRule",
|
|
643
651
|
"repeatTemplateId"
|
|
644
652
|
],
|
|
645
653
|
updateFields: [
|
|
654
|
+
"agentAssigneeId",
|
|
646
655
|
"title",
|
|
647
656
|
"description",
|
|
648
657
|
"sortOrder",
|
|
@@ -674,7 +683,7 @@ const TASK_FIELDS = {
|
|
|
674
683
|
}.readProjection.join("\n");
|
|
675
684
|
//#endregion
|
|
676
685
|
//#region ../../packages/task-logic/src/date-only.ts
|
|
677
|
-
const DATE_ONLY_DAY_MS =
|
|
686
|
+
const DATE_ONLY_DAY_MS = 864e5;
|
|
678
687
|
const DATE_ONLY_INPUT_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/u;
|
|
679
688
|
const isFiniteEpoch$1 = (value) => typeof value === "number" && Number.isFinite(value);
|
|
680
689
|
const isCanonicalDateOnlyEpoch = (value) => value % DATE_ONLY_DAY_MS === 0;
|
|
@@ -938,6 +947,71 @@ const createSyncMutateClient = ({ baseUrl, errorMapper = defaultErrorMapper$1, f
|
|
|
938
947
|
return { mutate };
|
|
939
948
|
};
|
|
940
949
|
//#endregion
|
|
950
|
+
//#region ../../packages/manage-client/src/bound-task-update.ts
|
|
951
|
+
/** Task fields a bound agent may change through a conditional command. */
|
|
952
|
+
const BOUND_TASK_PATCH_FIELDS = [
|
|
953
|
+
"archivedAt",
|
|
954
|
+
"completedAt",
|
|
955
|
+
"deadlineAt",
|
|
956
|
+
"description",
|
|
957
|
+
"headingId",
|
|
958
|
+
"projectId",
|
|
959
|
+
"reminderAt",
|
|
960
|
+
"start",
|
|
961
|
+
"startBucket",
|
|
962
|
+
"startDate",
|
|
963
|
+
"teamId",
|
|
964
|
+
"title",
|
|
965
|
+
"todayIndexReferenceDate"
|
|
966
|
+
];
|
|
967
|
+
/**
|
|
968
|
+
* Bound keys write through the conditional Task command; unbound callers
|
|
969
|
+
* retain native sync. Returns false when the caller is not a bound agent.
|
|
970
|
+
*/
|
|
971
|
+
const updateBoundTasks = async (input) => {
|
|
972
|
+
const noun = input.noun ?? "change";
|
|
973
|
+
if (!input.token.startsWith("db_")) return false;
|
|
974
|
+
const workspaceId = input.updates[0]?.task.workspaceId;
|
|
975
|
+
if (!workspaceId || input.updates.some(({ task }) => task.workspaceId !== workspaceId)) throw new Error("Change tasks in one explicit workspace.");
|
|
976
|
+
const binding = await input.send(`/api/task-agents/self?workspaceId=${encodeURIComponent(workspaceId)}`);
|
|
977
|
+
if (binding.workspaceId !== workspaceId || binding.agent === void 0) throw new Error("Task agent binding is unavailable.");
|
|
978
|
+
if (binding.agent === null) return false;
|
|
979
|
+
if (binding.agent.disabledAt !== null) throw new Error("This task agent is disabled.");
|
|
980
|
+
if (input.updates.some(({ task }) => task.agentAssigneeId !== binding.agent?.id)) throw new Error("A task agent can change only tasks assigned to it.");
|
|
981
|
+
if (input.updates.some(({ task }) => !Number.isInteger(task.revision) || task.revision < 1)) throw new Error("The change requires the original selected task revision.");
|
|
982
|
+
const allowed = new Set(BOUND_TASK_PATCH_FIELDS);
|
|
983
|
+
for (const { patch } of input.updates) {
|
|
984
|
+
const refused = Object.keys(patch).filter((key) => !allowed.has(key));
|
|
985
|
+
if (refused.length > 0) throw new Error(`A task agent cannot change ${refused.join(", ")}. Hand the task off instead.`);
|
|
986
|
+
}
|
|
987
|
+
let changed = 0;
|
|
988
|
+
for (const { task, patch } of input.updates.filter(({ task: selected }, index, updates) => updates.findIndex((candidate) => candidate.task.id === selected.id) === index)) try {
|
|
989
|
+
const result = await input.send("/api/task-commands", {
|
|
990
|
+
expectedRevision: task.revision,
|
|
991
|
+
kind: "update",
|
|
992
|
+
operationId: crypto.randomUUID(),
|
|
993
|
+
patch,
|
|
994
|
+
taskId: task.id,
|
|
995
|
+
workspaceId
|
|
996
|
+
});
|
|
997
|
+
if (result.kind !== "update" || result.task?.id !== task.id || Object.entries(patch).some(([key, value]) => result.task?.[key] !== value) || result.task.revision !== task.revision + 1) throw new Error("The change has no matching committed receipt; do not retry automatically.");
|
|
998
|
+
changed += 1;
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
throw new Error(`${noun[0]?.toUpperCase()}${noun.slice(1)} failed with ${changed} confirmed ${noun}(s). The current request may have an unknown outcome; do not retry automatically. ${error instanceof Error ? error.message : "No response."}`, { cause: error });
|
|
1001
|
+
}
|
|
1002
|
+
return true;
|
|
1003
|
+
};
|
|
1004
|
+
/** Archive is the one bound write the CLI also drives; it keeps its own wording. */
|
|
1005
|
+
const archiveBoundTasks = (input) => updateBoundTasks({
|
|
1006
|
+
noun: "archive",
|
|
1007
|
+
send: input.send,
|
|
1008
|
+
token: input.token,
|
|
1009
|
+
updates: input.tasks.map((task) => ({
|
|
1010
|
+
patch: { archivedAt: input.archivedAt },
|
|
1011
|
+
task
|
|
1012
|
+
}))
|
|
1013
|
+
});
|
|
1014
|
+
//#endregion
|
|
941
1015
|
//#region ../../packages/manage-client/src/index.ts
|
|
942
1016
|
const TASKS_QUERY = `
|
|
943
1017
|
query DonebearTasks($first: Int!, $after: String, $workspaceId: ID!) {
|
|
@@ -976,6 +1050,7 @@ const toInstantEpoch = (value) => {
|
|
|
976
1050
|
return Number.isFinite(epoch) ? epoch : null;
|
|
977
1051
|
};
|
|
978
1052
|
const toTaskRecord$1 = (node) => ({
|
|
1053
|
+
agentAssigneeId: node.agentAssigneeId,
|
|
979
1054
|
archivedAt: toInstantEpoch(node.archivedAt),
|
|
980
1055
|
assigneeId: node.assigneeId,
|
|
981
1056
|
completedAt: toInstantEpoch(node.completedAt),
|
|
@@ -989,6 +1064,7 @@ const toTaskRecord$1 = (node) => ({
|
|
|
989
1064
|
projectId: node.projectId,
|
|
990
1065
|
repeatRule: node.repeatRule,
|
|
991
1066
|
repeatTemplateId: node.repeatTemplateId,
|
|
1067
|
+
revision: node.revision,
|
|
992
1068
|
start: node.start,
|
|
993
1069
|
startBucket: node.startBucket,
|
|
994
1070
|
startDate: parseDateOnlyInputToEpoch(node.startDate),
|
|
@@ -1389,6 +1465,7 @@ const toTaskRecord = (value) => {
|
|
|
1389
1465
|
if (typeof row.id !== "string" || typeof row.number !== "number" || typeof row.title !== "string") return null;
|
|
1390
1466
|
if (typeof row.creatorId !== "string" || typeof row.workspaceId !== "string" || typeof row.start !== "string" || typeof row.startBucket !== "string") return null;
|
|
1391
1467
|
return {
|
|
1468
|
+
agentAssigneeId: typeof row.agentAssigneeId === "string" ? row.agentAssigneeId : null,
|
|
1392
1469
|
archivedAt: toNullableNumber(row.archivedAt),
|
|
1393
1470
|
assigneeId: typeof row.assigneeId === "string" ? row.assigneeId : null,
|
|
1394
1471
|
completedAt: toNullableNumber(row.completedAt),
|
|
@@ -1402,6 +1479,7 @@ const toTaskRecord = (value) => {
|
|
|
1402
1479
|
projectId: typeof row.projectId === "string" ? row.projectId : null,
|
|
1403
1480
|
repeatRule: typeof row.repeatRule === "string" ? row.repeatRule : null,
|
|
1404
1481
|
repeatTemplateId: typeof row.repeatTemplateId === "string" ? row.repeatTemplateId : null,
|
|
1482
|
+
revision: typeof row.revision === "number" ? row.revision : 0,
|
|
1405
1483
|
start: row.start,
|
|
1406
1484
|
startBucket: row.startBucket,
|
|
1407
1485
|
startDate: toNullableNumber(row.startDate),
|
|
@@ -1550,12 +1628,14 @@ const toTaskAttachmentRecord = (value) => {
|
|
|
1550
1628
|
};
|
|
1551
1629
|
/** A task's comment thread, oldest first. */
|
|
1552
1630
|
const loadCommentsByTask = async (context, options) => {
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1631
|
+
const url = `${options.baseUrl}/sync/batch`;
|
|
1632
|
+
const body = { requests: [{
|
|
1633
|
+
indexedKey: "taskId",
|
|
1634
|
+
keyValue: options.taskId,
|
|
1635
|
+
modelName: "Comment"
|
|
1636
|
+
}] };
|
|
1637
|
+
const rows = await requestNdjson(context, url, {
|
|
1638
|
+
body,
|
|
1559
1639
|
token: options.token
|
|
1560
1640
|
});
|
|
1561
1641
|
const results = [];
|
|
@@ -1566,12 +1646,14 @@ const loadCommentsByTask = async (context, options) => {
|
|
|
1566
1646
|
return results.toSorted((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0));
|
|
1567
1647
|
};
|
|
1568
1648
|
const loadReadyTaskAttachmentsByTask = async (context, options) => {
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1649
|
+
const url = `${options.baseUrl}/sync/batch`;
|
|
1650
|
+
const body = { requests: [{
|
|
1651
|
+
indexedKey: "taskId",
|
|
1652
|
+
keyValue: options.taskId,
|
|
1653
|
+
modelName: "TaskAttachment"
|
|
1654
|
+
}] };
|
|
1655
|
+
const rows = await requestNdjson(context, url, {
|
|
1656
|
+
body,
|
|
1575
1657
|
token: options.token
|
|
1576
1658
|
});
|
|
1577
1659
|
const results = [];
|
|
@@ -1584,7 +1666,8 @@ const loadReadyTaskAttachmentsByTask = async (context, options) => {
|
|
|
1584
1666
|
const resolveApiBaseUrl = (context) => {
|
|
1585
1667
|
const fromContext = context.apiUrlOverride?.trim();
|
|
1586
1668
|
if (fromContext) return normalizeApiUrl(fromContext);
|
|
1587
|
-
|
|
1669
|
+
const fromEnv = readEnvValue(DONEBEAR_API_URL_ENV);
|
|
1670
|
+
return normalizeApiUrl(fromEnv ?? "https://api.donebear.com");
|
|
1588
1671
|
};
|
|
1589
1672
|
const mapGraphqlFailure = (failure) => {
|
|
1590
1673
|
if (failure.type === "http") {
|
|
@@ -1620,11 +1703,12 @@ const listWorkspaces = async (_context, options) => await createGraphqlClient({
|
|
|
1620
1703
|
getToken: () => options.token
|
|
1621
1704
|
}).listWorkspaces();
|
|
1622
1705
|
const createWorkspace = async (context, options) => {
|
|
1623
|
-
|
|
1706
|
+
const data = await requestGraphql(context, options.baseUrl, {
|
|
1624
1707
|
query: WORKSPACE_CREATE_MUTATION,
|
|
1625
1708
|
token: options.token,
|
|
1626
1709
|
variables: { input: options.body }
|
|
1627
|
-
})
|
|
1710
|
+
});
|
|
1711
|
+
return toWorkspaceRecordFromGraphqlNode(data.workspaceCreate.workspace);
|
|
1628
1712
|
};
|
|
1629
1713
|
const joinWorkspace = async (context, options) => {
|
|
1630
1714
|
const data = await requestGraphql(context, options.baseUrl, {
|
|
@@ -1654,12 +1738,14 @@ const loadTasksByWorkspaceIds = (context, options) => paginateGraphql(context, {
|
|
|
1654
1738
|
variables: { workspaceIds: options.workspaceIds }
|
|
1655
1739
|
});
|
|
1656
1740
|
const loadTaskById = async (context, options) => {
|
|
1657
|
-
const
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1741
|
+
const url = `${options.baseUrl}/sync/batch`;
|
|
1742
|
+
const body = { requests: [{
|
|
1743
|
+
indexedKey: "id",
|
|
1744
|
+
keyValue: options.taskId,
|
|
1745
|
+
modelName: "Task"
|
|
1746
|
+
}] };
|
|
1747
|
+
const rows = await requestNdjson(context, url, {
|
|
1748
|
+
body,
|
|
1663
1749
|
token: options.token
|
|
1664
1750
|
});
|
|
1665
1751
|
for (const row of rows) {
|
|
@@ -1717,12 +1803,14 @@ const loadTeamsByWorkspaceIds = (context, options) => paginateGraphql(context, {
|
|
|
1717
1803
|
variables: { workspaceIds: options.workspaceIds }
|
|
1718
1804
|
});
|
|
1719
1805
|
const loadChecklistItemsByTask = async (context, options) => {
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1806
|
+
const url = `${options.baseUrl}/sync/batch`;
|
|
1807
|
+
const body = { requests: [{
|
|
1808
|
+
indexedKey: "taskId",
|
|
1809
|
+
keyValue: options.taskId,
|
|
1810
|
+
modelName: "TaskChecklistItem"
|
|
1811
|
+
}] };
|
|
1812
|
+
const rows = await requestNdjson(context, url, {
|
|
1813
|
+
body,
|
|
1726
1814
|
token: options.token
|
|
1727
1815
|
});
|
|
1728
1816
|
const results = [];
|
|
@@ -1742,7 +1830,10 @@ const mapSyncMutateFailure = (failure) => {
|
|
|
1742
1830
|
const exitCode = status === 401 || status === 403 ? EXIT_CODES.AUTH_REQUIRED : EXIT_CODES.ERROR;
|
|
1743
1831
|
return new CliError(extractErrorMessage(body), exitCode);
|
|
1744
1832
|
}
|
|
1745
|
-
if (failure.type === "mutation")
|
|
1833
|
+
if (failure.type === "mutation") {
|
|
1834
|
+
const result = failure.response.results.find((item) => !item.success);
|
|
1835
|
+
return new SyncMutationRejectedError(result?.error ?? "Sync mutation failed", failure.response.results);
|
|
1836
|
+
}
|
|
1746
1837
|
if (failure.type === "timeout") return new CliError(`Sync mutation timed out after ${failure.timeoutMs}ms`, EXIT_CODES.ERROR);
|
|
1747
1838
|
if (failure.cause instanceof Error) return failure.cause;
|
|
1748
1839
|
return new CliError("Sync mutation request failed", EXIT_CODES.ERROR);
|
|
@@ -1791,11 +1882,12 @@ const createWorkspaceInvitation = async (context, options) => {
|
|
|
1791
1882
|
role: options.role,
|
|
1792
1883
|
workspaceId: options.workspaceId
|
|
1793
1884
|
});
|
|
1794
|
-
|
|
1885
|
+
const data = await requestGraphql(context, options.baseUrl, {
|
|
1795
1886
|
query: WORKSPACE_INVITATION_CREATE_MUTATION,
|
|
1796
1887
|
token: options.token,
|
|
1797
1888
|
variables: { input }
|
|
1798
|
-
})
|
|
1889
|
+
});
|
|
1890
|
+
return toWorkspaceInvitationRecordFromGraphqlNode(data.workspaceInvitationCreate.invitation);
|
|
1799
1891
|
};
|
|
1800
1892
|
const listWorkspaceHistory = async (context, options) => {
|
|
1801
1893
|
const variables = omitUndefined({
|
|
@@ -1875,6 +1967,56 @@ const requireUserId = async (context, commandContext) => {
|
|
|
1875
1967
|
token: commandContext.token
|
|
1876
1968
|
})).id;
|
|
1877
1969
|
};
|
|
1970
|
+
const packageManifest = createRequire(import.meta.url)("../package.json");
|
|
1971
|
+
//#endregion
|
|
1972
|
+
//#region src/crash-report.ts
|
|
1973
|
+
const POSTHOG_HOST = "https://us.i.posthog.com";
|
|
1974
|
+
const SHUTDOWN_TIMEOUT_MS = 2e3;
|
|
1975
|
+
const buildKey = () => "";
|
|
1976
|
+
const hasNodeErrorCode = (error) => [error, error.cause].some((candidate) => typeof candidate === "object" && candidate !== null && "code" in candidate && typeof candidate.code === "string");
|
|
1977
|
+
const isCliBug = (error) => (error instanceof TypeError || error instanceof ReferenceError || error instanceof RangeError) && !hasNodeErrorCode(error);
|
|
1978
|
+
const crashReportingEnabled = (key, env) => key.length > 0 && ![
|
|
1979
|
+
"1",
|
|
1980
|
+
"true",
|
|
1981
|
+
"yes"
|
|
1982
|
+
].includes(env.DO_NOT_TRACK?.trim().toLowerCase() ?? "");
|
|
1983
|
+
const REDACTED_MESSAGE = "[message not sent]";
|
|
1984
|
+
const WINDOWS_PATH = /^[A-Za-z]:\\/u;
|
|
1985
|
+
/** The report as sent: home paths cut to `~`, every exception message gone. */
|
|
1986
|
+
const redactCrashReport = (event, home) => {
|
|
1987
|
+
if (!event) return event;
|
|
1988
|
+
const forms = home ? [.../* @__PURE__ */ new Set([
|
|
1989
|
+
home,
|
|
1990
|
+
home.replaceAll("\\", "/"),
|
|
1991
|
+
pathToFileURL(home, { windows: WINDOWS_PATH.test(home) }).pathname
|
|
1992
|
+
])] : [];
|
|
1993
|
+
let json = JSON.stringify(event);
|
|
1994
|
+
for (const form of forms) json = json.replaceAll(JSON.stringify(form).slice(1, -1), "~");
|
|
1995
|
+
const redacted = JSON.parse(json);
|
|
1996
|
+
const exceptions = redacted.properties?.$exception_list;
|
|
1997
|
+
for (const exception of exceptions ?? []) exception.value = REDACTED_MESSAGE;
|
|
1998
|
+
return redacted;
|
|
1999
|
+
};
|
|
2000
|
+
const reportCrash = async (error) => {
|
|
2001
|
+
const key = buildKey();
|
|
2002
|
+
if (!(isCliBug(error) && crashReportingEnabled(key, process.env))) return;
|
|
2003
|
+
try {
|
|
2004
|
+
const { PostHog } = await import("posthog-node");
|
|
2005
|
+
const home = homedir();
|
|
2006
|
+
const client = new PostHog(key, {
|
|
2007
|
+
before_send: (event) => redactCrashReport(event, home),
|
|
2008
|
+
disableGeoip: true,
|
|
2009
|
+
flushAt: 1,
|
|
2010
|
+
host: POSTHOG_HOST
|
|
2011
|
+
});
|
|
2012
|
+
client.captureException(error, void 0, {
|
|
2013
|
+
cli_version: packageManifest.version,
|
|
2014
|
+
node_version: process.version,
|
|
2015
|
+
os_platform: process.platform
|
|
2016
|
+
});
|
|
2017
|
+
await client.shutdown(SHUTDOWN_TIMEOUT_MS);
|
|
2018
|
+
} catch {}
|
|
2019
|
+
};
|
|
1878
2020
|
//#endregion
|
|
1879
2021
|
//#region src/options.ts
|
|
1880
2022
|
const parseOutputFormat = (value) => {
|
|
@@ -1964,6 +2106,7 @@ const runWithErrorHandling = async (context, action) => {
|
|
|
1964
2106
|
}
|
|
1965
2107
|
if (context.debug && error instanceof Error && typeof error.stack === "string") writeDebug(context, error.stack);
|
|
1966
2108
|
process.exitCode = cliError.exitCode;
|
|
2109
|
+
if (cliError.exitCode === EXIT_CODES.ERROR) await reportCrash(error);
|
|
1967
2110
|
}
|
|
1968
2111
|
};
|
|
1969
2112
|
//#endregion
|
|
@@ -2045,8 +2188,9 @@ const runCreate = async (context, label, options) => {
|
|
|
2045
2188
|
};
|
|
2046
2189
|
const runList = async (context) => {
|
|
2047
2190
|
const { token } = await requireAuthToken(context);
|
|
2191
|
+
const baseUrl = resolveApiBaseUrl(context);
|
|
2048
2192
|
const keys = await listApiKeys(context, {
|
|
2049
|
-
baseUrl
|
|
2193
|
+
baseUrl,
|
|
2050
2194
|
token
|
|
2051
2195
|
});
|
|
2052
2196
|
if (context.json) {
|
|
@@ -2068,8 +2212,9 @@ const runList = async (context) => {
|
|
|
2068
2212
|
};
|
|
2069
2213
|
const runRevoke = async (context, keyId) => {
|
|
2070
2214
|
const { token } = await requireAuthToken(context);
|
|
2215
|
+
const baseUrl = resolveApiBaseUrl(context);
|
|
2071
2216
|
await revokeApiKey(context, {
|
|
2072
|
-
baseUrl
|
|
2217
|
+
baseUrl,
|
|
2073
2218
|
keyId,
|
|
2074
2219
|
token
|
|
2075
2220
|
});
|
|
@@ -2432,7 +2577,8 @@ const parseSignupTimeout = (value) => parsePositiveInteger(value, "Timeout");
|
|
|
2432
2577
|
//#endregion
|
|
2433
2578
|
//#region src/commands/auth.ts
|
|
2434
2579
|
const runLogin = async (context, options) => {
|
|
2435
|
-
const
|
|
2580
|
+
const config = resolveSupabaseConfig();
|
|
2581
|
+
const { authorizeUrl, tokenUrl } = buildOAuthUrls(config);
|
|
2436
2582
|
const clientId = resolveOAuthClientId();
|
|
2437
2583
|
const redirectUrl = new URL(`http://127.0.0.1:${options.port}${DEFAULT_OAUTH_CALLBACK_PATH}`);
|
|
2438
2584
|
const state = createOAuthState();
|
|
@@ -2461,10 +2607,11 @@ const runLogin = async (context, options) => {
|
|
|
2461
2607
|
writeInfo(context, authUrlString);
|
|
2462
2608
|
}
|
|
2463
2609
|
const authorizationCode = await callbackPromise;
|
|
2464
|
-
const
|
|
2610
|
+
const tokenResponse = await exchangeAuthorizationCode({
|
|
2465
2611
|
clientId,
|
|
2466
2612
|
tokenUrl
|
|
2467
|
-
}, authorizationCode, codeVerifier, redirectUrl.toString())
|
|
2613
|
+
}, authorizationCode, codeVerifier, redirectUrl.toString());
|
|
2614
|
+
const storedSession = tokenResponseToStoredSession(tokenResponse);
|
|
2468
2615
|
await writeStoredAuthSession(storedSession);
|
|
2469
2616
|
if (context.json) {
|
|
2470
2617
|
writeJson({
|
|
@@ -2479,7 +2626,8 @@ const runLogin = async (context, options) => {
|
|
|
2479
2626
|
if (storedSession.user?.email) writeInfo(context, `Signed in as ${storedSession.user.email}`);
|
|
2480
2627
|
};
|
|
2481
2628
|
const runStatus = async (context) => {
|
|
2482
|
-
const
|
|
2629
|
+
const config = getOptionalSupabaseConfig();
|
|
2630
|
+
const token = await resolveAuthToken(context, config);
|
|
2483
2631
|
if (!token) {
|
|
2484
2632
|
if (context.json) writeJson({
|
|
2485
2633
|
authenticated: false,
|
|
@@ -2637,7 +2785,7 @@ const parseTaskNumberRef = (value) => {
|
|
|
2637
2785
|
const normalized = value.trim();
|
|
2638
2786
|
if (!/^[1-9]\d*$/u.test(normalized)) throw new Error(`Invalid task number "${normalized}". Use the positive number shown on the task.`);
|
|
2639
2787
|
const number = Number(normalized);
|
|
2640
|
-
if (!Number.isSafeInteger(number) || number > MAX_TASK_NUMBER) throw new
|
|
2788
|
+
if (!Number.isSafeInteger(number) || number > MAX_TASK_NUMBER) throw new Error(`Invalid task number "${normalized}". Number is too large.`);
|
|
2641
2789
|
return number;
|
|
2642
2790
|
};
|
|
2643
2791
|
const parseTaskState = (value) => {
|
|
@@ -2706,29 +2854,29 @@ const confirmAction = (context, message) => {
|
|
|
2706
2854
|
});
|
|
2707
2855
|
};
|
|
2708
2856
|
//#endregion
|
|
2709
|
-
//#region ../../node_modules/uuid/dist-node/regex.js
|
|
2857
|
+
//#region ../../node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist-node/regex.js
|
|
2710
2858
|
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
|
|
2711
2859
|
//#endregion
|
|
2712
|
-
//#region ../../node_modules/uuid/dist-node/validate.js
|
|
2860
|
+
//#region ../../node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist-node/validate.js
|
|
2713
2861
|
function validate(uuid) {
|
|
2714
2862
|
return typeof uuid === "string" && regex_default.test(uuid);
|
|
2715
2863
|
}
|
|
2716
2864
|
//#endregion
|
|
2717
|
-
//#region ../../node_modules/uuid/dist-node/parse.js
|
|
2865
|
+
//#region ../../node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist-node/parse.js
|
|
2718
2866
|
function parse(uuid) {
|
|
2719
2867
|
if (!validate(uuid)) throw TypeError("Invalid UUID");
|
|
2720
2868
|
let v;
|
|
2721
2869
|
return Uint8Array.of((v = parseInt(uuid.slice(0, 8), 16)) >>> 24, v >>> 16 & 255, v >>> 8 & 255, v & 255, (v = parseInt(uuid.slice(9, 13), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(14, 18), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(19, 23), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(24, 36), 16)) / 1099511627776 & 255, v / 4294967296 & 255, v >>> 24 & 255, v >>> 16 & 255, v >>> 8 & 255, v & 255);
|
|
2722
2870
|
}
|
|
2723
2871
|
//#endregion
|
|
2724
|
-
//#region ../../node_modules/uuid/dist-node/stringify.js
|
|
2872
|
+
//#region ../../node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist-node/stringify.js
|
|
2725
2873
|
const byteToHex = [];
|
|
2726
2874
|
for (let i = 0; i < 256; ++i) byteToHex.push((i + 256).toString(16).slice(1));
|
|
2727
2875
|
function unsafeStringify(arr, offset = 0) {
|
|
2728
2876
|
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
2729
2877
|
}
|
|
2730
2878
|
//#endregion
|
|
2731
|
-
//#region ../../node_modules/uuid/dist-node/v35.js
|
|
2879
|
+
//#region ../../node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist-node/v35.js
|
|
2732
2880
|
function stringToBytes(str) {
|
|
2733
2881
|
str = unescape(encodeURIComponent(str));
|
|
2734
2882
|
const bytes = new Uint8Array(str.length);
|
|
@@ -2757,14 +2905,14 @@ function v35(version, hash, value, namespace, buf, offset) {
|
|
|
2757
2905
|
return unsafeStringify(bytes);
|
|
2758
2906
|
}
|
|
2759
2907
|
//#endregion
|
|
2760
|
-
//#region ../../node_modules/uuid/dist-node/sha1.js
|
|
2908
|
+
//#region ../../node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist-node/sha1.js
|
|
2761
2909
|
function sha1(bytes) {
|
|
2762
2910
|
if (Array.isArray(bytes)) bytes = Buffer.from(bytes);
|
|
2763
2911
|
else if (typeof bytes === "string") bytes = Buffer.from(bytes, "utf8");
|
|
2764
2912
|
return createHash("sha1").update(bytes).digest();
|
|
2765
2913
|
}
|
|
2766
2914
|
//#endregion
|
|
2767
|
-
//#region ../../node_modules/uuid/dist-node/v5.js
|
|
2915
|
+
//#region ../../node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist-node/v5.js
|
|
2768
2916
|
function v5(value, namespace, buf, offset) {
|
|
2769
2917
|
return v35(80, sha1, value, namespace, buf, offset);
|
|
2770
2918
|
}
|
|
@@ -2875,7 +3023,7 @@ const resolveWorkspace = async (context, options) => {
|
|
|
2875
3023
|
//#region src/commands/task.ts
|
|
2876
3024
|
const DEFAULT_TASK_LIST_LIMIT = 20;
|
|
2877
3025
|
/** The views `--when` can place a task in: the ones with start fields. */
|
|
2878
|
-
const ACTIVE_TASK_VIEWS = new Set([
|
|
3026
|
+
const ACTIVE_TASK_VIEWS = /* @__PURE__ */ new Set([
|
|
2879
3027
|
"inbox",
|
|
2880
3028
|
"anytime",
|
|
2881
3029
|
"today",
|
|
@@ -2883,7 +3031,7 @@ const ACTIVE_TASK_VIEWS = new Set([
|
|
|
2883
3031
|
"someday"
|
|
2884
3032
|
]);
|
|
2885
3033
|
/** The views `--view` can filter a list by, which adds the two read-only ones. */
|
|
2886
|
-
const VALID_TASK_VIEWS = new Set([
|
|
3034
|
+
const VALID_TASK_VIEWS = /* @__PURE__ */ new Set([
|
|
2887
3035
|
...ACTIVE_TASK_VIEWS,
|
|
2888
3036
|
"logbook",
|
|
2889
3037
|
"trash"
|
|
@@ -3173,7 +3321,18 @@ const runTaskMutation = async (context, taskRef, payload) => {
|
|
|
3173
3321
|
taskRef,
|
|
3174
3322
|
workspaceRef: payload.workspaceRef
|
|
3175
3323
|
});
|
|
3176
|
-
|
|
3324
|
+
if (!(payload.action === "ARCHIVE" && !context.dryRun && await archiveBoundTasks({
|
|
3325
|
+
archivedAt: Number(payload.data.archivedAt),
|
|
3326
|
+
send: (path, body) => requestJson(context, `${commandContext.apiBaseUrl}${path}`, {
|
|
3327
|
+
token: commandContext.token,
|
|
3328
|
+
...body === void 0 ? {} : {
|
|
3329
|
+
body,
|
|
3330
|
+
method: "POST"
|
|
3331
|
+
}
|
|
3332
|
+
}),
|
|
3333
|
+
tasks: [task],
|
|
3334
|
+
token: commandContext.token
|
|
3335
|
+
}))) await mutateModel(context, {
|
|
3177
3336
|
baseUrl: commandContext.apiBaseUrl,
|
|
3178
3337
|
request: {
|
|
3179
3338
|
batchId: randomUUID(),
|
|
@@ -3183,7 +3342,10 @@ const runTaskMutation = async (context, taskRef, payload) => {
|
|
|
3183
3342
|
clientTxId: randomUUID(),
|
|
3184
3343
|
modelId: task.id,
|
|
3185
3344
|
modelName: "Task",
|
|
3186
|
-
payload:
|
|
3345
|
+
payload: {
|
|
3346
|
+
...payload.data,
|
|
3347
|
+
...task.agentAssigneeId ? { expectedRevision: task.revision } : {}
|
|
3348
|
+
}
|
|
3187
3349
|
}]
|
|
3188
3350
|
},
|
|
3189
3351
|
token: commandContext.token
|
|
@@ -3213,11 +3375,12 @@ const resolveProjectId = async (context, commandContext, projectRef, workspaceRe
|
|
|
3213
3375
|
token: commandContext.token,
|
|
3214
3376
|
workspaceRef
|
|
3215
3377
|
});
|
|
3216
|
-
|
|
3378
|
+
const projects = await loadProjectsByWorkspace(context, {
|
|
3217
3379
|
baseUrl: commandContext.apiBaseUrl,
|
|
3218
3380
|
token: commandContext.token,
|
|
3219
3381
|
workspaceId: workspace.id
|
|
3220
|
-
})
|
|
3382
|
+
});
|
|
3383
|
+
return resolveEntityId(projects, projectRef, "Project");
|
|
3221
3384
|
};
|
|
3222
3385
|
const resolveTeamId = async (context, commandContext, teamRef, workspaceRef) => {
|
|
3223
3386
|
const { workspace } = await resolveWorkspace(context, {
|
|
@@ -3226,11 +3389,12 @@ const resolveTeamId = async (context, commandContext, teamRef, workspaceRef) =>
|
|
|
3226
3389
|
token: commandContext.token,
|
|
3227
3390
|
workspaceRef
|
|
3228
3391
|
});
|
|
3229
|
-
|
|
3392
|
+
const teams = await loadTeamsByWorkspace(context, {
|
|
3230
3393
|
baseUrl: commandContext.apiBaseUrl,
|
|
3231
3394
|
token: commandContext.token,
|
|
3232
3395
|
workspaceId: workspace.id
|
|
3233
|
-
})
|
|
3396
|
+
});
|
|
3397
|
+
return resolveEntityId(teams, teamRef, "Team");
|
|
3234
3398
|
};
|
|
3235
3399
|
const validateTaskEditOptions = (options) => {
|
|
3236
3400
|
if (options.clearDescription && typeof options.description === "string") throw new Error("Use either --description or --clear-description, not both.");
|
|
@@ -3273,8 +3437,9 @@ const runTaskEdit = async (context, taskRef, options, workspaceOverride) => {
|
|
|
3273
3437
|
});
|
|
3274
3438
|
};
|
|
3275
3439
|
const runTaskRead = async (context, taskRef, options, workspaceOverride) => {
|
|
3440
|
+
const commandContext = await resolveCommandContext(context);
|
|
3276
3441
|
const task = await resolveTaskReference(context, {
|
|
3277
|
-
commandContext
|
|
3442
|
+
commandContext,
|
|
3278
3443
|
taskRef,
|
|
3279
3444
|
workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
|
|
3280
3445
|
});
|
|
@@ -3289,14 +3454,16 @@ const runTaskRead = async (context, taskRef, options, workspaceOverride) => {
|
|
|
3289
3454
|
writeInfo(context, task.description ?? "(empty)");
|
|
3290
3455
|
};
|
|
3291
3456
|
const runTaskAppend = async (context, taskRef, text, options, workspaceOverride) => {
|
|
3457
|
+
const commandContext = await resolveCommandContext(context);
|
|
3458
|
+
const newDesc = `${(await resolveTaskReference(context, {
|
|
3459
|
+
commandContext,
|
|
3460
|
+
taskRef,
|
|
3461
|
+
workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
|
|
3462
|
+
})).description ?? ""}\n${text.trim()}`;
|
|
3292
3463
|
await runTaskMutation(context, taskRef, {
|
|
3293
3464
|
action: "UPDATE",
|
|
3294
3465
|
data: {
|
|
3295
|
-
description:
|
|
3296
|
-
commandContext: await resolveCommandContext(context),
|
|
3297
|
-
taskRef,
|
|
3298
|
-
workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
|
|
3299
|
-
})).description ?? ""}\n${text.trim()}`,
|
|
3466
|
+
description: newDesc,
|
|
3300
3467
|
updatedAt: Date.now()
|
|
3301
3468
|
},
|
|
3302
3469
|
successMessage: "Appended",
|
|
@@ -3304,15 +3471,17 @@ const runTaskAppend = async (context, taskRef, text, options, workspaceOverride)
|
|
|
3304
3471
|
});
|
|
3305
3472
|
};
|
|
3306
3473
|
const runTaskPrepend = async (context, taskRef, text, options, workspaceOverride) => {
|
|
3474
|
+
const commandContext = await resolveCommandContext(context);
|
|
3307
3475
|
const existing = (await resolveTaskReference(context, {
|
|
3308
|
-
commandContext
|
|
3476
|
+
commandContext,
|
|
3309
3477
|
taskRef,
|
|
3310
3478
|
workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
|
|
3311
3479
|
})).description ?? "";
|
|
3480
|
+
const newDesc = existing.length > 0 ? `${text.trim()}\n${existing}` : text.trim();
|
|
3312
3481
|
await runTaskMutation(context, taskRef, {
|
|
3313
3482
|
action: "UPDATE",
|
|
3314
3483
|
data: {
|
|
3315
|
-
description:
|
|
3484
|
+
description: newDesc,
|
|
3316
3485
|
updatedAt: Date.now()
|
|
3317
3486
|
},
|
|
3318
3487
|
successMessage: "Prepended",
|
|
@@ -3328,11 +3497,12 @@ const runTaskRandom = async (context, options, workspaceOverride) => {
|
|
|
3328
3497
|
token: commandContext.token,
|
|
3329
3498
|
workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
|
|
3330
3499
|
});
|
|
3331
|
-
const
|
|
3500
|
+
const tasks = await loadTasksByWorkspace(context, {
|
|
3332
3501
|
baseUrl: commandContext.apiBaseUrl,
|
|
3333
3502
|
token: commandContext.token,
|
|
3334
3503
|
workspaceId: workspace.id
|
|
3335
|
-
})
|
|
3504
|
+
});
|
|
3505
|
+
const filtered = filterTasks(tasks, {
|
|
3336
3506
|
search: null,
|
|
3337
3507
|
state: requestedState,
|
|
3338
3508
|
view: null
|
|
@@ -3412,7 +3582,10 @@ const runChecklistList = async (context, taskId, workspaceOption) => {
|
|
|
3412
3582
|
return;
|
|
3413
3583
|
}
|
|
3414
3584
|
writeInfo(context, `Checklist for: ${task.title}`);
|
|
3415
|
-
for (const item of sorted)
|
|
3585
|
+
for (const item of sorted) {
|
|
3586
|
+
const marker = item.completedAt === null ? "[ ]" : "[x]";
|
|
3587
|
+
writeInfo(context, `${marker} ${item.id.slice(0, 8)} ${item.title}`);
|
|
3588
|
+
}
|
|
3416
3589
|
};
|
|
3417
3590
|
const registerTaskSubcommands = (task, workspaceOverride) => {
|
|
3418
3591
|
task.command("list").alias("ls").description("List tasks").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("-s, --state <state>", "open|done|archived|all", "open").option("-n, --limit <count>", "Maximum number of tasks", (value) => parsePositiveInteger(value, "Limit"), DEFAULT_TASK_LIST_LIMIT).option("-q, --search <query>", "Filter by title or description").option("-v, --view <view>", "Filter by view (inbox|anytime|today|upcoming|someday|logbook|trash)").addHelpText("after", `
|
|
@@ -3598,11 +3771,12 @@ const registerChecklistSubcommands = (checklist) => {
|
|
|
3598
3771
|
const context = contextFromCommand(command);
|
|
3599
3772
|
await runWithErrorHandling(context, async () => {
|
|
3600
3773
|
const commandContext = await resolveCommandContext(context);
|
|
3601
|
-
const
|
|
3774
|
+
const resolvedTask = await resolveTaskReference(context, {
|
|
3602
3775
|
commandContext,
|
|
3603
3776
|
taskRef: taskId,
|
|
3604
3777
|
workspaceRef: options.workspace?.trim() ?? null
|
|
3605
|
-
})
|
|
3778
|
+
});
|
|
3779
|
+
const item = await resolveChecklistItemReference(context, commandContext, resolvedTask.id, itemId);
|
|
3606
3780
|
const now = Date.now();
|
|
3607
3781
|
await mutateModel(context, {
|
|
3608
3782
|
baseUrl: commandContext.apiBaseUrl,
|
|
@@ -3636,11 +3810,12 @@ const registerChecklistSubcommands = (checklist) => {
|
|
|
3636
3810
|
const context = contextFromCommand(command);
|
|
3637
3811
|
await runWithErrorHandling(context, async () => {
|
|
3638
3812
|
const commandContext = await resolveCommandContext(context);
|
|
3639
|
-
const
|
|
3813
|
+
const resolvedTask = await resolveTaskReference(context, {
|
|
3640
3814
|
commandContext,
|
|
3641
3815
|
taskRef: taskId,
|
|
3642
3816
|
workspaceRef: options.workspace?.trim() ?? null
|
|
3643
|
-
})
|
|
3817
|
+
});
|
|
3818
|
+
const item = await resolveChecklistItemReference(context, commandContext, resolvedTask.id, itemId);
|
|
3644
3819
|
const now = Date.now();
|
|
3645
3820
|
await mutateModel(context, {
|
|
3646
3821
|
baseUrl: commandContext.apiBaseUrl,
|
|
@@ -3676,11 +3851,12 @@ const registerChecklistSubcommands = (checklist) => {
|
|
|
3676
3851
|
const newTitle = options.title.trim();
|
|
3677
3852
|
if (newTitle.length === 0) throw new Error("Checklist item title cannot be empty.");
|
|
3678
3853
|
const commandContext = await resolveCommandContext(context);
|
|
3679
|
-
const
|
|
3854
|
+
const resolvedTask = await resolveTaskReference(context, {
|
|
3680
3855
|
commandContext,
|
|
3681
3856
|
taskRef: taskId,
|
|
3682
3857
|
workspaceRef: options.workspace?.trim() ?? null
|
|
3683
|
-
})
|
|
3858
|
+
});
|
|
3859
|
+
const item = await resolveChecklistItemReference(context, commandContext, resolvedTask.id, itemId);
|
|
3684
3860
|
const now = Date.now();
|
|
3685
3861
|
await mutateModel(context, {
|
|
3686
3862
|
baseUrl: commandContext.apiBaseUrl,
|
|
@@ -3714,11 +3890,12 @@ const registerChecklistSubcommands = (checklist) => {
|
|
|
3714
3890
|
const context = contextFromCommand(command);
|
|
3715
3891
|
await runWithErrorHandling(context, async () => {
|
|
3716
3892
|
const commandContext = await resolveCommandContext(context);
|
|
3717
|
-
const
|
|
3893
|
+
const resolvedTask = await resolveTaskReference(context, {
|
|
3718
3894
|
commandContext,
|
|
3719
3895
|
taskRef: taskId,
|
|
3720
3896
|
workspaceRef: options.workspace?.trim() ?? null
|
|
3721
|
-
})
|
|
3897
|
+
});
|
|
3898
|
+
const item = await resolveChecklistItemReference(context, commandContext, resolvedTask.id, itemId);
|
|
3722
3899
|
await mutateModel(context, {
|
|
3723
3900
|
baseUrl: commandContext.apiBaseUrl,
|
|
3724
3901
|
request: {
|
|
@@ -3773,12 +3950,13 @@ Agent tip: run "donebear spec task add" for full option reference
|
|
|
3773
3950
|
});
|
|
3774
3951
|
});
|
|
3775
3952
|
registerTaskSubcommands(task, workspaceOverride);
|
|
3776
|
-
|
|
3953
|
+
const checklist = task.command("checklist").alias("cl").description("Manage task checklist items").argument("<task-number>", "Workspace-scoped task number").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").action(async (taskId, options, command) => {
|
|
3777
3954
|
const context = contextFromCommand(command);
|
|
3778
3955
|
await runWithErrorHandling(context, async () => {
|
|
3779
3956
|
await runChecklistList(context, taskId, options.workspace);
|
|
3780
3957
|
});
|
|
3781
|
-
})
|
|
3958
|
+
});
|
|
3959
|
+
registerChecklistSubcommands(checklist);
|
|
3782
3960
|
};
|
|
3783
3961
|
//#endregion
|
|
3784
3962
|
//#region src/commands/comment.ts
|
|
@@ -3940,11 +4118,12 @@ respond to a teammate's comment, add your own instead.
|
|
|
3940
4118
|
await runWithErrorHandling(context, async () => {
|
|
3941
4119
|
const body = await resolveCommentBody(options.body);
|
|
3942
4120
|
const commandContext = await resolveCommandContext(context);
|
|
3943
|
-
const
|
|
4121
|
+
const task = await resolveTaskReference(context, {
|
|
3944
4122
|
commandContext,
|
|
3945
4123
|
taskRef,
|
|
3946
4124
|
workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
|
|
3947
|
-
})
|
|
4125
|
+
});
|
|
4126
|
+
const existing = await resolveCommentReference(context, commandContext, task.id, commentRef);
|
|
3948
4127
|
await mutateModel(context, {
|
|
3949
4128
|
baseUrl: commandContext.apiBaseUrl,
|
|
3950
4129
|
request: {
|
|
@@ -3980,11 +4159,12 @@ workspace owner or admin can remove anyone's.
|
|
|
3980
4159
|
const context = contextFromCommand(command);
|
|
3981
4160
|
await runWithErrorHandling(context, async () => {
|
|
3982
4161
|
const commandContext = await resolveCommandContext(context);
|
|
3983
|
-
const
|
|
4162
|
+
const task = await resolveTaskReference(context, {
|
|
3984
4163
|
commandContext,
|
|
3985
4164
|
taskRef,
|
|
3986
4165
|
workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
|
|
3987
|
-
})
|
|
4166
|
+
});
|
|
4167
|
+
const existing = await resolveCommentReference(context, commandContext, task.id, commentRef);
|
|
3988
4168
|
await mutateModel(context, {
|
|
3989
4169
|
baseUrl: commandContext.apiBaseUrl,
|
|
3990
4170
|
request: {
|
|
@@ -4142,7 +4322,6 @@ Examples:
|
|
|
4142
4322
|
});
|
|
4143
4323
|
});
|
|
4144
4324
|
};
|
|
4145
|
-
const packageManifest = createRequire(import.meta.url)("../package.json");
|
|
4146
4325
|
//#endregion
|
|
4147
4326
|
//#region src/commands/doctor.ts
|
|
4148
4327
|
const checkCliVersion = async () => {
|
|
@@ -4184,13 +4363,14 @@ const maskToken = (token) => {
|
|
|
4184
4363
|
};
|
|
4185
4364
|
const checkAuth = async (context) => {
|
|
4186
4365
|
try {
|
|
4187
|
-
const
|
|
4366
|
+
const config = (() => {
|
|
4188
4367
|
try {
|
|
4189
4368
|
return resolveSupabaseConfig();
|
|
4190
4369
|
} catch {
|
|
4191
4370
|
return null;
|
|
4192
4371
|
}
|
|
4193
|
-
})()
|
|
4372
|
+
})();
|
|
4373
|
+
const token = await resolveAuthToken(context, config);
|
|
4194
4374
|
if (!token) return {
|
|
4195
4375
|
message: "No API key or token found",
|
|
4196
4376
|
name: "Auth",
|
|
@@ -4211,7 +4391,7 @@ const checkAuth = async (context) => {
|
|
|
4211
4391
|
};
|
|
4212
4392
|
const checkWorkspace = async () => {
|
|
4213
4393
|
try {
|
|
4214
|
-
const { ensureLocalContext } = await import("./context-store-
|
|
4394
|
+
const { ensureLocalContext } = await import("./context-store-BuhPB0I9.mjs");
|
|
4215
4395
|
const localContext = await ensureLocalContext();
|
|
4216
4396
|
if (!localContext.workspaceId) return {
|
|
4217
4397
|
message: "No workspace selected (run `donebear workspace use`)",
|
|
@@ -4233,7 +4413,10 @@ const checkWorkspace = async () => {
|
|
|
4233
4413
|
};
|
|
4234
4414
|
const detectClaudeDesktop = (home) => {
|
|
4235
4415
|
if (process.platform === "darwin") return existsSync(join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"));
|
|
4236
|
-
if (process.platform === "win32")
|
|
4416
|
+
if (process.platform === "win32") {
|
|
4417
|
+
const appData = process.env.APPDATA ?? join(home, "AppData", "Roaming");
|
|
4418
|
+
return existsSync(join(appData, "Claude", "claude_desktop_config.json"));
|
|
4419
|
+
}
|
|
4237
4420
|
return existsSync(join(home, ".config", "claude", "config.json"));
|
|
4238
4421
|
};
|
|
4239
4422
|
const detectAgents = () => {
|
|
@@ -4648,6 +4831,8 @@ Not on \`task edit\`:
|
|
|
4648
4831
|
- completion \`donebear task done|reopen\`
|
|
4649
4832
|
- archiving \`donebear task archive\` (moves to Trash, not a delete)
|
|
4650
4833
|
|
|
4834
|
+
An API key bound to a task agent archives only its own assigned tasks, using the selected revision. A changed task returns a conflict; reload and review it before retrying. Pass \`-w <workspace>\` to select the intended workspace.
|
|
4835
|
+
|
|
4651
4836
|
Task numbers are positive integers scoped to the workspace. Projects, teams, and
|
|
4652
4837
|
labels also resolve by key or name.
|
|
4653
4838
|
|
|
@@ -4667,7 +4852,10 @@ thing that needs care.
|
|
|
4667
4852
|
2. Check for truncation. Results carry a count; if you passed \`--limit\`, you
|
|
4668
4853
|
are looking at a page, not the set.
|
|
4669
4854
|
3. Show the user the titles, not the ids, and get agreement before writing.
|
|
4670
|
-
4. Then write, one call per task.
|
|
4855
|
+
4. Then write, one call per task. Archive writes are not atomic: a later
|
|
4856
|
+
conflict leaves earlier tasks in Trash. Inspect Trash and re-read the failed
|
|
4857
|
+
task before retrying. After a timeout, reconcile current task state first;
|
|
4858
|
+
never retry the whole set blindly.
|
|
4671
4859
|
|
|
4672
4860
|
donebear task list --state open --view today --json --jq "[].{number: number, title: title}"
|
|
4673
4861
|
|
|
@@ -5141,11 +5329,14 @@ const summarizeThingsImportResult = (result) => {
|
|
|
5141
5329
|
`source: ${result.counts.source.tasks} tasks, ${result.counts.source.projects} projects, ${result.counts.source.headings} headings, ${result.counts.source.areas} areas, ${result.counts.source.tags} tags, ${result.counts.source.checklistItems} checklist items`,
|
|
5142
5330
|
`planned: ${result.counts.planned.teams ?? 0} teams, ${result.counts.planned.labels ?? 0} labels, ${result.counts.planned.projects ?? 0} projects, ${result.counts.planned.teamProjects ?? 0} team-project links, ${result.counts.planned.headings ?? 0} headings, ${result.counts.planned.tasks ?? 0} tasks, ${result.counts.planned.taskLabels ?? 0} task-label links, ${result.counts.planned.checklistItems ?? 0} checklist items`
|
|
5143
5331
|
];
|
|
5332
|
+
const { leftBehind } = result.counts;
|
|
5333
|
+
if (leftBehind.tasks > 0 || leftBehind.projects > 0) infoLines.push(`left behind (--open-only): ${leftBehind.tasks} to-dos, ${leftBehind.projects} projects`);
|
|
5144
5334
|
if (result.dryRun) infoLines.push("dry run: no mutations sent");
|
|
5145
5335
|
else infoLines.push(`mutations: ${result.execution.inserted} inserted, ${result.execution.updated} updated, ${result.execution.skipped} skipped across ${result.execution.batches} batch(es)`);
|
|
5146
5336
|
return {
|
|
5147
5337
|
infoLines,
|
|
5148
|
-
successMessage: result.dryRun ? "Things 3 import dry run complete" : "Things 3 import complete"
|
|
5338
|
+
successMessage: result.dryRun ? "Things 3 import dry run complete" : "Things 3 import complete",
|
|
5339
|
+
warningLines: result.issues.map((issue) => issue.message)
|
|
5149
5340
|
};
|
|
5150
5341
|
};
|
|
5151
5342
|
//#endregion
|
|
@@ -5221,6 +5412,40 @@ const getUtcCalendarDayEndEpoch = (date) => {
|
|
|
5221
5412
|
return Date.UTC(parts.year, parts.month - 1, parts.day, 23, 59, 59, 999);
|
|
5222
5413
|
};
|
|
5223
5414
|
//#endregion
|
|
5415
|
+
//#region ../../packages/import-core/src/issues.ts
|
|
5416
|
+
/**
|
|
5417
|
+
* Codes are part of the contract between a provider and the completion report:
|
|
5418
|
+
* the UI groups by code, so adding a message variant must not mean adding a
|
|
5419
|
+
* code.
|
|
5420
|
+
*/
|
|
5421
|
+
const ImportIssueCode = {
|
|
5422
|
+
/** The user chose to import without turning areas into teams. */
|
|
5423
|
+
AREAS_NOT_IMPORTED: "areas_not_imported",
|
|
5424
|
+
ASSIGNEE_UNRESOLVED: "assignee_unresolved",
|
|
5425
|
+
ATTACHMENT_NOT_TRANSFERRED: "attachment_not_transferred",
|
|
5426
|
+
DUPLICATE_SOURCE_ID: "duplicate_source_id",
|
|
5427
|
+
FILE_TOO_LARGE: "file_too_large",
|
|
5428
|
+
INVALID_DATE: "invalid_date",
|
|
5429
|
+
INVALID_RECORD: "invalid_record",
|
|
5430
|
+
/** Kept out of the plan because the user asked for open tasks only. */
|
|
5431
|
+
LEFT_BEHIND: "left_behind",
|
|
5432
|
+
MISSING_TITLE: "missing_title",
|
|
5433
|
+
PARENT_NOT_FOUND: "parent_not_found",
|
|
5434
|
+
RECURRENCE_APPROXIMATED: "recurrence_approximated",
|
|
5435
|
+
RECURRENCE_UNSUPPORTED: "recurrence_unsupported",
|
|
5436
|
+
TIMEZONE_UNKNOWN: "timezone_unknown",
|
|
5437
|
+
UNKNOWN_FILE_SHAPE: "unknown_file_shape",
|
|
5438
|
+
UNSUPPORTED_FIELD: "unsupported_field"
|
|
5439
|
+
};
|
|
5440
|
+
ImportIssueCode.AREAS_NOT_IMPORTED, ImportIssueCode.LEFT_BEHIND;
|
|
5441
|
+
const createIssue = (code, message, options = {}) => ({
|
|
5442
|
+
code,
|
|
5443
|
+
field: options.field ?? null,
|
|
5444
|
+
message,
|
|
5445
|
+
severity: options.severity ?? "warning",
|
|
5446
|
+
sourceRecordId: options.sourceRecordId ?? null
|
|
5447
|
+
});
|
|
5448
|
+
//#endregion
|
|
5224
5449
|
//#region ../../packages/import-core/src/things/ids.ts
|
|
5225
5450
|
const THINGS_IMPORT_ID_NAMESPACE = "b1f9f0ac-9f76-54ed-8eb0-7d6d3a0a0a31";
|
|
5226
5451
|
const createDeterministicImportId = (params) => createDeterministicId(THINGS_IMPORT_ID_NAMESPACE, `things:${params.workspaceId}:${params.modelName}:${params.sourceId}`);
|
|
@@ -5240,17 +5465,23 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
5240
5465
|
return (() => {
|
|
5241
5466
|
"use strict";
|
|
5242
5467
|
var __webpack_require__ = {};
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
};
|
|
5468
|
+
(() => {
|
|
5469
|
+
__webpack_require__.d = (exports$1, definition) => {
|
|
5470
|
+
for (var key in definition) if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports$1, key)) Object.defineProperty(exports$1, key, {
|
|
5471
|
+
enumerable: true,
|
|
5472
|
+
get: definition[key]
|
|
5473
|
+
});
|
|
5474
|
+
};
|
|
5475
|
+
})();
|
|
5476
|
+
(() => {
|
|
5477
|
+
__webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
|
|
5478
|
+
})();
|
|
5479
|
+
(() => {
|
|
5480
|
+
__webpack_require__.r = (exports$2) => {
|
|
5481
|
+
if (typeof Symbol !== "undefined" && Symbol.toStringTag) Object.defineProperty(exports$2, Symbol.toStringTag, { value: "Module" });
|
|
5482
|
+
Object.defineProperty(exports$2, "__esModule", { value: true });
|
|
5483
|
+
};
|
|
5484
|
+
})();
|
|
5254
5485
|
var __webpack_exports__ = {};
|
|
5255
5486
|
__webpack_require__.r(__webpack_exports__);
|
|
5256
5487
|
__webpack_require__.d(__webpack_exports__, {
|
|
@@ -5421,7 +5652,7 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
5421
5652
|
/**
|
|
5422
5653
|
* Number of milliseconds of one day
|
|
5423
5654
|
*/
|
|
5424
|
-
var ONE_DAY =
|
|
5655
|
+
var ONE_DAY = 864e5;
|
|
5425
5656
|
/**
|
|
5426
5657
|
* @see: <http://docs.python.org/library/datetime.html#datetime.MAXYEAR>
|
|
5427
5658
|
*/
|
|
@@ -5656,9 +5887,6 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
5656
5887
|
}
|
|
5657
5888
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
5658
5889
|
}
|
|
5659
|
-
function __await(v) {
|
|
5660
|
-
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
|
5661
|
-
}
|
|
5662
5890
|
const callbackiterresult = function(_super) {
|
|
5663
5891
|
__extends(CallbackIterResult, _super);
|
|
5664
5892
|
function CallbackIterResult(method, args, iterator) {
|
|
@@ -5872,9 +6100,10 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
5872
6100
|
ToText.prototype.WEEKLY = function() {
|
|
5873
6101
|
var gettext = this.gettext;
|
|
5874
6102
|
if (this.options.interval !== 1) this.add(this.options.interval.toString()).add(this.plural(this.options.interval) ? gettext("weeks") : gettext("week"));
|
|
5875
|
-
if (this.byweekday && this.byweekday.isWeekdays)
|
|
5876
|
-
|
|
5877
|
-
|
|
6103
|
+
if (this.byweekday && this.byweekday.isWeekdays) {
|
|
6104
|
+
if (this.options.interval === 1) this.add(this.plural(this.options.interval) ? gettext("weekdays") : gettext("weekday"));
|
|
6105
|
+
else this.add(gettext("on")).add(gettext("weekdays"));
|
|
6106
|
+
} else if (this.byweekday && this.byweekday.isEveryDay) this.add(this.plural(this.options.interval) ? gettext("days") : gettext("day"));
|
|
5878
6107
|
else {
|
|
5879
6108
|
if (this.options.interval === 1) this.add(gettext("week"));
|
|
5880
6109
|
if (this.origOptions.bymonth) {
|
|
@@ -5985,8 +6214,10 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
5985
6214
|
var delimJoin = function(array, delimiter, finalDelimiter) {
|
|
5986
6215
|
var list = "";
|
|
5987
6216
|
for (var i = 0; i < array.length; i++) {
|
|
5988
|
-
if (i !== 0)
|
|
5989
|
-
|
|
6217
|
+
if (i !== 0) {
|
|
6218
|
+
if (i === array.length - 1) list += " " + finalDelimiter + " ";
|
|
6219
|
+
else list += delimiter + " ";
|
|
6220
|
+
}
|
|
5990
6221
|
list += array[i];
|
|
5991
6222
|
}
|
|
5992
6223
|
return list;
|
|
@@ -6196,16 +6427,17 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
6196
6427
|
var nth = decodeNTH();
|
|
6197
6428
|
var wkd = decodeWKD();
|
|
6198
6429
|
var m = decodeM();
|
|
6199
|
-
if (nth)
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6430
|
+
if (nth) {
|
|
6431
|
+
if (wkd) {
|
|
6432
|
+
ttr.nextSymbol();
|
|
6433
|
+
if (!options.byweekday) options.byweekday = [];
|
|
6434
|
+
options.byweekday.push(RRule[wkd].nth(nth));
|
|
6435
|
+
} else {
|
|
6436
|
+
if (!options.bymonthday) options.bymonthday = [];
|
|
6437
|
+
options.bymonthday.push(nth);
|
|
6438
|
+
ttr.accept("day(s)");
|
|
6439
|
+
}
|
|
6440
|
+
} else if (wkd) {
|
|
6209
6441
|
ttr.nextSymbol();
|
|
6210
6442
|
if (!options.byweekday) options.byweekday = [];
|
|
6211
6443
|
options.byweekday.push(RRule[wkd]);
|
|
@@ -6611,9 +6843,7 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
6611
6843
|
case RRule.MONTHLY:
|
|
6612
6844
|
opts.bymonthday = opts.dtstart.getUTCDate();
|
|
6613
6845
|
break;
|
|
6614
|
-
case RRule.WEEKLY:
|
|
6615
|
-
opts.byweekday = [getWeekday(opts.dtstart)];
|
|
6616
|
-
break;
|
|
6846
|
+
case RRule.WEEKLY: opts.byweekday = [getWeekday(opts.dtstart)];
|
|
6617
6847
|
}
|
|
6618
6848
|
if (isPresent(opts.bymonth) && !isArray(opts.bymonth)) opts.bymonth = [opts.bymonth];
|
|
6619
6849
|
if (isPresent(opts.byyearday) && !isArray(opts.byyearday) && isNumber(opts.byyearday)) opts.byyearday = [opts.byyearday];
|
|
@@ -6645,14 +6875,15 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
6645
6875
|
} else if (isWeekdayStr(opts.byweekday)) {
|
|
6646
6876
|
opts.byweekday = [Weekday.fromStr(opts.byweekday).weekday];
|
|
6647
6877
|
opts.bynweekday = null;
|
|
6648
|
-
} else if (opts.byweekday instanceof Weekday)
|
|
6649
|
-
opts.byweekday
|
|
6650
|
-
|
|
6878
|
+
} else if (opts.byweekday instanceof Weekday) {
|
|
6879
|
+
if (!opts.byweekday.n || opts.freq > RRule.MONTHLY) {
|
|
6880
|
+
opts.byweekday = [opts.byweekday.weekday];
|
|
6881
|
+
opts.bynweekday = null;
|
|
6882
|
+
} else {
|
|
6883
|
+
opts.bynweekday = [[opts.byweekday.weekday, opts.byweekday.n]];
|
|
6884
|
+
opts.byweekday = null;
|
|
6885
|
+
}
|
|
6651
6886
|
} else {
|
|
6652
|
-
opts.bynweekday = [[opts.byweekday.weekday, opts.byweekday.n]];
|
|
6653
|
-
opts.byweekday = null;
|
|
6654
|
-
}
|
|
6655
|
-
else {
|
|
6656
6887
|
var byweekday = [];
|
|
6657
6888
|
var bynweekday = [];
|
|
6658
6889
|
for (var i = 0; i < opts.byweekday.length; i++) {
|
|
@@ -7071,12 +7302,13 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
7071
7302
|
nwdaymask: []
|
|
7072
7303
|
};
|
|
7073
7304
|
var ranges = [];
|
|
7074
|
-
if (options.freq === RRule.YEARLY)
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7305
|
+
if (options.freq === RRule.YEARLY) {
|
|
7306
|
+
if (empty(options.bymonth)) ranges = [[0, yearlen]];
|
|
7307
|
+
else for (var j = 0; j < options.bymonth.length; j++) {
|
|
7308
|
+
month = options.bymonth[j];
|
|
7309
|
+
ranges.push(mrange.slice(month - 1, month + 1));
|
|
7310
|
+
}
|
|
7311
|
+
} else if (options.freq === RRule.MONTHLY) ranges = [mrange.slice(month - 1, month + 1)];
|
|
7080
7312
|
if (empty(ranges)) return result;
|
|
7081
7313
|
result.nwdaymask = repeat(0, yearlen);
|
|
7082
7314
|
for (var j = 0; j < ranges.length; j++) {
|
|
@@ -7115,7 +7347,7 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
7115
7347
|
var month = Math.floor((h + l - 7 * m + 114) / 31);
|
|
7116
7348
|
var day = (h + l - 7 * m + 114) % 31 + 1;
|
|
7117
7349
|
var date = Date.UTC(y, month - 1, day + offset);
|
|
7118
|
-
return [Math.ceil((date - Date.UTC(y, 0, 1)) /
|
|
7350
|
+
return [Math.ceil((date - Date.UTC(y, 0, 1)) / 864e5)];
|
|
7119
7351
|
}
|
|
7120
7352
|
const iterinfo = function() {
|
|
7121
7353
|
function Iterinfo(options) {
|
|
@@ -7986,8 +8218,6 @@ var import_rrule = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expo
|
|
|
7986
8218
|
});
|
|
7987
8219
|
})))(), 1);
|
|
7988
8220
|
const PACKED_DATE_MULTIPLIER = 128;
|
|
7989
|
-
const PACKED_DATE_DAY_MASK = 31;
|
|
7990
|
-
const PACKED_DATE_MONTH_MASK = 15;
|
|
7991
8221
|
const PACKED_DATE_MONTH_SHIFT = 5;
|
|
7992
8222
|
const PACKED_DATE_YEAR_SHIFT = 9;
|
|
7993
8223
|
const THINGS_REMINDER_HOUR_SCALE = 67108864;
|
|
@@ -8008,8 +8238,8 @@ const decodeThingsPackedDateParts = (value) => {
|
|
|
8008
8238
|
if (integer <= 0 || integer % PACKED_DATE_MULTIPLIER !== 0) return null;
|
|
8009
8239
|
const packed = integer / PACKED_DATE_MULTIPLIER;
|
|
8010
8240
|
const parts = {
|
|
8011
|
-
day: packed %
|
|
8012
|
-
month: Math.trunc(packed / 2 ** PACKED_DATE_MONTH_SHIFT) %
|
|
8241
|
+
day: packed % 32,
|
|
8242
|
+
month: Math.trunc(packed / 2 ** PACKED_DATE_MONTH_SHIFT) % 16,
|
|
8013
8243
|
year: Math.trunc(packed / 2 ** PACKED_DATE_YEAR_SHIFT)
|
|
8014
8244
|
};
|
|
8015
8245
|
return isValidPackedDate(parts) ? parts : null;
|
|
@@ -8046,7 +8276,7 @@ const combineThingsReminderAt = (options) => {
|
|
|
8046
8276
|
//#endregion
|
|
8047
8277
|
//#region ../../packages/import-core/src/things/recurrence.ts
|
|
8048
8278
|
const { RRule, rrulestr } = typeof import_rrule.RRule === "function" ? import_rrule : import_rrule.default;
|
|
8049
|
-
const THINGS_FREQUENCY_TO_RRULE = new Map([
|
|
8279
|
+
const THINGS_FREQUENCY_TO_RRULE = /* @__PURE__ */ new Map([
|
|
8050
8280
|
[4, "YEARLY"],
|
|
8051
8281
|
[8, "MONTHLY"],
|
|
8052
8282
|
[16, "DAILY"],
|
|
@@ -8061,6 +8291,11 @@ const THINGS_WEEKDAY_TO_RRULE = [
|
|
|
8061
8291
|
"FR",
|
|
8062
8292
|
"SA"
|
|
8063
8293
|
];
|
|
8294
|
+
/**
|
|
8295
|
+
* Things writes `ed` as Unix seconds at UTC midnight, and uses 4001-01-01 as
|
|
8296
|
+
* "never ends". Anything at or past it is treated as no end date.
|
|
8297
|
+
*/
|
|
8298
|
+
const THINGS_NEVER_ENDS_SECONDS = 64092211200;
|
|
8064
8299
|
const parseNumericPlistField = (xml, key) => {
|
|
8065
8300
|
const match = new RegExp(`<key>${key}</key>\\s*<(integer|real)>([^<]+)</\\1>`, "u").exec(xml);
|
|
8066
8301
|
if (!match?.[2]) return null;
|
|
@@ -8096,7 +8331,8 @@ const buildByDayTokens = (definition) => {
|
|
|
8096
8331
|
const tokens = definition.offsets.map((offset) => typeof offset.wd === "number" ? THINGS_WEEKDAY_TO_RRULE[offset.wd] ?? null : null).filter((value) => value !== null);
|
|
8097
8332
|
if (tokens.length > 0) return tokens;
|
|
8098
8333
|
if (definition.ia === null) return [];
|
|
8099
|
-
|
|
8334
|
+
const weekday = (/* @__PURE__ */ new Date(definition.ia * 1e3)).getUTCDay();
|
|
8335
|
+
return [THINGS_WEEKDAY_TO_RRULE[weekday] ?? "MO"];
|
|
8100
8336
|
};
|
|
8101
8337
|
const buildMonthDays = (definition) => {
|
|
8102
8338
|
const monthDays = definition.offsets.map((offset) => typeof offset.dy === "number" ? Math.trunc(offset.dy) + 1 : null).filter((value) => value !== null && value >= 1);
|
|
@@ -8110,6 +8346,10 @@ const buildAnchor = (definition, task, timeZone) => {
|
|
|
8110
8346
|
if (!timeZone) return `DTSTART:${anchor}`;
|
|
8111
8347
|
return `DTSTART;TZID=${timeZone}:${anchor}`;
|
|
8112
8348
|
};
|
|
8349
|
+
/**
|
|
8350
|
+
* Builds the rule without any end. The end is appended separately because a
|
|
8351
|
+
* count has to be resolved against this exact rule first.
|
|
8352
|
+
*/
|
|
8113
8353
|
const buildRepeatRule = (definition, task, timeZone) => {
|
|
8114
8354
|
const frequency = definition.fu === null ? null : THINGS_FREQUENCY_TO_RRULE.get(definition.fu) ?? null;
|
|
8115
8355
|
if (!frequency) return null;
|
|
@@ -8161,21 +8401,77 @@ const parseThingsRecurrenceDefinition = (xml) => {
|
|
|
8161
8401
|
if (definition.ia === null && definition.sr === null && definition.ed === null && definition.rc === null && definition.fu === null && definition.fa === null && definition.tp === null && definition.ts === null && definition.offsets.length === 0) return null;
|
|
8162
8402
|
return definition;
|
|
8163
8403
|
};
|
|
8404
|
+
const resolveThingsRepeatEnd = (definition) => {
|
|
8405
|
+
const untilDate = definition.ed !== null && definition.ed < THINGS_NEVER_ENDS_SECONDS ? decodeThingsUnixCalendarDate(definition.ed, null) : null;
|
|
8406
|
+
const count = definition.rc !== null && definition.rc > 0 ? Math.trunc(definition.rc) : null;
|
|
8407
|
+
return untilDate === null && count === null ? null : {
|
|
8408
|
+
count,
|
|
8409
|
+
untilDate
|
|
8410
|
+
};
|
|
8411
|
+
};
|
|
8412
|
+
/**
|
|
8413
|
+
* Date-only epoch of the last occurrence a count allows, counted from the
|
|
8414
|
+
* rule's DTSTART, or null when the rule has no DTSTART to count from.
|
|
8415
|
+
*
|
|
8416
|
+
* COUNT itself is never written: iOS only honours COUNT <= 0, and rrule counts
|
|
8417
|
+
* from DTSTART while the rule anchor is Things' `ia`. Resolving the count here
|
|
8418
|
+
* against that same anchor and storing the result as UNTIL gives every
|
|
8419
|
+
* platform one date to agree on. `instanceCreationCount` is not used: it
|
|
8420
|
+
* counts instances Things has already made from that same anchor, so it does
|
|
8421
|
+
* not change which occurrence is last. It would matter only if `ia` had moved
|
|
8422
|
+
* since the count began, and nothing in the database records that.
|
|
8423
|
+
*/
|
|
8424
|
+
const resolveLastOccurrenceDate = (repeatRule, count) => {
|
|
8425
|
+
const parsed = parseRRule(`${repeatRule};COUNT=${count}`);
|
|
8426
|
+
if (!parsed?.origOptions.dtstart) return null;
|
|
8427
|
+
const last = parsed.all().at(-1);
|
|
8428
|
+
return last ? normalizeOccurrenceEpoch(last, getRuleTimeZone(parsed)) : null;
|
|
8429
|
+
};
|
|
8430
|
+
/**
|
|
8431
|
+
* The last day a fixed repeat may land on, from Things' end date and count
|
|
8432
|
+
* together (whichever comes first), or null when neither can be resolved.
|
|
8433
|
+
*/
|
|
8434
|
+
const resolveFixedUntilDate = (repeatRule, end) => {
|
|
8435
|
+
const lastByCount = end.count === null ? null : resolveLastOccurrenceDate(repeatRule, end.count);
|
|
8436
|
+
if (end.count !== null && lastByCount === null) return null;
|
|
8437
|
+
const candidates = [end.untilDate, lastByCount].filter((value) => value !== null);
|
|
8438
|
+
return candidates.length > 0 ? Math.min(...candidates) : null;
|
|
8439
|
+
};
|
|
8440
|
+
/**
|
|
8441
|
+
* UNTIL as the end of that calendar day. rrule compares it against the rule's
|
|
8442
|
+
* wall-clock occurrences, so this admits the last day in the rule's own zone,
|
|
8443
|
+
* and iOS reads only the date digits, so both stop on the same day.
|
|
8444
|
+
*
|
|
8445
|
+
* With a TZID the time is local, written without `Z`, because that is how
|
|
8446
|
+
* rrule serializes it and so how manage-api's `canonicalizeRepeatRule` stores
|
|
8447
|
+
* it. Writing `Z` there would have the server rewrite the rule on insert.
|
|
8448
|
+
*/
|
|
8449
|
+
const formatUntil = (untilDate, timeZone) => {
|
|
8450
|
+
const day = formatDateOnlyEpoch(untilDate);
|
|
8451
|
+
return day ? `UNTIL=${day.replaceAll("-", "")}T235959${timeZone ? "" : "Z"}` : null;
|
|
8452
|
+
};
|
|
8164
8453
|
const isThingsRepeatTemplateTask = (task) => task.recurrenceRuleRaw !== null && task.repeatTemplateSourceId === null;
|
|
8165
8454
|
const isThingsRepeatInstanceTask = (task) => task.repeatTemplateSourceId !== null;
|
|
8166
|
-
const resolveThingsRepeatTemplateFields = (task, timeZone) => {
|
|
8455
|
+
const resolveThingsRepeatTemplateFields = (task, timeZone, today) => {
|
|
8167
8456
|
if (!isThingsRepeatTemplateTask(task)) return null;
|
|
8168
8457
|
const recurrence = parseThingsRecurrenceDefinition(task.recurrenceRuleRaw);
|
|
8169
8458
|
if (!recurrence) return null;
|
|
8170
8459
|
const repeatTimeZone = resolveTimeZoneOrNull(timeZone);
|
|
8171
|
-
const
|
|
8172
|
-
if (!
|
|
8173
|
-
const currentInstanceDate = decodeThingsPackedCalendarDate(task.nextInstanceStartDate, repeatTimeZone) ?? decodeThingsPackedCalendarDate(task.startDate, repeatTimeZone) ?? decodeThingsUnixCalendarDate(recurrence.ia, repeatTimeZone) ?? decodeThingsUnixSeconds(recurrence.ia);
|
|
8460
|
+
const openRule = buildRepeatRule(recurrence, task, repeatTimeZone);
|
|
8461
|
+
if (!openRule) return null;
|
|
8174
8462
|
const repeatType = recurrence.tp === 1 ? "after_completion" : "fixed";
|
|
8463
|
+
const end = resolveThingsRepeatEnd(recurrence);
|
|
8464
|
+
const untilDate = end && repeatType === "fixed" ? resolveFixedUntilDate(openRule, end) : null;
|
|
8465
|
+
const until = untilDate === null ? null : formatUntil(untilDate, repeatTimeZone);
|
|
8466
|
+
const repeatRule = until ? `${openRule};${until}` : openRule;
|
|
8467
|
+
const currentInstanceDate = decodeThingsPackedCalendarDate(task.nextInstanceStartDate, repeatTimeZone) ?? decodeThingsPackedCalendarDate(task.startDate, repeatTimeZone) ?? decodeThingsUnixCalendarDate(recurrence.ia, repeatTimeZone) ?? decodeThingsUnixSeconds(recurrence.ia);
|
|
8468
|
+
const ended = untilDate !== null && (untilDate < today || currentInstanceDate !== null && currentInstanceDate > untilDate);
|
|
8175
8469
|
return {
|
|
8176
8470
|
currentInstanceDate,
|
|
8471
|
+
droppedEnd: until ? null : end,
|
|
8472
|
+
ended,
|
|
8177
8473
|
recurrence,
|
|
8178
|
-
repeatNextAt: repeatType === "fixed" ? calculateNextOccurrence(repeatRule, currentInstanceDate) : null,
|
|
8474
|
+
repeatNextAt: repeatType === "fixed" && !ended ? calculateNextOccurrence(repeatRule, currentInstanceDate) : null,
|
|
8179
8475
|
repeatRule,
|
|
8180
8476
|
repeatStatus: task.instanceCreationPaused || recurrence.ts === 1 ? "paused" : "active",
|
|
8181
8477
|
repeatType
|
|
@@ -8281,11 +8577,15 @@ const buildReminderAt = (task, startDate) => combineThingsReminderAt({
|
|
|
8281
8577
|
reminderTime: task.reminderTime,
|
|
8282
8578
|
startDate
|
|
8283
8579
|
});
|
|
8284
|
-
|
|
8580
|
+
/**
|
|
8581
|
+
* `fallback` is the import time. A trashed task with no dates of its own must
|
|
8582
|
+
* still get an `archivedAt`, or it lands outside Trash.
|
|
8583
|
+
*/
|
|
8584
|
+
const pickArchivedAt = (task, fallback) => {
|
|
8285
8585
|
const updatedAt = decodeThingsUnixSeconds(task.userModificationDate);
|
|
8286
8586
|
const completedAt = decodeThingsUnixSeconds(task.stopDate);
|
|
8287
8587
|
const createdAt = decodeThingsUnixSeconds(task.creationDate);
|
|
8288
|
-
if (task.trashed || task.status === 2) return updatedAt ?? completedAt ?? createdAt;
|
|
8588
|
+
if (task.trashed || task.status === 2) return updatedAt ?? completedAt ?? createdAt ?? fallback;
|
|
8289
8589
|
return null;
|
|
8290
8590
|
};
|
|
8291
8591
|
const pickCompletedAt = (task) => {
|
|
@@ -8438,7 +8738,7 @@ const buildTagTitleMap = (tags) => {
|
|
|
8438
8738
|
const buildChecklistMutation = (options) => {
|
|
8439
8739
|
const sourceId = options.sourceId ?? options.item.uuid;
|
|
8440
8740
|
const title = normalizeTitle(options.item.title, `Checklist item ${options.item.uuid.slice(0, 8)}`);
|
|
8441
|
-
const safeTitle = title.length > MAX_CHECKLIST_TITLE_LENGTH ? `${title.slice(0,
|
|
8741
|
+
const safeTitle = title.length > MAX_CHECKLIST_TITLE_LENGTH ? `${title.slice(0, 497)}...` : title;
|
|
8442
8742
|
return {
|
|
8443
8743
|
modelId: createDeterministicImportId({
|
|
8444
8744
|
modelName: "TaskChecklistItem",
|
|
@@ -8568,7 +8868,7 @@ const buildProjectActions = (ctx, sourceProjects) => {
|
|
|
8568
8868
|
modelId: projectId,
|
|
8569
8869
|
modelName: "Project",
|
|
8570
8870
|
payload: {
|
|
8571
|
-
archivedAt: pickArchivedAt(project),
|
|
8871
|
+
archivedAt: pickArchivedAt(project, ctx.createdAtFallback),
|
|
8572
8872
|
color: null,
|
|
8573
8873
|
completedAt: pickCompletedAt(project),
|
|
8574
8874
|
createdAt: decodeThingsUnixSeconds(project.creationDate) ?? ctx.createdAtFallback,
|
|
@@ -8646,7 +8946,7 @@ const buildHeadingActions = (ctx, sourceHeadings) => {
|
|
|
8646
8946
|
modelId: headingId,
|
|
8647
8947
|
modelName: "ProjectHeading",
|
|
8648
8948
|
payload: {
|
|
8649
|
-
archivedAt: pickArchivedAt(heading),
|
|
8949
|
+
archivedAt: pickArchivedAt(heading, ctx.createdAtFallback),
|
|
8650
8950
|
createdAt: decodeThingsUnixSeconds(heading.creationDate) ?? ctx.createdAtFallback,
|
|
8651
8951
|
projectId: parentProjectId,
|
|
8652
8952
|
sortOrder: sortValue(heading.sortIndex, headings.length),
|
|
@@ -8717,7 +9017,7 @@ const pushTaskMutation = (ctx, tasks, taskLabels, checklistItems, mutationOption
|
|
|
8717
9017
|
modelId: mutationOptions.taskId,
|
|
8718
9018
|
modelName: "Task",
|
|
8719
9019
|
payload: {
|
|
8720
|
-
archivedAt: pickArchivedAt(mutationOptions.task),
|
|
9020
|
+
archivedAt: pickArchivedAt(mutationOptions.task, ctx.createdAtFallback),
|
|
8721
9021
|
assigneeId: null,
|
|
8722
9022
|
completedAt: pickCompletedAt(mutationOptions.task),
|
|
8723
9023
|
createdAt,
|
|
@@ -8757,8 +9057,22 @@ const pushTaskMutation = (ctx, tasks, taskLabels, checklistItems, mutationOption
|
|
|
8757
9057
|
});
|
|
8758
9058
|
return createdAt;
|
|
8759
9059
|
};
|
|
9060
|
+
/**
|
|
9061
|
+
* The repeat is imported without its end, so it keeps going. Name the task in
|
|
9062
|
+
* the preview rather than let the user find out when it comes back.
|
|
9063
|
+
*/
|
|
9064
|
+
const buildRepeatEndIssue = (task, end, repeatType) => {
|
|
9065
|
+
const title = normalizeTitle(task.title, `Task ${task.uuid.slice(0, 8)}`);
|
|
9066
|
+
const until = formatDateOnlyEpoch(end.untilDate);
|
|
9067
|
+
const thingsEnd = until ? `stops repeating on ${until} in Things` : `repeats ${end.count} ${end.count === 1 ? "time" : "times"} in Things`;
|
|
9068
|
+
const outcome = repeatType === "after_completion" ? "In Done Bear it repeats after each completion with no end, so stop it by hand when you're done." : "That end won't carry over, so it keeps repeating in Done Bear.";
|
|
9069
|
+
return createIssue(ImportIssueCode.RECURRENCE_APPROXIMATED, `"${title}" ${thingsEnd}. ${outcome}`, {
|
|
9070
|
+
field: "repeatRule",
|
|
9071
|
+
sourceRecordId: task.uuid
|
|
9072
|
+
});
|
|
9073
|
+
};
|
|
8760
9074
|
const planRepeatTemplateTask = (ctx, task, taskId, openRepeatInstanceTemplateIds, tasks, taskLabels, checklistItems) => {
|
|
8761
|
-
const repeatTemplate = resolveThingsRepeatTemplateFields(task, ctx.timeZone);
|
|
9075
|
+
const repeatTemplate = resolveThingsRepeatTemplateFields(task, ctx.timeZone, ctx.todayDateOnly);
|
|
8762
9076
|
if (!repeatTemplate) {
|
|
8763
9077
|
pushTaskMutation(ctx, tasks, taskLabels, checklistItems, {
|
|
8764
9078
|
sourceId: task.uuid,
|
|
@@ -8767,6 +9081,8 @@ const planRepeatTemplateTask = (ctx, task, taskId, openRepeatInstanceTemplateIds
|
|
|
8767
9081
|
});
|
|
8768
9082
|
return;
|
|
8769
9083
|
}
|
|
9084
|
+
const archived = pickArchivedAt(task, ctx.createdAtFallback) !== null;
|
|
9085
|
+
if (repeatTemplate.droppedEnd && !archived) ctx.issues.push(buildRepeatEndIssue(task, repeatTemplate.droppedEnd, repeatTemplate.repeatType));
|
|
8770
9086
|
pushTaskMutation(ctx, tasks, taskLabels, checklistItems, {
|
|
8771
9087
|
repeat: {
|
|
8772
9088
|
repeatInstanceDate: null,
|
|
@@ -8780,7 +9096,7 @@ const planRepeatTemplateTask = (ctx, task, taskId, openRepeatInstanceTemplateIds
|
|
|
8780
9096
|
task,
|
|
8781
9097
|
taskId
|
|
8782
9098
|
});
|
|
8783
|
-
if (pickCompletedAt(task) !== null ||
|
|
9099
|
+
if (pickCompletedAt(task) !== null || archived || repeatTemplate.ended || openRepeatInstanceTemplateIds.has(task.uuid)) return;
|
|
8784
9100
|
const currentInstanceTaskId = createDeterministicImportId({
|
|
8785
9101
|
modelName: "Task",
|
|
8786
9102
|
sourceId: `${task.uuid}:current-instance`,
|
|
@@ -8819,7 +9135,7 @@ const buildTaskActions = (ctx, sourceTasks) => {
|
|
|
8819
9135
|
if (todayComparison !== 0) return todayComparison;
|
|
8820
9136
|
return a.uuid.localeCompare(b.uuid);
|
|
8821
9137
|
});
|
|
8822
|
-
const openRepeatInstanceTemplateIds = new Set(sourceTasks.filter(isThingsRepeatInstanceTask).filter((task) => pickCompletedAt(task) === null && pickArchivedAt(task) === null).map((task) => task.repeatTemplateSourceId).filter((value) => typeof value === "string"));
|
|
9138
|
+
const openRepeatInstanceTemplateIds = new Set(sourceTasks.filter(isThingsRepeatInstanceTask).filter((task) => pickCompletedAt(task) === null && pickArchivedAt(task, ctx.createdAtFallback) === null).map((task) => task.repeatTemplateSourceId).filter((value) => typeof value === "string"));
|
|
8823
9139
|
for (const task of sortedSourceTasks) ctx.taskIdBySourceId.set(task.uuid, createDeterministicImportId({
|
|
8824
9140
|
modelName: "Task",
|
|
8825
9141
|
sourceId: task.uuid,
|
|
@@ -8862,6 +9178,45 @@ const buildTaskActions = (ctx, sourceTasks) => {
|
|
|
8862
9178
|
tasks
|
|
8863
9179
|
};
|
|
8864
9180
|
};
|
|
9181
|
+
/**
|
|
9182
|
+
* Still to do in Things: not completed (status 3), not canceled (status 2),
|
|
9183
|
+
* not in the Trash. Canceled and trashed items would arrive in Done Bear's
|
|
9184
|
+
* Trash anyway, and someone asking for open items does not want them.
|
|
9185
|
+
*/
|
|
9186
|
+
const isThingsItemOpen = (task) => !task.trashed && task.status !== 2 && task.status !== 3;
|
|
9187
|
+
/**
|
|
9188
|
+
* Decides, once and here, what `openOnly` keeps:
|
|
9189
|
+
*
|
|
9190
|
+
* - To-dos: only open ones. That includes repeat templates (the rule lives on
|
|
9191
|
+
* them) and open repeat instances; completed instances are the Logbook
|
|
9192
|
+
* history of a repeat and are left behind. An open instance whose template
|
|
9193
|
+
* is left behind keeps its date but loses the link, since the template is
|
|
9194
|
+
* not written.
|
|
9195
|
+
* - Projects: an open project always comes; a finished one comes only when an
|
|
9196
|
+
* open to-do still lives in it (directly or under one of its headings), and
|
|
9197
|
+
* keeps its finished state. Its headings follow it.
|
|
9198
|
+
* - Checklist items follow their to-do, ticked ones included: they are part of
|
|
9199
|
+
* an open to-do's state and do not count as tasks.
|
|
9200
|
+
* - Areas and tags are not filtered.
|
|
9201
|
+
*/
|
|
9202
|
+
const selectOpenOnly = (sourceProjects, sourceHeadings, sourceTasks) => {
|
|
9203
|
+
const tasks = sourceTasks.filter(isThingsItemOpen);
|
|
9204
|
+
const projectIdByHeadingId = new Map(sourceHeadings.map((heading) => [heading.uuid, heading.projectId]));
|
|
9205
|
+
const projectIdsWithOpenTasks = /* @__PURE__ */ new Set();
|
|
9206
|
+
for (const task of tasks) {
|
|
9207
|
+
const projectId = task.projectId ?? (task.headingId === null ? null : projectIdByHeadingId.get(task.headingId) ?? null);
|
|
9208
|
+
if (projectId !== null) projectIdsWithOpenTasks.add(projectId);
|
|
9209
|
+
}
|
|
9210
|
+
const projects = sourceProjects.filter((project) => isThingsItemOpen(project) || projectIdsWithOpenTasks.has(project.uuid));
|
|
9211
|
+
return {
|
|
9212
|
+
leftBehind: {
|
|
9213
|
+
projects: sourceProjects.length - projects.length,
|
|
9214
|
+
tasks: sourceTasks.length - tasks.length
|
|
9215
|
+
},
|
|
9216
|
+
projects,
|
|
9217
|
+
tasks
|
|
9218
|
+
};
|
|
9219
|
+
};
|
|
8865
9220
|
const buildThingsImportPlan = (options) => {
|
|
8866
9221
|
const { snapshot, workspaceId, existingLabelsByTitle } = options;
|
|
8867
9222
|
const ctx = {
|
|
@@ -8875,6 +9230,7 @@ const buildThingsImportPlan = (options) => {
|
|
|
8875
9230
|
existingLabelIdByTitle: existingLabelsByTitle ?? /* @__PURE__ */ new Map(),
|
|
8876
9231
|
existingTeamIdByName: options.existingTeamsByName ?? /* @__PURE__ */ new Map(),
|
|
8877
9232
|
headingIdBySourceId: /* @__PURE__ */ new Map(),
|
|
9233
|
+
issues: [],
|
|
8878
9234
|
metadataMode: options.metadataMode,
|
|
8879
9235
|
projectIdBySourceId: /* @__PURE__ */ new Map(),
|
|
8880
9236
|
projectKeyCandidates: buildUniqueKeys(snapshot.tasks.filter((task) => classifyTaskKind(task) === "project"), {
|
|
@@ -8901,9 +9257,17 @@ const buildThingsImportPlan = (options) => {
|
|
|
8901
9257
|
const sourceProjects = snapshot.tasks.filter((task) => classifyTaskKind(task) === "project");
|
|
8902
9258
|
const sourceHeadings = snapshot.tasks.filter((task) => classifyTaskKind(task) === "heading");
|
|
8903
9259
|
const sourceTasks = snapshot.tasks.filter((task) => classifyTaskKind(task) === "task");
|
|
8904
|
-
const
|
|
9260
|
+
const selected = options.openOnly ? selectOpenOnly(sourceProjects, sourceHeadings, sourceTasks) : {
|
|
9261
|
+
leftBehind: {
|
|
9262
|
+
projects: 0,
|
|
9263
|
+
tasks: 0
|
|
9264
|
+
},
|
|
9265
|
+
projects: sourceProjects,
|
|
9266
|
+
tasks: sourceTasks
|
|
9267
|
+
};
|
|
9268
|
+
const { projectMemberships, projects, teamProjects } = buildProjectActions(ctx, selected.projects);
|
|
8905
9269
|
const headings = buildHeadingActions(ctx, sourceHeadings);
|
|
8906
|
-
const { tasks, taskLabels, checklistItems } = buildTaskActions(ctx,
|
|
9270
|
+
const { tasks, taskLabels, checklistItems } = buildTaskActions(ctx, selected.tasks);
|
|
8907
9271
|
const phases = [
|
|
8908
9272
|
{
|
|
8909
9273
|
items: teams,
|
|
@@ -8948,6 +9312,7 @@ const buildThingsImportPlan = (options) => {
|
|
|
8948
9312
|
];
|
|
8949
9313
|
return {
|
|
8950
9314
|
counts: {
|
|
9315
|
+
leftBehind: selected.leftBehind,
|
|
8951
9316
|
planned: Object.fromEntries(phases.map((phase) => [phase.name, phase.items.length])),
|
|
8952
9317
|
source: {
|
|
8953
9318
|
areaTagLinks: snapshot.areaTags.length,
|
|
@@ -8961,6 +9326,7 @@ const buildThingsImportPlan = (options) => {
|
|
|
8961
9326
|
}
|
|
8962
9327
|
},
|
|
8963
9328
|
databasePath: snapshot.databasePath,
|
|
9329
|
+
issues: ctx.issues,
|
|
8964
9330
|
phases,
|
|
8965
9331
|
taskColumns: snapshot.taskColumns
|
|
8966
9332
|
};
|
|
@@ -9251,7 +9617,7 @@ const loadThingsDatabase = (databaseUrl) => {
|
|
|
9251
9617
|
//#endregion
|
|
9252
9618
|
//#region src/import/things-runner.ts
|
|
9253
9619
|
const WORKSPACE_SCOPED_MODELS = SYNC_CONTENT_MODELS;
|
|
9254
|
-
const INSERT_ONLY_MODELS = new Set([
|
|
9620
|
+
const INSERT_ONLY_MODELS = /* @__PURE__ */ new Set([
|
|
9255
9621
|
"TaskLabel",
|
|
9256
9622
|
"TeamProject",
|
|
9257
9623
|
"TeamMembership",
|
|
@@ -9384,6 +9750,24 @@ const buildTransactions = (phaseItems, options) => {
|
|
|
9384
9750
|
updated
|
|
9385
9751
|
};
|
|
9386
9752
|
};
|
|
9753
|
+
const PLAN_LIMIT_PATTERN = /plan includes up to|upgrade to add more/iu;
|
|
9754
|
+
const LIMITED_MODELS = ["Task", "Team"];
|
|
9755
|
+
/**
|
|
9756
|
+
* Turns a batch the server partly refused for the plan limit into one clear
|
|
9757
|
+
* stop: how many tasks and teams landed before it, and what to do next. Any
|
|
9758
|
+
* other refusal is passed through unchanged.
|
|
9759
|
+
*/
|
|
9760
|
+
const explainPlanLimitStop = (error, context) => {
|
|
9761
|
+
if (!(error instanceof SyncMutationRejectedError) || !error.results.some((result) => !result.success && PLAN_LIMIT_PATTERN.test(result.error ?? ""))) return error;
|
|
9762
|
+
const landed = new Map(context.landed);
|
|
9763
|
+
const accepted = new Set(error.results.filter((result) => result.success).map((r) => r.clientTxId));
|
|
9764
|
+
for (const tx of context.batch) if (tx.action === "INSERT" && accepted.has(tx.clientTxId)) landed.set(tx.modelName, (landed.get(tx.modelName) ?? 0) + 1);
|
|
9765
|
+
const parts = LIMITED_MODELS.filter((model) => (context.planned.get(model) ?? 0) > 0).map((model) => {
|
|
9766
|
+
const noun = model === "Task" ? "tasks" : "teams";
|
|
9767
|
+
return `${landed.get(model) ?? 0} of ${context.planned.get(model) ?? 0} ${noun}`;
|
|
9768
|
+
});
|
|
9769
|
+
return new CliError(`Import stopped: the workspace reached its plan limit. Imported ${parts.join(" and ")} before it stopped.`, EXIT_CODES.ERROR, "Upgrade the workspace, or run again with --open-only (and --skip-teams if the team limit was hit). A re-run skips what already landed.");
|
|
9770
|
+
};
|
|
9387
9771
|
const executeImportPlan = async (options, plan, rowsByModel) => {
|
|
9388
9772
|
const execution = {
|
|
9389
9773
|
batches: 0,
|
|
@@ -9392,25 +9776,37 @@ const executeImportPlan = async (options, plan, rowsByModel) => {
|
|
|
9392
9776
|
updated: 0
|
|
9393
9777
|
};
|
|
9394
9778
|
const existingIds = buildExistingIds(rowsByModel);
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9779
|
+
const prepared = plan.phases.map((phase) => buildTransactions(phase.items, {
|
|
9780
|
+
clientId: options.commandContext.localContext.clientId,
|
|
9781
|
+
existingIds
|
|
9782
|
+
}));
|
|
9783
|
+
const planned = /* @__PURE__ */ new Map();
|
|
9784
|
+
for (const tx of prepared.flatMap((phase) => phase.transactions)) if (tx.action === "INSERT") planned.set(tx.modelName, (planned.get(tx.modelName) ?? 0) + 1);
|
|
9785
|
+
const landed = /* @__PURE__ */ new Map();
|
|
9786
|
+
for (const phasePrepared of prepared) {
|
|
9787
|
+
execution.inserted += phasePrepared.inserted;
|
|
9788
|
+
execution.updated += phasePrepared.updated;
|
|
9789
|
+
execution.skipped += phasePrepared.skipped;
|
|
9790
|
+
const transactionBatches = chunk(phasePrepared.transactions, options.batchSize);
|
|
9404
9791
|
for (const transactionBatch of transactionBatches) {
|
|
9405
9792
|
if (transactionBatch.length === 0) continue;
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9793
|
+
try {
|
|
9794
|
+
await mutateModel(options.context, {
|
|
9795
|
+
baseUrl: options.commandContext.apiBaseUrl,
|
|
9796
|
+
request: {
|
|
9797
|
+
batchId: randomUUID(),
|
|
9798
|
+
transactions: transactionBatch
|
|
9799
|
+
},
|
|
9800
|
+
token: options.commandContext.token
|
|
9801
|
+
});
|
|
9802
|
+
} catch (error) {
|
|
9803
|
+
throw explainPlanLimitStop(error, {
|
|
9804
|
+
batch: transactionBatch,
|
|
9805
|
+
landed,
|
|
9806
|
+
planned
|
|
9807
|
+
});
|
|
9808
|
+
}
|
|
9809
|
+
for (const tx of transactionBatch) if (tx.action === "INSERT") landed.set(tx.modelName, (landed.get(tx.modelName) ?? 0) + 1);
|
|
9414
9810
|
execution.batches += 1;
|
|
9415
9811
|
}
|
|
9416
9812
|
}
|
|
@@ -9454,6 +9850,7 @@ const runThingsImport = async (options) => {
|
|
|
9454
9850
|
existingLabelsByTitle: existingState.existingLabelIdsByTitle,
|
|
9455
9851
|
existingTeamsByName: existingState.existingTeamIdsByName,
|
|
9456
9852
|
metadataMode: options.metadataMode,
|
|
9853
|
+
openOnly: options.openOnly,
|
|
9457
9854
|
skipTeams: options.skipTeams,
|
|
9458
9855
|
snapshot,
|
|
9459
9856
|
timeZone: resolveLocalTimeZone(),
|
|
@@ -9469,6 +9866,7 @@ const runThingsImport = async (options) => {
|
|
|
9469
9866
|
skipped: 0,
|
|
9470
9867
|
updated: 0
|
|
9471
9868
|
},
|
|
9869
|
+
issues: plan.issues,
|
|
9472
9870
|
taskColumns: plan.taskColumns,
|
|
9473
9871
|
workspace: options.workspace
|
|
9474
9872
|
};
|
|
@@ -9479,6 +9877,7 @@ const runThingsImport = async (options) => {
|
|
|
9479
9877
|
databasePath: plan.databasePath,
|
|
9480
9878
|
dryRun: false,
|
|
9481
9879
|
execution,
|
|
9880
|
+
issues: plan.issues,
|
|
9482
9881
|
taskColumns: plan.taskColumns,
|
|
9483
9882
|
workspace: options.workspace
|
|
9484
9883
|
};
|
|
@@ -9508,6 +9907,7 @@ const runThingsImportCommand = async (context, options, workspaceOverride) => {
|
|
|
9508
9907
|
databaseUrl: options.databaseUrl,
|
|
9509
9908
|
dryRun: context.dryRun,
|
|
9510
9909
|
metadataMode,
|
|
9910
|
+
openOnly: options.openOnly,
|
|
9511
9911
|
skipTeams: options.skipTeams,
|
|
9512
9912
|
workspace
|
|
9513
9913
|
});
|
|
@@ -9519,9 +9919,10 @@ const runThingsImportCommand = async (context, options, workspaceOverride) => {
|
|
|
9519
9919
|
const summary = summarizeThingsImportResult(result);
|
|
9520
9920
|
writeSuccess(context, summary.successMessage);
|
|
9521
9921
|
for (const line of summary.infoLines) writeInfo(context, line);
|
|
9922
|
+
for (const line of summary.warningLines) writeWarning(context, line);
|
|
9522
9923
|
};
|
|
9523
9924
|
const registerImportCommands = (program, workspaceOverride) => {
|
|
9524
|
-
program.command("import").description("Import external data into Done Bear").command("things").description("Import a Things 3 SQLite export").option("--database-url <database-url>", "Path, file:// URL, sqlite:// URL, .thingsdatabase bundle, or ThingsData-* directory").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("--skip-teams", "Skip importing Things Areas as Teams and TeamProject links", false).option("--metadata-mode <mode>", "How to preserve Things-only metadata: note|none", "none").option("--batch-size <count>", "Maximum sync mutations per batch", (value) => parsePositiveInteger(value, "Batch size"), 100).addHelpText("after", `
|
|
9925
|
+
program.command("import").description("Import external data into Done Bear").command("things").description("Import a Things 3 SQLite export").option("--database-url <database-url>", "Path, file:// URL, sqlite:// URL, .thingsdatabase bundle, or ThingsData-* directory").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("--skip-teams", "Skip importing Things Areas as Teams and TeamProject links", false).option("--open-only", "Import only open to-dos; leave completed, canceled and trashed ones behind", false).option("--metadata-mode <mode>", "How to preserve Things-only metadata: note|none", "none").option("--batch-size <count>", "Maximum sync mutations per batch", (value) => parsePositiveInteger(value, "Batch size"), 100).addHelpText("after", `
|
|
9525
9926
|
Examples:
|
|
9526
9927
|
donebear import things --dry-run --json
|
|
9527
9928
|
donebear import things --database-url ~/Backups/Things\\ Database.thingsdatabase --json
|
|
@@ -9529,6 +9930,7 @@ Examples:
|
|
|
9529
9930
|
|
|
9530
9931
|
Notes:
|
|
9531
9932
|
--database-url accepts a copied Things 3 export bundle, a direct main.sqlite path, or a ThingsData-* directory.
|
|
9933
|
+
--open-only leaves the Logbook behind. Completed to-dos count toward the Free plan's task limit.
|
|
9532
9934
|
--metadata-mode note preserves Things-only fields in task/project/team descriptions using a structured appendix.
|
|
9533
9935
|
`).action(async (options, command) => {
|
|
9534
9936
|
const context = contextFromCommand(command);
|
|
@@ -9601,11 +10003,15 @@ const runLabelList = async (context, options) => {
|
|
|
9601
10003
|
}
|
|
9602
10004
|
writeInfo(context, `${scopeLabel(scope)} (${sorted.length} label(s))`);
|
|
9603
10005
|
for (const labelRecord of sorted) writeInfo(context, `${labelRecord.id.slice(0, 8)} ${labelRecord.title}`);
|
|
9604
|
-
if (context.copy)
|
|
10006
|
+
if (context.copy) {
|
|
10007
|
+
const text = sorted.map((l) => `${l.id.slice(0, 8)} ${l.title}`).join("\n");
|
|
10008
|
+
copyToClipboard(text);
|
|
10009
|
+
}
|
|
9605
10010
|
};
|
|
9606
10011
|
const runLabelShow = async (context, labelRef, options) => {
|
|
10012
|
+
const commandContext = await resolveCommandContext(context);
|
|
9607
10013
|
const label = await resolveLabel(context, {
|
|
9608
|
-
commandContext
|
|
10014
|
+
commandContext,
|
|
9609
10015
|
labelRef,
|
|
9610
10016
|
workspaceRef: normalizeWorkspaceRef(options.workspace)
|
|
9611
10017
|
});
|
|
@@ -9888,7 +10294,7 @@ const runProjectList = async (context, options) => {
|
|
|
9888
10294
|
token: commandContext.token,
|
|
9889
10295
|
workspaceRef: normalizeWorkspaceRef(options.workspace)
|
|
9890
10296
|
});
|
|
9891
|
-
const
|
|
10297
|
+
const allProjects = scope.mode === "all" ? await loadProjectsByWorkspaceIds(context, {
|
|
9892
10298
|
baseUrl: commandContext.apiBaseUrl,
|
|
9893
10299
|
token: commandContext.token,
|
|
9894
10300
|
workspaceIds: scope.workspaces.map((w) => w.id)
|
|
@@ -9896,7 +10302,8 @@ const runProjectList = async (context, options) => {
|
|
|
9896
10302
|
baseUrl: commandContext.apiBaseUrl,
|
|
9897
10303
|
token: commandContext.token,
|
|
9898
10304
|
workspaceId: scope.workspace.id
|
|
9899
|
-
})
|
|
10305
|
+
});
|
|
10306
|
+
const filtered = filterProjects(sortProjectsByUpdatedDesc(allProjects), requestedStatus);
|
|
9900
10307
|
if (context.json) {
|
|
9901
10308
|
writeOutput(context, {
|
|
9902
10309
|
...scopeJsonFields(scope),
|
|
@@ -9935,11 +10342,15 @@ const runProjectList = async (context, options) => {
|
|
|
9935
10342
|
const suffix = target ? ` due ${target}` : "";
|
|
9936
10343
|
writeInfo(context, `${project.id.slice(0, 8)} [${project.key}] ${project.name} (${status})${suffix}`);
|
|
9937
10344
|
}
|
|
9938
|
-
if (context.copy)
|
|
10345
|
+
if (context.copy) {
|
|
10346
|
+
const text = filtered.map((p) => `${p.id.slice(0, 8)} [${p.key}] ${p.name} (${getProjectStatus(p)})`).join("\n");
|
|
10347
|
+
copyToClipboard(text);
|
|
10348
|
+
}
|
|
9939
10349
|
};
|
|
9940
10350
|
const runProjectShow = async (context, projectRef, options) => {
|
|
10351
|
+
const commandContext = await resolveCommandContext(context);
|
|
9941
10352
|
const { project, workspace } = await resolveProjectReference(context, {
|
|
9942
|
-
commandContext
|
|
10353
|
+
commandContext,
|
|
9943
10354
|
projectRef,
|
|
9944
10355
|
workspaceRef: normalizeWorkspaceRef(options.workspace)
|
|
9945
10356
|
});
|
|
@@ -9971,7 +10382,7 @@ const resolveProjectName = async (context, rawName) => {
|
|
|
9971
10382
|
const trimmed = rawName.trim();
|
|
9972
10383
|
if (trimmed.length > 0) return trimmed;
|
|
9973
10384
|
}
|
|
9974
|
-
const { promptForValue } = await import("./prompt
|
|
10385
|
+
const { promptForValue } = await import("./prompt-ujp1V6r9.mjs");
|
|
9975
10386
|
const prompted = await promptForValue(context, "Project name");
|
|
9976
10387
|
if (prompted) return prompted;
|
|
9977
10388
|
throw new CliError("Project name is required.", EXIT_CODES.VALIDATION, "Provide a name: donebear project add \"My project\"");
|
|
@@ -10216,10 +10627,7 @@ const runProjectMembershipChange = async (context, projectRef, options) => {
|
|
|
10216
10627
|
case "already_member":
|
|
10217
10628
|
writeInfo(context, `Already in project: ${project.name}`);
|
|
10218
10629
|
break;
|
|
10219
|
-
case "not_a_member":
|
|
10220
|
-
writeInfo(context, `Not in project: ${project.name}`);
|
|
10221
|
-
break;
|
|
10222
|
-
default: break;
|
|
10630
|
+
case "not_a_member": writeInfo(context, `Not in project: ${project.name}`);
|
|
10223
10631
|
}
|
|
10224
10632
|
};
|
|
10225
10633
|
const registerProjectCommands = (program, workspaceOverride) => {
|
|
@@ -10354,7 +10762,7 @@ const runSearch = async (context, query, options, workspaceOverride) => {
|
|
|
10354
10762
|
token: commandContext.token,
|
|
10355
10763
|
workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
|
|
10356
10764
|
});
|
|
10357
|
-
const
|
|
10765
|
+
const tasks = scope.mode === "all" ? await loadTasksByWorkspaceIds(context, {
|
|
10358
10766
|
baseUrl: commandContext.apiBaseUrl,
|
|
10359
10767
|
token: commandContext.token,
|
|
10360
10768
|
workspaceIds: scope.workspaces.map((w) => w.id)
|
|
@@ -10362,7 +10770,8 @@ const runSearch = async (context, query, options, workspaceOverride) => {
|
|
|
10362
10770
|
baseUrl: commandContext.apiBaseUrl,
|
|
10363
10771
|
token: commandContext.token,
|
|
10364
10772
|
workspaceId: scope.workspace.id
|
|
10365
|
-
})
|
|
10773
|
+
});
|
|
10774
|
+
const filtered = filterTasks(tasks, {
|
|
10366
10775
|
search: trimmedQuery,
|
|
10367
10776
|
state: requestedState,
|
|
10368
10777
|
view: null
|
|
@@ -10965,6 +11374,10 @@ const COMMAND_SPECS = {
|
|
|
10965
11374
|
enum: ["note", "none"],
|
|
10966
11375
|
type: "string"
|
|
10967
11376
|
},
|
|
11377
|
+
"--open-only": {
|
|
11378
|
+
description: "Import only open to-dos; completed, canceled and trashed ones are left behind",
|
|
11379
|
+
type: "boolean"
|
|
11380
|
+
},
|
|
10968
11381
|
"--skip-teams": {
|
|
10969
11382
|
description: "Skip importing Things Areas as Teams and TeamProject links",
|
|
10970
11383
|
type: "boolean"
|
|
@@ -11893,7 +12306,7 @@ const runToday = async (context, options, workspaceOverride) => {
|
|
|
11893
12306
|
token: commandContext.token,
|
|
11894
12307
|
workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
|
|
11895
12308
|
});
|
|
11896
|
-
const
|
|
12309
|
+
const tasks = scope.mode === "all" ? await loadTasksByWorkspaceIds(context, {
|
|
11897
12310
|
baseUrl: commandContext.apiBaseUrl,
|
|
11898
12311
|
token: commandContext.token,
|
|
11899
12312
|
workspaceIds: scope.workspaces.map((w) => w.id)
|
|
@@ -11901,7 +12314,8 @@ const runToday = async (context, options, workspaceOverride) => {
|
|
|
11901
12314
|
baseUrl: commandContext.apiBaseUrl,
|
|
11902
12315
|
token: commandContext.token,
|
|
11903
12316
|
workspaceId: scope.workspace.id
|
|
11904
|
-
})
|
|
12317
|
+
});
|
|
12318
|
+
const todayTasks = filterToday(tasks);
|
|
11905
12319
|
const limited = todayTasks.slice(0, options.limit);
|
|
11906
12320
|
if (context.json) {
|
|
11907
12321
|
writeOutput(context, {
|
|
@@ -12026,7 +12440,10 @@ const runWorkspaceList = async (context) => {
|
|
|
12026
12440
|
return;
|
|
12027
12441
|
}
|
|
12028
12442
|
writeInfo(context, "Workspaces:");
|
|
12029
|
-
for (const workspace of workspaces)
|
|
12443
|
+
for (const workspace of workspaces) {
|
|
12444
|
+
const marker = workspace.id === commandContext.localContext.workspaceId ? "*" : " ";
|
|
12445
|
+
writeInfo(context, `${marker} ${workspace.id} ${workspace.name}${workspace.urlKey ? ` (${workspace.urlKey})` : ""} role=${workspace.role}`);
|
|
12446
|
+
}
|
|
12030
12447
|
};
|
|
12031
12448
|
const runWorkspaceCurrent = async (context) => {
|
|
12032
12449
|
const commandContext = await resolveCommandContext(context);
|
|
@@ -12053,10 +12470,11 @@ const runWorkspaceCurrent = async (context) => {
|
|
|
12053
12470
|
};
|
|
12054
12471
|
const runWorkspaceUse = async (context, reference) => {
|
|
12055
12472
|
const commandContext = await resolveCommandContext(context);
|
|
12056
|
-
const
|
|
12473
|
+
const workspaces = await listWorkspaces(context, {
|
|
12057
12474
|
baseUrl: commandContext.apiBaseUrl,
|
|
12058
12475
|
token: commandContext.token
|
|
12059
|
-
})
|
|
12476
|
+
});
|
|
12477
|
+
const selected = findWorkspace(reference, workspaces);
|
|
12060
12478
|
if (!selected) throw new Error(`Workspace "${reference}" not found. Run \`donebear workspace list\`.`);
|
|
12061
12479
|
await setCurrentWorkspace(selected.id);
|
|
12062
12480
|
if (context.json) {
|
|
@@ -12411,7 +12829,8 @@ const main = async () => {
|
|
|
12411
12829
|
const hasUserArgs = processedArgv.length <= 2;
|
|
12412
12830
|
const isTTY = process.stdin.isTTY === true;
|
|
12413
12831
|
if (hasUserArgs && isTTY) {
|
|
12414
|
-
|
|
12832
|
+
const ttyContext = contextFromArgv(processedArgv);
|
|
12833
|
+
await runInteractiveMode(ttyContext, program);
|
|
12415
12834
|
return;
|
|
12416
12835
|
}
|
|
12417
12836
|
await program.parseAsync(processedArgv);
|