taskair-cli 1.0.5 → 1.0.7

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.
Files changed (2) hide show
  1. package/dist/index.js +756 -318
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { program } from "commander";
5
5
 
6
6
  // src/commands/config.tsx
7
- import React3, { useState as useState3, useEffect as useEffect3 } from "react";
7
+ import React4, { useState as useState4, useEffect as useEffect4 } from "react";
8
8
  import { Box as Box4, Text as Text4, useInput, useApp } from "ink";
9
9
  import { hostname } from "os";
10
10
  import http from "http";
@@ -283,6 +283,7 @@ function Spinner({
283
283
  }
284
284
 
285
285
  // src/components/AsciiHeader.tsx
286
+ import { useState as useState3, useEffect as useEffect3 } from "react";
286
287
  import { Box as Box3, Text as Text3 } from "ink";
287
288
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
288
289
  var LOGO = [
@@ -293,23 +294,86 @@ var LOGO = [
293
294
  " \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
294
295
  " \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D"
295
296
  ];
296
- var TAGLINE = " \u2726 Space-themed \xB7 Privacy-first \xB7 AI-native task management \u2726";
297
- function AsciiHeader() {
297
+ var WAVE_COLORS = [
298
+ "#FF0080",
299
+ "#EE40FB",
300
+ "#B44FFF",
301
+ "#7B61FF",
302
+ "#5B8AFF",
303
+ "#00D4FF",
304
+ "#5B8AFF",
305
+ "#7B61FF",
306
+ "#B44FFF",
307
+ "#EE40FB"
308
+ ];
309
+ var DIVIDER_COLORS = [
310
+ "#7B61FF",
311
+ "#B44FFF",
312
+ "#FF0080",
313
+ "#B44FFF",
314
+ "#7B61FF",
315
+ "#5B8AFF",
316
+ "#00D4FF",
317
+ "#5B8AFF"
318
+ ];
319
+ var TAGLINE = "\u2726 CLI \xB7 MCP \xB7 Web \u2726";
320
+ var DIVIDER_LEN = 60;
321
+ function AnimatedDivider() {
322
+ const [frame, setFrame] = useState3(0);
323
+ useEffect3(() => {
324
+ const id = setInterval(() => {
325
+ setFrame((f) => (f + 1) % DIVIDER_COLORS.length);
326
+ }, 280);
327
+ return () => clearInterval(id);
328
+ }, []);
329
+ return /* @__PURE__ */ jsx3(Text3, { color: DIVIDER_COLORS[frame], children: "\u2500".repeat(DIVIDER_LEN) });
330
+ }
331
+ function PremiumHeader() {
332
+ const [visibleLines, setVisibleLines] = useState3(0);
333
+ const [colorFrame, setColorFrame] = useState3(0);
334
+ const [taglineChars, setTaglineChars] = useState3(0);
335
+ const [showDivider, setShowDivider] = useState3(false);
336
+ const revealed = visibleLines >= LOGO.length;
337
+ useEffect3(() => {
338
+ if (visibleLines >= LOGO.length) return;
339
+ const id = setTimeout(() => setVisibleLines((v) => v + 1), 55);
340
+ return () => clearTimeout(id);
341
+ }, [visibleLines]);
342
+ useEffect3(() => {
343
+ if (!revealed) return;
344
+ const id = setInterval(() => {
345
+ setColorFrame((f) => (f + 1) % WAVE_COLORS.length);
346
+ }, 110);
347
+ return () => clearInterval(id);
348
+ }, [revealed]);
349
+ useEffect3(() => {
350
+ if (!revealed || taglineChars >= TAGLINE.length) return;
351
+ const id = setTimeout(() => setTaglineChars((c) => c + 1), 38);
352
+ return () => clearTimeout(id);
353
+ }, [revealed, taglineChars]);
354
+ useEffect3(() => {
355
+ if (taglineChars >= TAGLINE.length && !showDivider) {
356
+ setShowDivider(true);
357
+ }
358
+ }, [taglineChars, showDivider]);
298
359
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
299
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: LOGO.map((line, i) => /* @__PURE__ */ jsx3(Text3, { color: "magenta", bold: true, children: line }, i)) }),
300
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", dimColor: true, children: TAGLINE }),
301
- /* @__PURE__ */ jsx3(Box3, { marginTop: 0, children: /* @__PURE__ */ jsx3(Text3, { color: "#7B61FF", children: "\u2500".repeat(60) }) })
360
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: LOGO.slice(0, visibleLines).map((line, i) => {
361
+ const colorIdx = (i * 2 + colorFrame) % WAVE_COLORS.length;
362
+ return /* @__PURE__ */ jsx3(Text3, { color: revealed ? WAVE_COLORS[colorIdx] : "#B44FFF", bold: true, children: line }, i);
363
+ }) }),
364
+ revealed && taglineChars > 0 && /* @__PURE__ */ jsx3(Box3, { marginTop: 0, children: /* @__PURE__ */ jsxs3(Text3, { color: "#00D4FF", bold: true, children: [
365
+ " ",
366
+ TAGLINE.slice(0, taglineChars),
367
+ taglineChars < TAGLINE.length ? /* @__PURE__ */ jsx3(Text3, { color: "#7B61FF", children: "\u258C" }) : null
368
+ ] }) }),
369
+ showDivider && /* @__PURE__ */ jsx3(Box3, { marginTop: 0, children: /* @__PURE__ */ jsx3(AnimatedDivider, {}) })
302
370
  ] });
303
371
  }
372
+ function AsciiHeader() {
373
+ return /* @__PURE__ */ jsx3(PremiumHeader, {});
374
+ }
304
375
  function MiniHeader() {
305
- return /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
306
- /* @__PURE__ */ jsxs3(Text3, { color: "magenta", bold: true, children: [
307
- "\u2726",
308
- " "
309
- ] }),
310
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: "TaskAir" }),
311
- /* @__PURE__ */ jsx3(Text3, { color: "#7B61FF", children: " \u2014 Space-grade task management" })
312
- ] });
376
+ return /* @__PURE__ */ jsx3(PremiumHeader, {});
313
377
  }
314
378
 
315
379
  // src/commands/config.tsx
@@ -321,12 +385,12 @@ function openBrowser(url) {
321
385
  }
322
386
  function ConfigUI({ initialApiUrl }) {
323
387
  const { exit } = useApp();
324
- const [stage, setStage] = useState3("input_api_url");
325
- const [apiUrl, setApiUrl] = useState3(initialApiUrl);
326
- const [currentInput, setCurrentInput] = useState3(initialApiUrl);
327
- const [port, setPort] = useState3(0);
328
- const [errorMsg, setErrorMsg] = useState3("");
329
- const [authDetails, setAuthDetails] = useState3(null);
388
+ const [stage, setStage] = useState4("input_api_url");
389
+ const [apiUrl, setApiUrl] = useState4(initialApiUrl);
390
+ const [currentInput, setCurrentInput] = useState4(initialApiUrl);
391
+ const [port, setPort] = useState4(0);
392
+ const [errorMsg, setErrorMsg] = useState4("");
393
+ const [authDetails, setAuthDetails] = useState4(null);
330
394
  useInput((input, key) => {
331
395
  if (stage === "input_api_url") {
332
396
  if (key.return) {
@@ -350,7 +414,7 @@ function ConfigUI({ initialApiUrl }) {
350
414
  }
351
415
  }
352
416
  });
353
- useEffect3(() => {
417
+ useEffect4(() => {
354
418
  if (stage !== "waiting_for_browser") return;
355
419
  let server = null;
356
420
  let isActive = true;
@@ -557,7 +621,7 @@ function ConfigUI({ initialApiUrl }) {
557
621
  }
558
622
  };
559
623
  }, [stage, apiUrl]);
560
- useEffect3(() => {
624
+ useEffect4(() => {
561
625
  if (stage !== "registering" || !authDetails) return;
562
626
  async function registerAndSave() {
563
627
  try {
@@ -633,7 +697,7 @@ function registerConfig(program2) {
633
697
  const existing = readCredentials();
634
698
  const { render } = await import("ink");
635
699
  render(
636
- React3.createElement(ConfigUI, {
700
+ React4.createElement(ConfigUI, {
637
701
  initialApiUrl: existing?.api_url ?? "http://localhost:3001"
638
702
  })
639
703
  );
@@ -641,19 +705,19 @@ function registerConfig(program2) {
641
705
  }
642
706
 
643
707
  // src/commands/login.tsx
644
- import React4, { useState as useState4, useEffect as useEffect4 } from "react";
708
+ import React5, { useState as useState5, useEffect as useEffect5 } from "react";
645
709
  import { Box as Box5, Text as Text5, useInput as useInput2, useApp as useApp2 } from "ink";
646
710
  import { hostname as hostname2 } from "os";
647
711
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
648
712
  function LoginUI() {
649
713
  const { exit } = useApp2();
650
714
  const creds = readCredentials();
651
- const [email, setEmail] = useState4(creds?.email ?? "");
652
- const [password, setPassword] = useState4("");
653
- const [stage, setStage] = useState4("input_email");
654
- const [currentInput, setCurrentInput] = useState4(creds?.email ?? "");
655
- const [errorMsg, setErrorMsg] = useState4("");
656
- useEffect4(() => {
715
+ const [email, setEmail] = useState5(creds?.email ?? "");
716
+ const [password, setPassword] = useState5("");
717
+ const [stage, setStage] = useState5("input_email");
718
+ const [currentInput, setCurrentInput] = useState5(creds?.email ?? "");
719
+ const [errorMsg, setErrorMsg] = useState5("");
720
+ useEffect5(() => {
657
721
  if (stage === "loading") {
658
722
  const apiUrl = creds?.api_url ?? "http://localhost:3001";
659
723
  apiLogin(apiUrl, email, password).then(async (res) => {
@@ -750,19 +814,19 @@ function LoginUI() {
750
814
  function registerLogin(program2) {
751
815
  program2.command("login").description("Authenticate with TaskAir API").action(async () => {
752
816
  const { render } = await import("ink");
753
- render(React4.createElement(LoginUI));
817
+ render(React5.createElement(LoginUI));
754
818
  });
755
819
  }
756
820
 
757
821
  // src/commands/logout.tsx
758
- import React5, { useEffect as useEffect5, useState as useState5 } from "react";
822
+ import React6, { useEffect as useEffect6, useState as useState6 } from "react";
759
823
  import { Box as Box6, useApp as useApp3 } from "ink";
760
824
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
761
825
  function LogoutUI() {
762
826
  const { exit } = useApp3();
763
- const [status, setStatus] = useState5("loading");
764
- const [message, setMessage] = useState5("");
765
- useEffect5(() => {
827
+ const [status, setStatus] = useState6("loading");
828
+ const [message, setMessage] = useState6("");
829
+ useEffect6(() => {
766
830
  const creds = readCredentials();
767
831
  if (!creds?.access_token) {
768
832
  setMessage("Not logged in.");
@@ -789,12 +853,12 @@ function LogoutUI() {
789
853
  function registerLogout(program2) {
790
854
  program2.command("logout").description("Clear local credentials and revoke session").action(async () => {
791
855
  const { render } = await import("ink");
792
- render(React5.createElement(LogoutUI));
856
+ render(React6.createElement(LogoutUI));
793
857
  });
794
858
  }
795
859
 
796
860
  // src/commands/whoami.tsx
797
- import React6 from "react";
861
+ import React7 from "react";
798
862
  import { Box as Box7, Text as Text7 } from "ink";
799
863
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
800
864
  function WhoamiUI() {
@@ -854,12 +918,12 @@ function WhoamiUI() {
854
918
  function registerWhoami(program2) {
855
919
  program2.command("whoami").description("Display the currently authenticated user").action(async () => {
856
920
  const { render } = await import("ink");
857
- render(React6.createElement(WhoamiUI));
921
+ render(React7.createElement(WhoamiUI));
858
922
  });
859
923
  }
860
924
 
861
925
  // src/commands/add.tsx
862
- import React7, { useEffect as useEffect6, useState as useState6 } from "react";
926
+ import React8, { useEffect as useEffect7, useState as useState7 } from "react";
863
927
  import { Box as Box8, Text as Text8, useApp as useApp4 } from "ink";
864
928
  import { v4 as uuidv42 } from "uuid";
865
929
 
@@ -981,6 +1045,50 @@ function computeStats() {
981
1045
  };
982
1046
  }
983
1047
 
1048
+ // src/lib/crypto.ts
1049
+ import {
1050
+ pbkdf2Sync,
1051
+ createCipheriv,
1052
+ createDecipheriv,
1053
+ randomBytes,
1054
+ createHash
1055
+ } from "crypto";
1056
+ var PBKDF2_ITERATIONS = 1e5;
1057
+ var KEY_LENGTH = 32;
1058
+ var SALT_LENGTH = 16;
1059
+ var IV_LENGTH = 16;
1060
+ function deriveMasterKey(password, salt) {
1061
+ return pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
1062
+ }
1063
+ function deriveEncryptionKey(masterKey) {
1064
+ const hash = createHash("sha256");
1065
+ hash.update(masterKey);
1066
+ hash.update("taskair-encryption");
1067
+ return hash.digest();
1068
+ }
1069
+ function encrypt(plaintext, password) {
1070
+ const salt = randomBytes(SALT_LENGTH);
1071
+ const iv = randomBytes(IV_LENGTH);
1072
+ const masterKey = deriveMasterKey(password, salt);
1073
+ const encKey = deriveEncryptionKey(masterKey);
1074
+ const cipher = createCipheriv("aes-256-gcm", encKey, iv);
1075
+ const encrypted = Buffer.concat([
1076
+ cipher.update(plaintext, "utf8"),
1077
+ cipher.final()
1078
+ ]);
1079
+ const authTag = cipher.getAuthTag();
1080
+ return {
1081
+ version: "1",
1082
+ salt: salt.toString("base64"),
1083
+ iv: iv.toString("base64"),
1084
+ ciphertext: encrypted.toString("base64"),
1085
+ authTag: authTag.toString("base64")
1086
+ };
1087
+ }
1088
+ function checksum(plaintext) {
1089
+ return createHash("sha256").update(plaintext).digest("hex");
1090
+ }
1091
+
984
1092
  // src/commands/add.tsx
985
1093
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
986
1094
  function AddUI({
@@ -990,33 +1098,73 @@ function AddUI({
990
1098
  tags
991
1099
  }) {
992
1100
  const { exit } = useApp4();
993
- const [status, setStatus] = useState6("working");
994
- const [taskId, setTaskId] = useState6("");
995
- const [errorMsg, setErrorMsg] = useState6("");
996
- useEffect6(() => {
997
- try {
998
- const auth = requireAuth();
999
- const task = {
1000
- id: uuidv42(),
1001
- description,
1002
- priority,
1003
- status: "pending",
1004
- tags,
1005
- due_date: due,
1006
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
1007
- updated_at: (/* @__PURE__ */ new Date()).toISOString(),
1008
- device_id: auth.deviceId,
1009
- vector_clock: { [auth.deviceId]: 1 }
1010
- };
1011
- insertTask(task);
1012
- setTaskId(task.id.slice(0, 8));
1013
- setStatus("success");
1014
- setTimeout(() => exit(), 1500);
1015
- } catch (e) {
1016
- setErrorMsg(e.message);
1017
- setStatus("error");
1018
- setTimeout(() => exit(e), 1500);
1101
+ const [status, setStatus] = useState7("working");
1102
+ const [syncStatus, setSyncStatus] = useState7("idle");
1103
+ const [syncMessage, setSyncMessage] = useState7("");
1104
+ const [taskId, setTaskId] = useState7("");
1105
+ const [errorMsg, setErrorMsg] = useState7("");
1106
+ useEffect7(() => {
1107
+ async function createTaskAndSync() {
1108
+ try {
1109
+ const auth = requireAuth();
1110
+ const task = {
1111
+ id: uuidv42(),
1112
+ description,
1113
+ priority,
1114
+ status: "pending",
1115
+ tags,
1116
+ due_date: due,
1117
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1118
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
1119
+ device_id: auth.deviceId,
1120
+ vector_clock: { [auth.deviceId]: 1 }
1121
+ };
1122
+ insertTask(task);
1123
+ setTaskId(task.id.slice(0, 8));
1124
+ setStatus("success");
1125
+ setSyncStatus("syncing");
1126
+ const tasks = getAllTasks();
1127
+ const bundle = {
1128
+ tasks,
1129
+ schema_version: "1",
1130
+ exported_at: (/* @__PURE__ */ new Date()).toISOString()
1131
+ };
1132
+ const plaintext = JSON.stringify(bundle);
1133
+ const blobChecksum = checksum(plaintext);
1134
+ const syncPassword = auth.password;
1135
+ if (!syncPassword) {
1136
+ setSyncStatus("failed");
1137
+ setSyncMessage('Encryption password required. Run "taskair sync --password <pwd>".');
1138
+ setTimeout(() => exit(), 3e3);
1139
+ return;
1140
+ }
1141
+ const blob = encrypt(plaintext, syncPassword);
1142
+ const res = await apiUploadSync(
1143
+ auth.apiUrl,
1144
+ auth.accessToken,
1145
+ blob,
1146
+ blobChecksum,
1147
+ auth.deviceId
1148
+ );
1149
+ if (res.success) {
1150
+ clearSyncQueue();
1151
+ setSyncStatus("synced");
1152
+ setTimeout(() => exit(), 2e3);
1153
+ } else if (res.error?.code === "NETWORK_ERROR") {
1154
+ setSyncStatus("offline");
1155
+ setTimeout(() => exit(), 2500);
1156
+ } else {
1157
+ setSyncStatus("failed");
1158
+ setSyncMessage(res.error?.message ?? "Sync failed");
1159
+ setTimeout(() => exit(), 3500);
1160
+ }
1161
+ } catch (e) {
1162
+ setErrorMsg(e.message);
1163
+ setStatus("error");
1164
+ setTimeout(() => exit(e), 2e3);
1165
+ }
1019
1166
  }
1167
+ createTaskAndSync();
1020
1168
  }, []);
1021
1169
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", padding: 1, children: [
1022
1170
  /* @__PURE__ */ jsx8(MiniHeader, {}),
@@ -1048,14 +1196,13 @@ function AddUI({
1048
1196
  /* @__PURE__ */ jsx8(Text8, { color: "#7B61FF", children: tags.join(", ") })
1049
1197
  ] }),
1050
1198
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
1051
- /* @__PURE__ */ jsxs8(Text8, { color: "gray", dimColor: true, children: [
1052
- "Queued for sync \xB7 Run",
1053
- " "
1054
- ] }),
1055
- /* @__PURE__ */ jsx8(Text8, { color: "cyan", dimColor: true, children: "taskair sync" }),
1056
- /* @__PURE__ */ jsxs8(Text8, { color: "gray", dimColor: true, children: [
1057
- " ",
1058
- "to push to cloud"
1199
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " Sync: " }),
1200
+ syncStatus === "syncing" && /* @__PURE__ */ jsx8(Text8, { color: "magenta", children: "Syncing to cloud (E2E encrypted)..." }),
1201
+ syncStatus === "synced" && /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u2713 Synced to cloud" }),
1202
+ syncStatus === "offline" && /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "\u2601 Offline (queued locally)" }),
1203
+ syncStatus === "failed" && /* @__PURE__ */ jsxs8(Text8, { color: "red", children: [
1204
+ "\u2717 ",
1205
+ syncMessage
1059
1206
  ] })
1060
1207
  ] })
1061
1208
  ] })
@@ -1073,7 +1220,7 @@ function registerAdd(program2) {
1073
1220
  const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
1074
1221
  const { render } = await import("ink");
1075
1222
  render(
1076
- React7.createElement(AddUI, {
1223
+ React8.createElement(AddUI, {
1077
1224
  description,
1078
1225
  priority,
1079
1226
  due: options.due,
@@ -1084,11 +1231,11 @@ function registerAdd(program2) {
1084
1231
  }
1085
1232
 
1086
1233
  // src/commands/list.tsx
1087
- import React9, { useEffect as useEffect7, useState as useState7 } from "react";
1234
+ import React10, { useEffect as useEffect8, useState as useState8 } from "react";
1088
1235
  import { Box as Box10, Text as Text10, useApp as useApp5 } from "ink";
1089
1236
 
1090
1237
  // src/components/TaskTable.tsx
1091
- import React8 from "react";
1238
+ import React9 from "react";
1092
1239
  import { Box as Box9, Text as Text9 } from "ink";
1093
1240
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1094
1241
  function priorityColor(p) {
@@ -1190,7 +1337,7 @@ function TaskTable({ tasks }) {
1190
1337
  /* @__PURE__ */ jsx9(TopBorder, {}),
1191
1338
  /* @__PURE__ */ jsx9(Header, {}),
1192
1339
  /* @__PURE__ */ jsx9(Separator, {}),
1193
- tasks.map((task, i) => /* @__PURE__ */ jsxs9(React8.Fragment, { children: [
1340
+ tasks.map((task, i) => /* @__PURE__ */ jsxs9(React9.Fragment, { children: [
1194
1341
  /* @__PURE__ */ jsx9(TableRow, { task }),
1195
1342
  i < tasks.length - 1 && /* @__PURE__ */ jsx9(Separator, {})
1196
1343
  ] }, task.id)),
@@ -1247,8 +1394,8 @@ function TaskCompact({ tasks }) {
1247
1394
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1248
1395
  function ListUI({ filter, format }) {
1249
1396
  const { exit } = useApp5();
1250
- const [tasks, setTasks] = useState7([]);
1251
- useEffect7(() => {
1397
+ const [tasks, setTasks] = useState8([]);
1398
+ useEffect8(() => {
1252
1399
  const results = filterTasks(filter);
1253
1400
  results.sort((a, b) => {
1254
1401
  const pOrder = { high: 0, medium: 1, low: 2 };
@@ -1311,78 +1458,204 @@ function registerList(program2) {
1311
1458
  return;
1312
1459
  }
1313
1460
  const { render } = await import("ink");
1314
- render(React9.createElement(ListUI, { filter, format }));
1461
+ render(React10.createElement(ListUI, { filter, format }));
1315
1462
  });
1316
1463
  }
1317
1464
 
1318
1465
  // src/commands/done.tsx
1319
- import React10, { useEffect as useEffect8, useState as useState8 } from "react";
1466
+ import React11, { useEffect as useEffect9, useState as useState9 } from "react";
1320
1467
  import { Box as Box11, Text as Text11, useApp as useApp6 } from "ink";
1321
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1468
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1322
1469
  function DoneUI({ id, note }) {
1323
1470
  const { exit } = useApp6();
1324
- const [status, setStatus] = useState8("success");
1325
- const [message, setMessage] = useState8("");
1326
- const [description, setDescription] = useState8("");
1327
- useEffect8(() => {
1328
- const task = getTask(id);
1329
- if (!task) {
1330
- setMessage(`No task found with ID starting with "${id}"`);
1331
- setStatus("error");
1332
- setTimeout(() => exit(new Error("Task not found")), 1200);
1333
- return;
1334
- }
1335
- if (task.status === "completed") {
1336
- setMessage(`Task is already marked as completed.`);
1337
- setStatus("error");
1338
- setTimeout(() => exit(), 1200);
1339
- return;
1340
- }
1341
- const updated = updateTask(task.id, {
1342
- status: "completed",
1343
- completed_at: (/* @__PURE__ */ new Date()).toISOString(),
1344
- completion_note: note
1345
- });
1346
- if (updated) {
1347
- setDescription(task.description);
1348
- setMessage(`Task marked complete!`);
1349
- setStatus("success");
1350
- setTimeout(() => exit(), 1500);
1471
+ const [status, setStatus] = useState9("working");
1472
+ const [syncStatus, setSyncStatus] = useState9("idle");
1473
+ const [syncMessage, setSyncMessage] = useState9("");
1474
+ const [message, setMessage] = useState9("");
1475
+ const [description, setDescription] = useState9("");
1476
+ useEffect9(() => {
1477
+ async function completeTaskAndSync() {
1478
+ try {
1479
+ const auth = requireAuth();
1480
+ const task = getTask(id);
1481
+ if (!task) {
1482
+ setMessage(`No task found with ID starting with "${id}"`);
1483
+ setStatus("error");
1484
+ setTimeout(() => exit(new Error("Task not found")), 1200);
1485
+ return;
1486
+ }
1487
+ if (task.status === "completed") {
1488
+ setMessage(`Task is already marked as completed.`);
1489
+ setStatus("error");
1490
+ setTimeout(() => exit(), 1200);
1491
+ return;
1492
+ }
1493
+ const updated = updateTask(task.id, {
1494
+ status: "completed",
1495
+ completed_at: (/* @__PURE__ */ new Date()).toISOString(),
1496
+ completion_note: note
1497
+ });
1498
+ if (updated) {
1499
+ setDescription(task.description);
1500
+ setMessage(`Task marked complete!`);
1501
+ setStatus("success");
1502
+ setSyncStatus("syncing");
1503
+ const tasks = getAllTasks();
1504
+ const bundle = {
1505
+ tasks,
1506
+ schema_version: "1",
1507
+ exported_at: (/* @__PURE__ */ new Date()).toISOString()
1508
+ };
1509
+ const plaintext = JSON.stringify(bundle);
1510
+ const blobChecksum = checksum(plaintext);
1511
+ const syncPassword = auth.password;
1512
+ if (!syncPassword) {
1513
+ setSyncStatus("failed");
1514
+ setSyncMessage('Encryption password required. Run "taskair sync --password <pwd>".');
1515
+ setTimeout(() => exit(), 3e3);
1516
+ return;
1517
+ }
1518
+ const blob = encrypt(plaintext, syncPassword);
1519
+ const res = await apiUploadSync(
1520
+ auth.apiUrl,
1521
+ auth.accessToken,
1522
+ blob,
1523
+ blobChecksum,
1524
+ auth.deviceId
1525
+ );
1526
+ if (res.success) {
1527
+ clearSyncQueue();
1528
+ setSyncStatus("synced");
1529
+ setTimeout(() => exit(), 2e3);
1530
+ } else if (res.error?.code === "NETWORK_ERROR") {
1531
+ setSyncStatus("offline");
1532
+ setTimeout(() => exit(), 2500);
1533
+ } else {
1534
+ setSyncStatus("failed");
1535
+ setSyncMessage(res.error?.message ?? "Sync failed");
1536
+ setTimeout(() => exit(), 3500);
1537
+ }
1538
+ }
1539
+ } catch (e) {
1540
+ setMessage(e.message);
1541
+ setStatus("error");
1542
+ setTimeout(() => exit(e), 2e3);
1543
+ }
1351
1544
  }
1545
+ completeTaskAndSync();
1352
1546
  }, []);
1353
1547
  return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", padding: 1, children: [
1354
1548
  /* @__PURE__ */ jsx11(MiniHeader, {}),
1355
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, flexDirection: "column", children: status === "success" ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
1549
+ status === "working" && /* @__PURE__ */ jsx11(Spinner, { label: "Updating task\u2026", type: "star", color: "green" }),
1550
+ status === "success" && /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
1356
1551
  /* @__PURE__ */ jsx11(StarBurst, { label: message, color: "green" }),
1357
- description && /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
1358
- /* @__PURE__ */ jsx11(Text11, { color: "gray", children: " " }),
1359
- /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713 " }),
1360
- /* @__PURE__ */ jsx11(Text11, { color: "white", children: description })
1361
- ] }),
1362
- note && /* @__PURE__ */ jsxs11(Box11, { children: [
1363
- /* @__PURE__ */ jsx11(Text11, { color: "gray", children: " Note: " }),
1364
- /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: note })
1552
+ /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, flexDirection: "column", children: [
1553
+ description && /* @__PURE__ */ jsxs11(Box11, { children: [
1554
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: " Task: " }),
1555
+ /* @__PURE__ */ jsx11(Text11, { color: "white", children: description })
1556
+ ] }),
1557
+ note && /* @__PURE__ */ jsxs11(Box11, { children: [
1558
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: " Note: " }),
1559
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: note })
1560
+ ] }),
1561
+ /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
1562
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: " Sync: " }),
1563
+ syncStatus === "syncing" && /* @__PURE__ */ jsx11(Text11, { color: "magenta", children: "Syncing to cloud (E2E encrypted)..." }),
1564
+ syncStatus === "synced" && /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713 Synced to cloud" }),
1565
+ syncStatus === "offline" && /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "\u2601 Offline (queued locally)" }),
1566
+ syncStatus === "failed" && /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
1567
+ "\u2717 ",
1568
+ syncMessage
1569
+ ] })
1570
+ ] })
1365
1571
  ] })
1366
- ] }) : /* @__PURE__ */ jsx11(StatusBadge, { type: "error", message }) })
1572
+ ] }),
1573
+ status === "error" && /* @__PURE__ */ jsx11(StatusBadge, { type: "error", message })
1367
1574
  ] });
1368
1575
  }
1369
1576
  function registerDone(program2) {
1370
1577
  program2.command("done <id>").alias("-d").description("Mark a task as complete").option("-n, --note <note>", "Completion note").action(async (id, options) => {
1371
1578
  const { render } = await import("ink");
1372
- render(React10.createElement(DoneUI, { id, note: options.note }));
1579
+ render(React11.createElement(DoneUI, { id, note: options.note }));
1373
1580
  });
1374
1581
  }
1375
1582
 
1376
1583
  // src/commands/remove.tsx
1377
- import React11, { useEffect as useEffect9, useState as useState9 } from "react";
1584
+ import React12, { useEffect as useEffect10, useState as useState10 } from "react";
1378
1585
  import { Box as Box12, Text as Text12, useApp as useApp7, useInput as useInput3 } from "ink";
1379
1586
  import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1380
1587
  function RemoveUI({ id, force }) {
1381
1588
  const { exit } = useApp7();
1382
- const [stage, setStage] = useState9("confirm");
1383
- const [message, setMessage] = useState9("");
1589
+ const [stage, setStage] = useState10("confirm");
1590
+ const [syncStatus, setSyncStatus] = useState10("idle");
1591
+ const [syncMessage, setSyncMessage] = useState10("");
1592
+ const [message, setMessage] = useState10("");
1384
1593
  const task = getTask(id);
1385
- useEffect9(() => {
1594
+ const performDeleteAndSync = async () => {
1595
+ try {
1596
+ const auth = requireAuth();
1597
+ setStage("deleting");
1598
+ const deleted = deleteTask(task.id);
1599
+ if (deleted) {
1600
+ setMessage(`Task removed: ${task.description}`);
1601
+ setSyncStatus("syncing");
1602
+ const tasks = getAllTasks();
1603
+ const bundle = {
1604
+ tasks,
1605
+ schema_version: "1",
1606
+ exported_at: (/* @__PURE__ */ new Date()).toISOString()
1607
+ };
1608
+ const plaintext = JSON.stringify(bundle);
1609
+ const blobChecksum = checksum(plaintext);
1610
+ const syncPassword = auth.password;
1611
+ if (!syncPassword) {
1612
+ setSyncStatus("failed");
1613
+ setSyncMessage('Encryption password required. Run "taskair sync --password <pwd>".');
1614
+ setStage("success");
1615
+ setTimeout(() => exit(), 3e3);
1616
+ return;
1617
+ }
1618
+ const blob = encrypt(plaintext, syncPassword);
1619
+ const res = await apiUploadSync(
1620
+ auth.apiUrl,
1621
+ auth.accessToken,
1622
+ blob,
1623
+ blobChecksum,
1624
+ auth.deviceId
1625
+ );
1626
+ setStage("success");
1627
+ if (res.success) {
1628
+ clearSyncQueue();
1629
+ setSyncStatus("synced");
1630
+ setTimeout(() => exit(), 2e3);
1631
+ } else if (res.error?.code === "NETWORK_ERROR") {
1632
+ setSyncStatus("offline");
1633
+ setTimeout(() => exit(), 2500);
1634
+ } else {
1635
+ setSyncStatus("failed");
1636
+ setSyncMessage(res.error?.message ?? "Sync failed");
1637
+ setTimeout(() => exit(), 3500);
1638
+ }
1639
+ } else {
1640
+ setMessage("Failed to delete task.");
1641
+ setStage("error");
1642
+ setTimeout(() => exit(new Error("Delete failed")), 1200);
1643
+ }
1644
+ } catch (e) {
1645
+ setMessage(e.message);
1646
+ setStage("error");
1647
+ setTimeout(() => exit(e), 2e3);
1648
+ }
1649
+ };
1650
+ useEffect10(() => {
1651
+ try {
1652
+ requireAuth();
1653
+ } catch (e) {
1654
+ setMessage(e.message);
1655
+ setStage("error");
1656
+ setTimeout(() => exit(e), 2e3);
1657
+ return;
1658
+ }
1386
1659
  if (!task) {
1387
1660
  setMessage(`No task found with ID starting with "${id}"`);
1388
1661
  setStage("error");
@@ -1390,23 +1663,13 @@ function RemoveUI({ id, force }) {
1390
1663
  return;
1391
1664
  }
1392
1665
  if (force) {
1393
- const deleted = deleteTask(task.id);
1394
- if (deleted) {
1395
- setMessage(`Task removed: ${task.description}`);
1396
- setStage("success");
1397
- setTimeout(() => exit(), 1200);
1398
- }
1666
+ performDeleteAndSync();
1399
1667
  }
1400
1668
  }, []);
1401
1669
  useInput3((input) => {
1402
1670
  if (stage !== "confirm" || !task) return;
1403
1671
  if (input.toLowerCase() === "y") {
1404
- const deleted = deleteTask(task.id);
1405
- if (deleted) {
1406
- setMessage(`Task removed: ${task.description}`);
1407
- setStage("success");
1408
- setTimeout(() => exit(), 1200);
1409
- }
1672
+ performDeleteAndSync();
1410
1673
  } else if (input.toLowerCase() === "n" || input === "") {
1411
1674
  setStage("cancelled");
1412
1675
  setTimeout(() => exit(), 800);
@@ -1442,7 +1705,20 @@ function RemoveUI({ id, force }) {
1442
1705
  /* @__PURE__ */ jsx12(Text12, { color: "gray", children: " to cancel: " })
1443
1706
  ] })
1444
1707
  ] }),
1445
- stage === "success" && /* @__PURE__ */ jsx12(StarBurst, { label: message, color: "cyan" }),
1708
+ stage === "deleting" && /* @__PURE__ */ jsx12(Spinner, { label: "Removing task\u2026", type: "star", color: "cyan" }),
1709
+ stage === "success" && /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
1710
+ /* @__PURE__ */ jsx12(StarBurst, { label: message, color: "cyan" }),
1711
+ /* @__PURE__ */ jsx12(Box12, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsxs12(Box12, { children: [
1712
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: " Sync: " }),
1713
+ syncStatus === "syncing" && /* @__PURE__ */ jsx12(Text12, { color: "magenta", children: "Syncing to cloud (E2E encrypted)..." }),
1714
+ syncStatus === "synced" && /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713 Synced to cloud" }),
1715
+ syncStatus === "offline" && /* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "\u2601 Offline (queued locally)" }),
1716
+ syncStatus === "failed" && /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
1717
+ "\u2717 ",
1718
+ syncMessage
1719
+ ] })
1720
+ ] }) })
1721
+ ] }),
1446
1722
  stage === "error" && /* @__PURE__ */ jsx12(StatusBadge, { type: "error", message }),
1447
1723
  stage === "cancelled" && /* @__PURE__ */ jsx12(StatusBadge, { type: "info", message: "Deletion cancelled." })
1448
1724
  ] });
@@ -1450,92 +1726,153 @@ function RemoveUI({ id, force }) {
1450
1726
  function registerRemove(program2) {
1451
1727
  program2.command("remove <id>").alias("-r").description("Delete a task").option("--force", "Skip confirmation prompt", false).action(async (id, options) => {
1452
1728
  const { render } = await import("ink");
1453
- render(React11.createElement(RemoveUI, { id, force: options.force }));
1729
+ render(React12.createElement(RemoveUI, { id, force: options.force }));
1454
1730
  });
1455
1731
  }
1456
1732
 
1457
1733
  // src/commands/edit.tsx
1458
- import React12, { useState as useState10, useEffect as useEffect10 } from "react";
1734
+ import React13, { useState as useState11, useEffect as useEffect11 } from "react";
1459
1735
  import { Box as Box13, Text as Text13, useApp as useApp8 } from "ink";
1460
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1736
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1461
1737
  function EditUI({ id, updates }) {
1462
1738
  const { exit } = useApp8();
1463
- const [status, setStatus] = useState10("success");
1464
- const [message, setMessage] = useState10("");
1465
- const [changes, setChanges] = useState10([]);
1466
- useEffect10(() => {
1467
- const task = getTask(id);
1468
- if (!task) {
1469
- setMessage(`No task found with ID starting with "${id}"`);
1470
- setStatus("error");
1471
- setTimeout(() => exit(new Error("Task not found")), 1200);
1472
- return;
1473
- }
1474
- const taskUpdates = {};
1475
- const changesLog = [];
1476
- if (updates.description) {
1477
- taskUpdates.description = updates.description;
1478
- changesLog.push(`description \u2192 "${updates.description}"`);
1479
- }
1480
- if (updates.priority) {
1481
- taskUpdates.priority = updates.priority;
1482
- changesLog.push(`priority \u2192 ${updates.priority}`);
1483
- }
1484
- if (updates.status) {
1485
- taskUpdates.status = updates.status;
1486
- if (updates.status === "completed") {
1487
- taskUpdates.completed_at = (/* @__PURE__ */ new Date()).toISOString();
1739
+ const [status, setStatus] = useState11("working");
1740
+ const [syncStatus, setSyncStatus] = useState11("idle");
1741
+ const [syncMessage, setSyncMessage] = useState11("");
1742
+ const [message, setMessage] = useState11("");
1743
+ const [changes, setChanges] = useState11([]);
1744
+ useEffect11(() => {
1745
+ async function editTaskAndSync() {
1746
+ try {
1747
+ const auth = requireAuth();
1748
+ const task = getTask(id);
1749
+ if (!task) {
1750
+ setMessage(`No task found with ID starting with "${id}"`);
1751
+ setStatus("error");
1752
+ setTimeout(() => exit(new Error("Task not found")), 1200);
1753
+ return;
1754
+ }
1755
+ const taskUpdates = {};
1756
+ const changesLog = [];
1757
+ if (updates.description) {
1758
+ taskUpdates.description = updates.description;
1759
+ changesLog.push(`description \u2192 "${updates.description}"`);
1760
+ }
1761
+ if (updates.priority) {
1762
+ taskUpdates.priority = updates.priority;
1763
+ changesLog.push(`priority \u2192 ${updates.priority}`);
1764
+ }
1765
+ if (updates.status) {
1766
+ taskUpdates.status = updates.status;
1767
+ if (updates.status === "completed") {
1768
+ taskUpdates.completed_at = (/* @__PURE__ */ new Date()).toISOString();
1769
+ }
1770
+ changesLog.push(`status \u2192 ${updates.status}`);
1771
+ }
1772
+ if (updates.due) {
1773
+ taskUpdates.due_date = updates.due;
1774
+ changesLog.push(`due date \u2192 ${updates.due}`);
1775
+ }
1776
+ if (updates.tags) {
1777
+ taskUpdates.tags = updates.tags.split(",").map((t) => t.trim());
1778
+ changesLog.push(`tags \u2192 [${taskUpdates.tags.join(", ")}]`);
1779
+ }
1780
+ if (updates.note) {
1781
+ taskUpdates.completion_note = updates.note;
1782
+ changesLog.push(`note \u2192 "${updates.note}"`);
1783
+ }
1784
+ if (changesLog.length === 0) {
1785
+ setMessage("No changes specified. Use options like --description, --priority, etc.");
1786
+ setStatus("error");
1787
+ setTimeout(() => exit(), 1200);
1788
+ return;
1789
+ }
1790
+ const updated = updateTask(task.id, taskUpdates);
1791
+ if (updated) {
1792
+ setChanges(changesLog);
1793
+ setMessage(`Task ${task.id.slice(0, 8)} updated!`);
1794
+ setStatus("success");
1795
+ setSyncStatus("syncing");
1796
+ const tasks = getAllTasks();
1797
+ const bundle = {
1798
+ tasks,
1799
+ schema_version: "1",
1800
+ exported_at: (/* @__PURE__ */ new Date()).toISOString()
1801
+ };
1802
+ const plaintext = JSON.stringify(bundle);
1803
+ const blobChecksum = checksum(plaintext);
1804
+ const syncPassword = auth.password;
1805
+ if (!syncPassword) {
1806
+ setSyncStatus("failed");
1807
+ setSyncMessage('Encryption password required. Run "taskair sync --password <pwd>".');
1808
+ setTimeout(() => exit(), 3e3);
1809
+ return;
1810
+ }
1811
+ const blob = encrypt(plaintext, syncPassword);
1812
+ const res = await apiUploadSync(
1813
+ auth.apiUrl,
1814
+ auth.accessToken,
1815
+ blob,
1816
+ blobChecksum,
1817
+ auth.deviceId
1818
+ );
1819
+ if (res.success) {
1820
+ clearSyncQueue();
1821
+ setSyncStatus("synced");
1822
+ setTimeout(() => exit(), 2e3);
1823
+ } else if (res.error?.code === "NETWORK_ERROR") {
1824
+ setSyncStatus("offline");
1825
+ setTimeout(() => exit(), 2500);
1826
+ } else {
1827
+ setSyncStatus("failed");
1828
+ setSyncMessage(res.error?.message ?? "Sync failed");
1829
+ setTimeout(() => exit(), 3500);
1830
+ }
1831
+ }
1832
+ } catch (e) {
1833
+ setMessage(e.message);
1834
+ setStatus("error");
1835
+ setTimeout(() => exit(e), 2e3);
1488
1836
  }
1489
- changesLog.push(`status \u2192 ${updates.status}`);
1490
- }
1491
- if (updates.due) {
1492
- taskUpdates.due_date = updates.due;
1493
- changesLog.push(`due date \u2192 ${updates.due}`);
1494
- }
1495
- if (updates.tags) {
1496
- taskUpdates.tags = updates.tags.split(",").map((t) => t.trim());
1497
- changesLog.push(`tags \u2192 [${taskUpdates.tags.join(", ")}]`);
1498
- }
1499
- if (updates.note) {
1500
- taskUpdates.completion_note = updates.note;
1501
- changesLog.push(`note \u2192 "${updates.note}"`);
1502
- }
1503
- if (changesLog.length === 0) {
1504
- setMessage("No changes specified. Use options like --description, --priority, etc.");
1505
- setStatus("error");
1506
- setTimeout(() => exit(), 1200);
1507
- return;
1508
- }
1509
- const updated = updateTask(task.id, taskUpdates);
1510
- if (updated) {
1511
- setChanges(changesLog);
1512
- setMessage(`Task ${task.id.slice(0, 8)} updated!`);
1513
- setStatus("success");
1514
- setTimeout(() => exit(), 1500);
1515
1837
  }
1838
+ editTaskAndSync();
1516
1839
  }, []);
1517
1840
  return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", padding: 1, children: [
1518
1841
  /* @__PURE__ */ jsx13(MiniHeader, {}),
1519
- /* @__PURE__ */ jsx13(Box13, { marginTop: 1, flexDirection: "column", children: status === "success" ? /* @__PURE__ */ jsxs13(Fragment3, { children: [
1842
+ status === "working" && /* @__PURE__ */ jsx13(Spinner, { label: "Updating task\u2026", type: "star", color: "magenta" }),
1843
+ status === "success" && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
1520
1844
  /* @__PURE__ */ jsx13(StarBurst, { label: message, color: "magenta" }),
1521
- changes.map((c) => /* @__PURE__ */ jsxs13(Box13, { marginTop: 0, children: [
1522
- /* @__PURE__ */ jsx13(Text13, { color: "gray", children: " \u21B3 " }),
1523
- /* @__PURE__ */ jsx13(Text13, { color: "white", children: c })
1524
- ] }, c))
1525
- ] }) : /* @__PURE__ */ jsx13(StatusBadge, { type: "error", message }) })
1845
+ /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
1846
+ changes.map((c) => /* @__PURE__ */ jsxs13(Box13, { marginTop: 0, children: [
1847
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: " \u21B3 " }),
1848
+ /* @__PURE__ */ jsx13(Text13, { color: "white", children: c })
1849
+ ] }, c)),
1850
+ /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, children: [
1851
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: " Sync: " }),
1852
+ syncStatus === "syncing" && /* @__PURE__ */ jsx13(Text13, { color: "magenta", children: "Syncing to cloud (E2E encrypted)..." }),
1853
+ syncStatus === "synced" && /* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713 Synced to cloud" }),
1854
+ syncStatus === "offline" && /* @__PURE__ */ jsx13(Text13, { color: "yellow", children: "\u2601 Offline (queued locally)" }),
1855
+ syncStatus === "failed" && /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
1856
+ "\u2717 ",
1857
+ syncMessage
1858
+ ] })
1859
+ ] })
1860
+ ] })
1861
+ ] }),
1862
+ status === "error" && /* @__PURE__ */ jsx13(StatusBadge, { type: "error", message })
1526
1863
  ] });
1527
1864
  }
1528
1865
  function registerEdit(program2) {
1529
1866
  program2.command("edit <id>").alias("-e").description("Update one or more fields of a task").option("--description <text>", "New description").option("--priority <level>", "New priority: high | medium | low").option("--status <status>", "New status: pending | in_progress | completed").option("--due <datetime>", "New due date").option("--tags <tags>", "New comma-separated tags").option("--note <note>", "Add a completion note").action(async (id, options) => {
1530
1867
  const { render } = await import("ink");
1531
- render(React12.createElement(EditUI, { id, updates: options }));
1868
+ render(React13.createElement(EditUI, { id, updates: options }));
1532
1869
  });
1533
1870
  }
1534
1871
 
1535
1872
  // src/commands/stats.tsx
1536
- import React13, { useEffect as useEffect11 } from "react";
1873
+ import React14, { useEffect as useEffect12 } from "react";
1537
1874
  import { Box as Box14, Text as Text14, useApp as useApp9 } from "ink";
1538
- import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1875
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1539
1876
  function ProgressBar({
1540
1877
  value,
1541
1878
  max,
@@ -1554,7 +1891,7 @@ function ProgressBar({
1554
1891
  function StatsUI() {
1555
1892
  const { exit } = useApp9();
1556
1893
  const stats = computeStats();
1557
- useEffect11(() => {
1894
+ useEffect12(() => {
1558
1895
  setTimeout(() => exit(), 100);
1559
1896
  }, []);
1560
1897
  const rows = [
@@ -1590,7 +1927,7 @@ function StatsUI() {
1590
1927
  row.label.padEnd(16)
1591
1928
  ] }),
1592
1929
  /* @__PURE__ */ jsx14(Text14, { color: row.color, bold: true, children: String(row.value).padStart(4) }),
1593
- stats.total > 0 && /* @__PURE__ */ jsxs14(Fragment4, { children: [
1930
+ stats.total > 0 && /* @__PURE__ */ jsxs14(Fragment2, { children: [
1594
1931
  /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " " }),
1595
1932
  /* @__PURE__ */ jsx14(
1596
1933
  ProgressBar,
@@ -1612,7 +1949,7 @@ function StatsUI() {
1612
1949
  row.label.padEnd(20)
1613
1950
  ] }),
1614
1951
  /* @__PURE__ */ jsx14(Text14, { color: row.color, bold: true, children: String(row.value).padStart(4) }),
1615
- stats.total > 0 && /* @__PURE__ */ jsxs14(Fragment4, { children: [
1952
+ stats.total > 0 && /* @__PURE__ */ jsxs14(Fragment2, { children: [
1616
1953
  /* @__PURE__ */ jsx14(Text14, { color: "gray", children: " " }),
1617
1954
  /* @__PURE__ */ jsx14(
1618
1955
  ProgressBar,
@@ -1645,66 +1982,20 @@ function StatsUI() {
1645
1982
  function registerStats(program2) {
1646
1983
  program2.command("stats").description("Show task analytics and completion stats").action(async () => {
1647
1984
  const { render } = await import("ink");
1648
- render(React13.createElement(StatsUI));
1985
+ render(React14.createElement(StatsUI));
1649
1986
  });
1650
1987
  }
1651
1988
 
1652
1989
  // src/commands/sync.tsx
1653
- import React14, { useState as useState11, useEffect as useEffect12 } from "react";
1990
+ import React15, { useState as useState12, useEffect as useEffect13 } from "react";
1654
1991
  import { Box as Box15, Text as Text15, useApp as useApp10 } from "ink";
1655
-
1656
- // src/lib/crypto.ts
1657
- import {
1658
- pbkdf2Sync,
1659
- createCipheriv,
1660
- createDecipheriv,
1661
- randomBytes,
1662
- createHash
1663
- } from "crypto";
1664
- var PBKDF2_ITERATIONS = 1e5;
1665
- var KEY_LENGTH = 32;
1666
- var SALT_LENGTH = 16;
1667
- var IV_LENGTH = 16;
1668
- function deriveMasterKey(password, salt) {
1669
- return pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
1670
- }
1671
- function deriveEncryptionKey(masterKey) {
1672
- const hash = createHash("sha256");
1673
- hash.update(masterKey);
1674
- hash.update("taskair-encryption");
1675
- return hash.digest();
1676
- }
1677
- function encrypt(plaintext, password) {
1678
- const salt = randomBytes(SALT_LENGTH);
1679
- const iv = randomBytes(IV_LENGTH);
1680
- const masterKey = deriveMasterKey(password, salt);
1681
- const encKey = deriveEncryptionKey(masterKey);
1682
- const cipher = createCipheriv("aes-256-gcm", encKey, iv);
1683
- const encrypted = Buffer.concat([
1684
- cipher.update(plaintext, "utf8"),
1685
- cipher.final()
1686
- ]);
1687
- const authTag = cipher.getAuthTag();
1688
- return {
1689
- version: "1",
1690
- salt: salt.toString("base64"),
1691
- iv: iv.toString("base64"),
1692
- ciphertext: encrypted.toString("base64"),
1693
- authTag: authTag.toString("base64")
1694
- };
1695
- }
1696
- function checksum(plaintext) {
1697
- return createHash("sha256").update(plaintext).digest("hex");
1698
- }
1699
-
1700
- // src/commands/sync.tsx
1701
- import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1992
+ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1702
1993
  function SyncUI({ dryRun, password }) {
1703
1994
  const { exit } = useApp10();
1704
- const [status, setStatus] = useState11("loading");
1705
- const [message, setMessage] = useState11("");
1706
- const [details, setDetails] = useState11([]);
1707
- useEffect12(() => {
1995
+ const [status, setStatus] = useState12("loading");
1996
+ const [message, setMessage] = useState12("");
1997
+ const [details, setDetails] = useState12([]);
1998
+ useEffect13(() => {
1708
1999
  async function doSync() {
1709
2000
  try {
1710
2001
  const auth = requireAuth();
@@ -1779,7 +2070,7 @@ function SyncUI({ dryRun, password }) {
1779
2070
  /* @__PURE__ */ jsx15(MiniHeader, {}),
1780
2071
  /* @__PURE__ */ jsxs15(Box15, { marginTop: 1, flexDirection: "column", children: [
1781
2072
  status === "loading" && /* @__PURE__ */ jsx15(Spinner, { label: "Syncing to cloud (E2E encrypted)\u2026", type: "orbit", color: "magenta" }),
1782
- status === "success" && /* @__PURE__ */ jsxs15(Fragment5, { children: [
2073
+ status === "success" && /* @__PURE__ */ jsxs15(Fragment3, { children: [
1783
2074
  /* @__PURE__ */ jsx15(StarBurst, { label: message, color: "cyan" }),
1784
2075
  details.map((d) => /* @__PURE__ */ jsxs15(Box15, { children: [
1785
2076
  /* @__PURE__ */ jsx15(Text15, { color: "gray", children: " \xB7 " }),
@@ -1807,7 +2098,7 @@ function SyncUI({ dryRun, password }) {
1807
2098
  function registerSync(program2) {
1808
2099
  program2.command("sync").description("Sync tasks with cloud (E2E encrypted)").option("--dry-run", "Preview what would be synced without making changes", false).option("--password <password>", "Master password for encryption").action(async (options) => {
1809
2100
  const { render } = await import("ink");
1810
- render(React14.createElement(SyncUI, { dryRun: options.dryRun, password: options.password }));
2101
+ render(React15.createElement(SyncUI, { dryRun: options.dryRun, password: options.password }));
1811
2102
  });
1812
2103
  }
1813
2104
 
@@ -1873,18 +2164,19 @@ function registerExport(program2) {
1873
2164
  }
1874
2165
 
1875
2166
  // src/commands/upgrade.tsx
1876
- import React15, { useState as useState12, useEffect as useEffect13 } from "react";
1877
- import { Box as Box16, Text as Text16 } from "ink";
2167
+ import React16, { useState as useState13, useEffect as useEffect14 } from "react";
2168
+ import { Box as Box16, Text as Text16, useApp as useApp11 } from "ink";
1878
2169
  import { exec as exec2 } from "child_process";
1879
2170
 
1880
2171
  // src/lib/version.ts
1881
- var CLI_VERSION = "1.0.5";
2172
+ var CLI_VERSION = "1.0.7";
1882
2173
 
1883
2174
  // src/commands/upgrade.tsx
1884
2175
  import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1885
2176
  function isOutdated(current, latest) {
1886
- const cParts = current.split(".").map(Number);
1887
- const lParts = latest.split(".").map(Number);
2177
+ if (!current) return true;
2178
+ const cParts = current.replace(/^v/, "").split(".").map(Number);
2179
+ const lParts = latest.replace(/^v/, "").split(".").map(Number);
1888
2180
  for (let i = 0; i < 3; i++) {
1889
2181
  const c = cParts[i] ?? 0;
1890
2182
  const l = lParts[i] ?? 0;
@@ -1893,40 +2185,159 @@ function isOutdated(current, latest) {
1893
2185
  }
1894
2186
  return false;
1895
2187
  }
1896
- function UpgradeUI() {
1897
- const [status, setStatus] = useState12("checking");
1898
- const [latestVersion, setLatestVersion] = useState12("");
1899
- const [errorMsg, setErrorMsg] = useState12("");
1900
- useEffect13(() => {
2188
+ function UpgradeUI({ target }) {
2189
+ const { exit } = useApp11();
2190
+ const shouldUpgradeCli = !target || target.toLowerCase() === "cli";
2191
+ const shouldUpgradeMcp = !target || target.toLowerCase() === "mcp";
2192
+ const [cliState, setCliState] = useState13({
2193
+ name: "TaskAir CLI",
2194
+ pkgName: "taskair-cli",
2195
+ current: CLI_VERSION,
2196
+ latest: "",
2197
+ status: shouldUpgradeCli ? "checking" : "up-to-date",
2198
+ errorMsg: ""
2199
+ });
2200
+ const [mcpState, setMcpState] = useState13({
2201
+ name: "TaskAir MCP Server",
2202
+ pkgName: "taskair-mcp",
2203
+ current: "",
2204
+ latest: "",
2205
+ status: shouldUpgradeMcp ? "checking" : "up-to-date",
2206
+ errorMsg: ""
2207
+ });
2208
+ useEffect14(() => {
1901
2209
  async function checkAndUpgrade() {
1902
- try {
1903
- const res = await fetch("https://registry.npmjs.org/taskair-cli/latest", {
1904
- signal: AbortSignal.timeout(5e3)
1905
- // 5s timeout
2210
+ const tasks = [];
2211
+ let mcpCurrent = "";
2212
+ if (shouldUpgradeMcp) {
2213
+ mcpCurrent = await new Promise((resolve) => {
2214
+ exec2("npm list -g taskair-mcp --json", (err, stdout) => {
2215
+ if (err) {
2216
+ resolve("");
2217
+ return;
2218
+ }
2219
+ try {
2220
+ const data = JSON.parse(stdout);
2221
+ resolve(data.dependencies?.["taskair-mcp"]?.version || "");
2222
+ } catch {
2223
+ resolve("");
2224
+ }
2225
+ });
1906
2226
  });
1907
- if (!res.ok) {
1908
- throw new Error(`Failed to fetch latest version from npm registry. Status: ${res.status}`);
2227
+ }
2228
+ let cliLatest = "";
2229
+ if (shouldUpgradeCli) {
2230
+ try {
2231
+ const res = await fetch("https://registry.npmjs.org/taskair-cli/latest", {
2232
+ signal: AbortSignal.timeout(5e3)
2233
+ });
2234
+ if (res.ok) {
2235
+ const data = await res.json();
2236
+ cliLatest = data.version || "";
2237
+ }
2238
+ } catch {
2239
+ cliLatest = "";
1909
2240
  }
1910
- const data = await res.json();
1911
- const latest = data.version || "1.0.4";
1912
- setLatestVersion(latest);
1913
- if (isOutdated(CLI_VERSION, latest)) {
1914
- setStatus("upgrading");
1915
- exec2("npm install -g taskair-cli", (error, stdout, stderr) => {
1916
- if (error) {
1917
- setErrorMsg(error.message || stderr || "Failed to install update");
1918
- setStatus("error");
1919
- } else {
1920
- setStatus("success");
1921
- }
2241
+ }
2242
+ let mcpLatest = "";
2243
+ if (shouldUpgradeMcp) {
2244
+ try {
2245
+ const res = await fetch("https://registry.npmjs.org/taskair-mcp/latest", {
2246
+ signal: AbortSignal.timeout(5e3)
1922
2247
  });
2248
+ if (res.ok) {
2249
+ const data = await res.json();
2250
+ mcpLatest = data.version || "";
2251
+ }
2252
+ } catch {
2253
+ mcpLatest = "";
2254
+ }
2255
+ }
2256
+ if (shouldUpgradeCli) {
2257
+ if (!cliLatest) {
2258
+ setCliState((prev) => ({
2259
+ ...prev,
2260
+ status: "error",
2261
+ errorMsg: "Could not fetch latest version from registry."
2262
+ }));
1923
2263
  } else {
1924
- setStatus("up-to-date");
2264
+ setCliState((prev) => ({ ...prev, latest: cliLatest }));
2265
+ if (isOutdated(CLI_VERSION, cliLatest)) {
2266
+ setCliState((prev) => ({ ...prev, status: "upgrading" }));
2267
+ tasks.push(new Promise((resolve) => {
2268
+ exec2("npm install -g taskair-cli", (error, stdout, stderr) => {
2269
+ if (error) {
2270
+ setCliState((prev) => ({
2271
+ ...prev,
2272
+ status: "error",
2273
+ errorMsg: error.message || stderr || "Failed to install updates"
2274
+ }));
2275
+ } else {
2276
+ setCliState((prev) => ({ ...prev, status: "success" }));
2277
+ }
2278
+ resolve();
2279
+ });
2280
+ }));
2281
+ } else {
2282
+ setCliState((prev) => ({ ...prev, status: "up-to-date" }));
2283
+ }
2284
+ }
2285
+ }
2286
+ if (shouldUpgradeMcp) {
2287
+ if (!mcpLatest) {
2288
+ setMcpState((prev) => ({
2289
+ ...prev,
2290
+ status: "error",
2291
+ errorMsg: "Could not fetch latest version from registry."
2292
+ }));
2293
+ } else {
2294
+ setMcpState((prev) => ({ ...prev, latest: mcpLatest, current: mcpCurrent }));
2295
+ const isMcpInstalled = !!mcpCurrent;
2296
+ if (!isMcpInstalled) {
2297
+ if (target === "mcp") {
2298
+ setMcpState((prev) => ({ ...prev, status: "upgrading" }));
2299
+ tasks.push(new Promise((resolve) => {
2300
+ exec2("npm install -g taskair-mcp", (error, stdout, stderr) => {
2301
+ if (error) {
2302
+ setMcpState((prev) => ({
2303
+ ...prev,
2304
+ status: "error",
2305
+ errorMsg: error.message || stderr || "Failed to install"
2306
+ }));
2307
+ } else {
2308
+ setMcpState((prev) => ({ ...prev, status: "success" }));
2309
+ }
2310
+ resolve();
2311
+ });
2312
+ }));
2313
+ } else {
2314
+ setMcpState((prev) => ({ ...prev, status: "not-installed" }));
2315
+ }
2316
+ } else if (isOutdated(mcpCurrent, mcpLatest)) {
2317
+ setMcpState((prev) => ({ ...prev, status: "upgrading" }));
2318
+ tasks.push(new Promise((resolve) => {
2319
+ exec2("npm install -g taskair-mcp", (error, stdout, stderr) => {
2320
+ if (error) {
2321
+ setMcpState((prev) => ({
2322
+ ...prev,
2323
+ status: "error",
2324
+ errorMsg: error.message || stderr || "Failed to install updates"
2325
+ }));
2326
+ } else {
2327
+ setMcpState((prev) => ({ ...prev, status: "success" }));
2328
+ }
2329
+ resolve();
2330
+ });
2331
+ }));
2332
+ } else {
2333
+ setMcpState((prev) => ({ ...prev, status: "up-to-date" }));
2334
+ }
1925
2335
  }
1926
- } catch (err) {
1927
- setErrorMsg(err.message || "Network error checking for updates.");
1928
- setStatus("error");
1929
2336
  }
2337
+ if (tasks.length > 0) {
2338
+ await Promise.all(tasks);
2339
+ }
2340
+ setTimeout(() => exit(), 2500);
1930
2341
  }
1931
2342
  checkAndUpgrade();
1932
2343
  }, []);
@@ -1934,45 +2345,68 @@ function UpgradeUI() {
1934
2345
  /* @__PURE__ */ jsx16(MiniHeader, {}),
1935
2346
  /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", borderStyle: "round", borderColor: "#7B61FF", paddingX: 2, paddingY: 1, marginTop: 1, children: [
1936
2347
  /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "cyan", bold: true, children: "\u2726 TaskAir Upgrade" }) }),
1937
- status === "checking" && /* @__PURE__ */ jsx16(Spinner, { label: `Checking npm registry for updates... (Current: v${CLI_VERSION})`, type: "orbit" }),
1938
- status === "upgrading" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
1939
- /* @__PURE__ */ jsxs16(Text16, { color: "yellow", bold: true, children: [
1940
- "\u2726 New version detected: v",
2348
+ shouldUpgradeCli && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", marginBottom: 1, children: [
2349
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: "white", children: "TaskAir CLI:" }),
2350
+ cliState.status === "checking" && /* @__PURE__ */ jsx16(Spinner, { label: `Checking latest version... (Current: v${CLI_VERSION})`, type: "orbit" }),
2351
+ cliState.status === "upgrading" && /* @__PURE__ */ jsx16(Spinner, { label: `Upgrading globally: v${CLI_VERSION} \u2192 v${cliState.latest}...`, type: "star", color: "yellow" }),
2352
+ cliState.status === "success" && /* @__PURE__ */ jsxs16(Text16, { color: "green", children: [
2353
+ "\u2713 Successfully upgraded to v",
2354
+ cliState.latest,
2355
+ "!"
2356
+ ] }),
2357
+ cliState.status === "up-to-date" && /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
2358
+ "\u2713 Already up to date (v",
1941
2359
  CLI_VERSION,
1942
- " \u2192 v",
1943
- latestVersion
2360
+ ")"
1944
2361
  ] }),
1945
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Spinner, { label: "Upgrading globally via npm install -g taskair-cli...", type: "star", color: "yellow" }) })
1946
- ] }),
1947
- status === "success" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
1948
- /* @__PURE__ */ jsx16(StatusBadge, { type: "success", message: `TaskAir CLI successfully upgraded to v${latestVersion}!` }),
1949
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "gray", children: "You are now running the latest space-grade build." }) })
1950
- ] }),
1951
- status === "up-to-date" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
1952
- /* @__PURE__ */ jsx16(StatusBadge, { type: "success", message: `TaskAir CLI is already up to date (v${CLI_VERSION}).` }),
1953
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "gray", children: "No upgrade required at this time. Blast off! \u{1F680}" }) })
2362
+ cliState.status === "error" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
2363
+ /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
2364
+ "\u2717 Upgrade failed: ",
2365
+ cliState.errorMsg
2366
+ ] }),
2367
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "Run manually: npm install -g taskair-cli" })
2368
+ ] })
1954
2369
  ] }),
1955
- status === "error" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
1956
- /* @__PURE__ */ jsx16(StatusBadge, { type: "error", message: `Upgrade failed: ${errorMsg}` }),
1957
- /* @__PURE__ */ jsxs16(Box16, { marginTop: 1, flexDirection: "column", children: [
1958
- /* @__PURE__ */ jsx16(Text16, { color: "gray", children: "You can try upgrading manually by running:" }),
1959
- /* @__PURE__ */ jsx16(Text16, { color: "cyan", bold: true, children: " npm install -g taskair-cli" }),
1960
- /* @__PURE__ */ jsx16(Text16, { color: "gray", marginTop: 1, children: "If permission errors occur, run with administrative rights (sudo)." })
2370
+ shouldUpgradeMcp && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
2371
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: "white", children: "TaskAir MCP Server:" }),
2372
+ mcpState.status === "checking" && /* @__PURE__ */ jsx16(Spinner, { label: `Checking latest version... (Current: ${mcpState.current ? "v" + mcpState.current : "not installed"})`, type: "orbit" }),
2373
+ mcpState.status === "upgrading" && /* @__PURE__ */ jsx16(Spinner, { label: `Upgrading globally: ${mcpState.current ? "v" + mcpState.current : "installing"} \u2192 v${mcpState.latest}...`, type: "star", color: "yellow" }),
2374
+ mcpState.status === "success" && /* @__PURE__ */ jsxs16(Text16, { color: "green", children: [
2375
+ "\u2713 Successfully upgraded to v",
2376
+ mcpState.latest,
2377
+ "!"
2378
+ ] }),
2379
+ mcpState.status === "up-to-date" && /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
2380
+ "\u2713 Already up to date (v",
2381
+ mcpState.current,
2382
+ ")"
2383
+ ] }),
2384
+ mcpState.status === "not-installed" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
2385
+ /* @__PURE__ */ jsx16(Text16, { color: "yellow", children: "\u2601 Not installed globally." }),
2386
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "Install manually: npm install -g taskair-mcp" }),
2387
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "Or install now with: taskair upgrade mcp" })
2388
+ ] }),
2389
+ mcpState.status === "error" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
2390
+ /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
2391
+ "\u2717 Upgrade failed: ",
2392
+ mcpState.errorMsg
2393
+ ] }),
2394
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "Run manually: npm install -g taskair-mcp" })
1961
2395
  ] })
1962
2396
  ] })
1963
2397
  ] })
1964
2398
  ] });
1965
2399
  }
1966
2400
  function registerUpgrade(program2) {
1967
- program2.command("upgrade").description("Upgrade TaskAir CLI to the latest version").action(async () => {
2401
+ program2.command("upgrade [target]").description('Upgrade TaskAir CLI and MCP Server. Target can be "cli", "mcp", or omitted to upgrade both.').action(async (target) => {
1968
2402
  const { render } = await import("ink");
1969
- render(React15.createElement(UpgradeUI));
2403
+ render(React16.createElement(UpgradeUI, { target }));
1970
2404
  });
1971
2405
  }
1972
2406
 
1973
2407
  // src/index.ts
1974
2408
  program.name("taskair").description(
1975
- "\u2726 Space-themed task management with E2E encryption \xB7 AI-native \xB7 Privacy-first"
2409
+ "\u2726 Privacy-first task management with E2E encryption \xB7 AI-native \xB7 CLI \xB7 MCP \xB7 Web"
1976
2410
  ).version(CLI_VERSION, "-v, --version", "Output the current version").helpOption("-h, --help", "Display help information");
1977
2411
  registerConfig(program);
1978
2412
  registerLogin(program);
@@ -1990,10 +2424,14 @@ registerExport(program);
1990
2424
  program.addHelpText(
1991
2425
  "beforeAll",
1992
2426
  `
1993
- \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1994
- \u2551 \u2726 T A S K A I R \u2014 Space-grade task management \u2726 \u2551
1995
- \u2551 End-to-end encrypted \xB7 AI-native \xB7 Developer-first \u2551
1996
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
2427
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
2428
+ \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
2429
+ \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
2430
+ \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
2431
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
2432
+ \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
2433
+
2434
+ \u2726 CLI \xB7 MCP \xB7 Web \u2726
1997
2435
  `
1998
2436
  );
1999
2437
  var args = [...process.argv];