donebear 0.4.4 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as toCliError, d as DEFAULT_APP_URL, f as DEFAULT_OAUTH_CALLBACK_PATH, i as EXIT_CODES, l as CONFIG_DIR, n as setCurrentWorkspace, o as AUTH_FILE_PATH, p as DEFAULT_OAUTH_CALLBACK_PORT, r as CliError, s as CLI_NAME, t as ensureLocalContext, x as DONEBEAR_TOKEN_ENV } from "./context-store-BbZMKY0F.mjs";
3
- import { n as readStdin, t as promptForValue } from "./prompt-B66eG-Zm.mjs";
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-CdRuvvVL.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) if (char === quoteChar) inQuote = false;
179
- else current += char;
180
- else if (char === "\"" || char === "'") {
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(-MAX_HISTORY),
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
- return parseAuthFile(await readFile(AUTH_FILE_PATH, "utf-8")).session;
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;
@@ -393,7 +393,7 @@ const clearStoredAuthSession = async () => {
393
393
  //#endregion
394
394
  //#region src/supabase.ts
395
395
  const resolveSupabaseConfig = () => {
396
- const url = readEnvValue("DONEBEAR_SUPABASE_URL") ?? "https://kdarvputxmlxqemisnbh.supabase.co";
396
+ const url = readEnvValue("DONEBEAR_SUPABASE_URL") ?? "https://auth.donebear.com";
397
397
  return {
398
398
  publishableKey: readEnvValue("DONEBEAR_SUPABASE_PUBLISHABLE_KEY") ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtkYXJ2cHV0eG1seHFlbWlzbmJoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzIzNTcyNjcsImV4cCI6MjA4NzkzMzI2N30.A8sDPWNe_1EcCy0-GsUw4h1icxSGz_VDc9E9tPTYEU8",
399
399
  url
@@ -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 = 1440 * 60 * 1e3;
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 rows = await requestNdjson(context, `${options.baseUrl}/sync/batch`, {
1554
- body: { requests: [{
1555
- indexedKey: "taskId",
1556
- keyValue: options.taskId,
1557
- modelName: "Comment"
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 rows = await requestNdjson(context, `${options.baseUrl}/sync/batch`, {
1570
- body: { requests: [{
1571
- indexedKey: "taskId",
1572
- keyValue: options.taskId,
1573
- modelName: "TaskAttachment"
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
- return normalizeApiUrl(readEnvValue("DONEBEAR_API_URL") ?? "https://api.donebear.com");
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
- return toWorkspaceRecordFromGraphqlNode((await requestGraphql(context, options.baseUrl, {
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
- })).workspaceCreate.workspace);
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 rows = await requestNdjson(context, `${options.baseUrl}/sync/batch`, {
1658
- body: { requests: [{
1659
- indexedKey: "id",
1660
- keyValue: options.taskId,
1661
- modelName: "Task"
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 rows = await requestNdjson(context, `${options.baseUrl}/sync/batch`, {
1721
- body: { requests: [{
1722
- indexedKey: "taskId",
1723
- keyValue: options.taskId,
1724
- modelName: "TaskChecklistItem"
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") return new CliError(failure.response.results.find((item) => !item.success)?.error ?? "Sync mutation failed", EXIT_CODES.ERROR);
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
- return toWorkspaceInvitationRecordFromGraphqlNode((await requestGraphql(context, options.baseUrl, {
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
- })).workspaceInvitationCreate.invitation);
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: resolveApiBaseUrl(context),
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: resolveApiBaseUrl(context),
2217
+ baseUrl,
2073
2218
  keyId,
2074
2219
  token
2075
2220
  });
@@ -2159,8 +2304,8 @@ const openUrl = async (url) => {
2159
2304
  //#endregion
2160
2305
  //#region src/command-parsers.ts
2161
2306
  const parsePositiveInteger = (value, label) => {
2162
- const parsed = Number.parseInt(value, 10);
2163
- if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${label} must be a positive integer`);
2307
+ const parsed = Number(value);
2308
+ if (!/^\d+$/u.test(value) || !Number.isSafeInteger(parsed) || parsed <= 0) throw new Error(`${label} must be a positive integer`);
2164
2309
  return parsed;
2165
2310
  };
2166
2311
  //#endregion
@@ -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 { authorizeUrl, tokenUrl } = buildOAuthUrls(resolveSupabaseConfig());
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 storedSession = tokenResponseToStoredSession(await exchangeAuthorizationCode({
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 token = await resolveAuthToken(context, getOptionalSupabaseConfig());
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 TypeError(`Invalid task number "${normalized}". Number is too large.`);
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
- await mutateModel(context, {
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: payload.data
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
- return resolveEntityId(await loadProjectsByWorkspace(context, {
3378
+ const projects = await loadProjectsByWorkspace(context, {
3217
3379
  baseUrl: commandContext.apiBaseUrl,
3218
3380
  token: commandContext.token,
3219
3381
  workspaceId: workspace.id
3220
- }), projectRef, "Project");
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
- return resolveEntityId(await loadTeamsByWorkspace(context, {
3392
+ const teams = await loadTeamsByWorkspace(context, {
3230
3393
  baseUrl: commandContext.apiBaseUrl,
3231
3394
  token: commandContext.token,
3232
3395
  workspaceId: workspace.id
3233
- }), teamRef, "Team");
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: await resolveCommandContext(context),
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: `${(await resolveTaskReference(context, {
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: await resolveCommandContext(context),
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: existing.length > 0 ? `${text.trim()}\n${existing}` : text.trim(),
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 filtered = filterTasks(await loadTasksByWorkspace(context, {
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) writeInfo(context, `${item.completedAt === null ? "[ ]" : "[x]"} ${item.id.slice(0, 8)} ${item.title}`);
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 item = await resolveChecklistItemReference(context, commandContext, (await resolveTaskReference(context, {
3774
+ const resolvedTask = await resolveTaskReference(context, {
3602
3775
  commandContext,
3603
3776
  taskRef: taskId,
3604
3777
  workspaceRef: options.workspace?.trim() ?? null
3605
- })).id, itemId);
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 item = await resolveChecklistItemReference(context, commandContext, (await resolveTaskReference(context, {
3813
+ const resolvedTask = await resolveTaskReference(context, {
3640
3814
  commandContext,
3641
3815
  taskRef: taskId,
3642
3816
  workspaceRef: options.workspace?.trim() ?? null
3643
- })).id, itemId);
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 item = await resolveChecklistItemReference(context, commandContext, (await resolveTaskReference(context, {
3854
+ const resolvedTask = await resolveTaskReference(context, {
3680
3855
  commandContext,
3681
3856
  taskRef: taskId,
3682
3857
  workspaceRef: options.workspace?.trim() ?? null
3683
- })).id, itemId);
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 item = await resolveChecklistItemReference(context, commandContext, (await resolveTaskReference(context, {
3893
+ const resolvedTask = await resolveTaskReference(context, {
3718
3894
  commandContext,
3719
3895
  taskRef: taskId,
3720
3896
  workspaceRef: options.workspace?.trim() ?? null
3721
- })).id, itemId);
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
- registerChecklistSubcommands(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) => {
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 existing = await resolveCommentReference(context, commandContext, (await resolveTaskReference(context, {
4121
+ const task = await resolveTaskReference(context, {
3944
4122
  commandContext,
3945
4123
  taskRef,
3946
4124
  workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
3947
- })).id, commentRef);
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 existing = await resolveCommentReference(context, commandContext, (await resolveTaskReference(context, {
4162
+ const task = await resolveTaskReference(context, {
3984
4163
  commandContext,
3985
4164
  taskRef,
3986
4165
  workspaceRef: workspaceOverride ?? normalizeWorkspaceRef(options.workspace)
3987
- })).id, commentRef);
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 token = await resolveAuthToken(context, (() => {
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-D8zfa5LS.mjs");
4394
+ const { ensureLocalContext } = await import("./context-store-CG0wdjGM.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") return existsSync(join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json"));
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
- __webpack_require__.d = (exports$1, definition) => {
5244
- for (var key in definition) if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports$1, key)) Object.defineProperty(exports$1, key, {
5245
- enumerable: true,
5246
- get: definition[key]
5247
- });
5248
- };
5249
- __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
5250
- __webpack_require__.r = (exports$2) => {
5251
- if (typeof Symbol !== "undefined" && Symbol.toStringTag) Object.defineProperty(exports$2, Symbol.toStringTag, { value: "Module" });
5252
- Object.defineProperty(exports$2, "__esModule", { value: true });
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 = 1e3 * 60 * 60 * 24;
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) if (this.options.interval === 1) this.add(this.plural(this.options.interval) ? gettext("weekdays") : gettext("weekday"));
5876
- else this.add(gettext("on")).add(gettext("weekdays"));
5877
- else if (this.byweekday && this.byweekday.isEveryDay) this.add(this.plural(this.options.interval) ? gettext("days") : gettext("day"));
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) if (i === array.length - 1) list += " " + finalDelimiter + " ";
5989
- else list += delimiter + " ";
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) if (wkd) {
6200
- ttr.nextSymbol();
6201
- if (!options.byweekday) options.byweekday = [];
6202
- options.byweekday.push(RRule[wkd].nth(nth));
6203
- } else {
6204
- if (!options.bymonthday) options.bymonthday = [];
6205
- options.bymonthday.push(nth);
6206
- ttr.accept("day(s)");
6207
- }
6208
- else if (wkd) {
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) if (!opts.byweekday.n || opts.freq > RRule.MONTHLY) {
6649
- opts.byweekday = [opts.byweekday.weekday];
6650
- opts.bynweekday = null;
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) if (empty(options.bymonth)) ranges = [[0, yearlen]];
7075
- else for (var j = 0; j < options.bymonth.length; j++) {
7076
- month = options.bymonth[j];
7077
- ranges.push(mrange.slice(month - 1, month + 1));
7078
- }
7079
- else if (options.freq === RRule.MONTHLY) ranges = [mrange.slice(month - 1, month + 1)];
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)) / (1e3 * 60 * 60 * 24))];
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 % (PACKED_DATE_DAY_MASK + 1),
8012
- month: Math.trunc(packed / 2 ** PACKED_DATE_MONTH_SHIFT) % (PACKED_DATE_MONTH_MASK + 1),
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
- return [THINGS_WEEKDAY_TO_RRULE[(/* @__PURE__ */ new Date(definition.ia * 1e3)).getUTCDay()] ?? "MO"];
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 repeatRule = buildRepeatRule(recurrence, task, repeatTimeZone);
8172
- if (!repeatRule) return null;
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
- const pickArchivedAt = (task) => {
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, MAX_CHECKLIST_TITLE_LENGTH - 3)}...` : title;
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 || pickArchivedAt(task) !== null || openRepeatInstanceTemplateIds.has(task.uuid)) return;
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 { projectMemberships, projects, teamProjects } = buildProjectActions(ctx, sourceProjects);
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, sourceTasks);
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
- for (const phase of plan.phases) {
9396
- const prepared = buildTransactions(phase.items, {
9397
- clientId: options.commandContext.localContext.clientId,
9398
- existingIds
9399
- });
9400
- execution.inserted += prepared.inserted;
9401
- execution.updated += prepared.updated;
9402
- execution.skipped += prepared.skipped;
9403
- const transactionBatches = chunk(prepared.transactions, options.batchSize);
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
- await mutateModel(options.context, {
9407
- baseUrl: options.commandContext.apiBaseUrl,
9408
- request: {
9409
- batchId: randomUUID(),
9410
- transactions: transactionBatch
9411
- },
9412
- token: options.commandContext.token
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) copyToClipboard(sorted.map((l) => `${l.id.slice(0, 8)} ${l.title}`).join("\n"));
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: await resolveCommandContext(context),
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 filtered = filterProjects(sortProjectsByUpdatedDesc(scope.mode === "all" ? await loadProjectsByWorkspaceIds(context, {
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
- })), requestedStatus);
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) copyToClipboard(filtered.map((p) => `${p.id.slice(0, 8)} [${p.key}] ${p.name} (${getProjectStatus(p)})`).join("\n"));
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: await resolveCommandContext(context),
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--P7Y1FBL.mjs");
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 filtered = filterTasks(scope.mode === "all" ? await loadTasksByWorkspaceIds(context, {
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 todayTasks = filterToday(scope.mode === "all" ? await loadTasksByWorkspaceIds(context, {
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) writeInfo(context, `${workspace.id === commandContext.localContext.workspaceId ? "*" : " "} ${workspace.id} ${workspace.name}${workspace.urlKey ? ` (${workspace.urlKey})` : ""} role=${workspace.role}`);
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 selected = findWorkspace(reference, await listWorkspaces(context, {
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
- await runInteractiveMode(contextFromArgv(processedArgv), program);
12832
+ const ttyContext = contextFromArgv(processedArgv);
12833
+ await runInteractiveMode(ttyContext, program);
12415
12834
  return;
12416
12835
  }
12417
12836
  await program.parseAsync(processedArgv);