taskair-cli 1.0.5 → 1.0.6

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 +309 -129
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -981,6 +981,50 @@ function computeStats() {
981
981
  };
982
982
  }
983
983
 
984
+ // src/lib/crypto.ts
985
+ import {
986
+ pbkdf2Sync,
987
+ createCipheriv,
988
+ createDecipheriv,
989
+ randomBytes,
990
+ createHash
991
+ } from "crypto";
992
+ var PBKDF2_ITERATIONS = 1e5;
993
+ var KEY_LENGTH = 32;
994
+ var SALT_LENGTH = 16;
995
+ var IV_LENGTH = 16;
996
+ function deriveMasterKey(password, salt) {
997
+ return pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
998
+ }
999
+ function deriveEncryptionKey(masterKey) {
1000
+ const hash = createHash("sha256");
1001
+ hash.update(masterKey);
1002
+ hash.update("taskair-encryption");
1003
+ return hash.digest();
1004
+ }
1005
+ function encrypt(plaintext, password) {
1006
+ const salt = randomBytes(SALT_LENGTH);
1007
+ const iv = randomBytes(IV_LENGTH);
1008
+ const masterKey = deriveMasterKey(password, salt);
1009
+ const encKey = deriveEncryptionKey(masterKey);
1010
+ const cipher = createCipheriv("aes-256-gcm", encKey, iv);
1011
+ const encrypted = Buffer.concat([
1012
+ cipher.update(plaintext, "utf8"),
1013
+ cipher.final()
1014
+ ]);
1015
+ const authTag = cipher.getAuthTag();
1016
+ return {
1017
+ version: "1",
1018
+ salt: salt.toString("base64"),
1019
+ iv: iv.toString("base64"),
1020
+ ciphertext: encrypted.toString("base64"),
1021
+ authTag: authTag.toString("base64")
1022
+ };
1023
+ }
1024
+ function checksum(plaintext) {
1025
+ return createHash("sha256").update(plaintext).digest("hex");
1026
+ }
1027
+
984
1028
  // src/commands/add.tsx
985
1029
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
986
1030
  function AddUI({
@@ -991,32 +1035,72 @@ function AddUI({
991
1035
  }) {
992
1036
  const { exit } = useApp4();
993
1037
  const [status, setStatus] = useState6("working");
1038
+ const [syncStatus, setSyncStatus] = useState6("idle");
1039
+ const [syncMessage, setSyncMessage] = useState6("");
994
1040
  const [taskId, setTaskId] = useState6("");
995
1041
  const [errorMsg, setErrorMsg] = useState6("");
996
1042
  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);
1043
+ async function createTaskAndSync() {
1044
+ try {
1045
+ const auth = requireAuth();
1046
+ const task = {
1047
+ id: uuidv42(),
1048
+ description,
1049
+ priority,
1050
+ status: "pending",
1051
+ tags,
1052
+ due_date: due,
1053
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1054
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
1055
+ device_id: auth.deviceId,
1056
+ vector_clock: { [auth.deviceId]: 1 }
1057
+ };
1058
+ insertTask(task);
1059
+ setTaskId(task.id.slice(0, 8));
1060
+ setStatus("success");
1061
+ setSyncStatus("syncing");
1062
+ const tasks = getAllTasks();
1063
+ const bundle = {
1064
+ tasks,
1065
+ schema_version: "1",
1066
+ exported_at: (/* @__PURE__ */ new Date()).toISOString()
1067
+ };
1068
+ const plaintext = JSON.stringify(bundle);
1069
+ const blobChecksum = checksum(plaintext);
1070
+ const syncPassword = auth.password;
1071
+ if (!syncPassword) {
1072
+ setSyncStatus("failed");
1073
+ setSyncMessage('Encryption password required. Run "taskair sync --password <pwd>".');
1074
+ setTimeout(() => exit(), 3e3);
1075
+ return;
1076
+ }
1077
+ const blob = encrypt(plaintext, syncPassword);
1078
+ const res = await apiUploadSync(
1079
+ auth.apiUrl,
1080
+ auth.accessToken,
1081
+ blob,
1082
+ blobChecksum,
1083
+ auth.deviceId
1084
+ );
1085
+ if (res.success) {
1086
+ clearSyncQueue();
1087
+ setSyncStatus("synced");
1088
+ setTimeout(() => exit(), 2e3);
1089
+ } else if (res.error?.code === "NETWORK_ERROR") {
1090
+ setSyncStatus("offline");
1091
+ setTimeout(() => exit(), 2500);
1092
+ } else {
1093
+ setSyncStatus("failed");
1094
+ setSyncMessage(res.error?.message ?? "Sync failed");
1095
+ setTimeout(() => exit(), 3500);
1096
+ }
1097
+ } catch (e) {
1098
+ setErrorMsg(e.message);
1099
+ setStatus("error");
1100
+ setTimeout(() => exit(e), 2e3);
1101
+ }
1019
1102
  }
1103
+ createTaskAndSync();
1020
1104
  }, []);
1021
1105
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", padding: 1, children: [
1022
1106
  /* @__PURE__ */ jsx8(MiniHeader, {}),
@@ -1048,14 +1132,13 @@ function AddUI({
1048
1132
  /* @__PURE__ */ jsx8(Text8, { color: "#7B61FF", children: tags.join(", ") })
1049
1133
  ] }),
1050
1134
  /* @__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"
1135
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " Sync: " }),
1136
+ syncStatus === "syncing" && /* @__PURE__ */ jsx8(Text8, { color: "magenta", children: "Syncing to cloud (E2E encrypted)..." }),
1137
+ syncStatus === "synced" && /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u2713 Synced to cloud" }),
1138
+ syncStatus === "offline" && /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "\u2601 Offline (queued locally)" }),
1139
+ syncStatus === "failed" && /* @__PURE__ */ jsxs8(Text8, { color: "red", children: [
1140
+ "\u2717 ",
1141
+ syncMessage
1059
1142
  ] })
1060
1143
  ] })
1061
1144
  ] })
@@ -1652,52 +1735,6 @@ function registerStats(program2) {
1652
1735
  // src/commands/sync.tsx
1653
1736
  import React14, { useState as useState11, useEffect as useEffect12 } from "react";
1654
1737
  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
1738
  import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1702
1739
  function SyncUI({ dryRun, password }) {
1703
1740
  const { exit } = useApp10();
@@ -1874,17 +1911,18 @@ function registerExport(program2) {
1874
1911
 
1875
1912
  // src/commands/upgrade.tsx
1876
1913
  import React15, { useState as useState12, useEffect as useEffect13 } from "react";
1877
- import { Box as Box16, Text as Text16 } from "ink";
1914
+ import { Box as Box16, Text as Text16, useApp as useApp11 } from "ink";
1878
1915
  import { exec as exec2 } from "child_process";
1879
1916
 
1880
1917
  // src/lib/version.ts
1881
- var CLI_VERSION = "1.0.5";
1918
+ var CLI_VERSION = "1.0.6";
1882
1919
 
1883
1920
  // src/commands/upgrade.tsx
1884
1921
  import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1885
1922
  function isOutdated(current, latest) {
1886
- const cParts = current.split(".").map(Number);
1887
- const lParts = latest.split(".").map(Number);
1923
+ if (!current) return true;
1924
+ const cParts = current.replace(/^v/, "").split(".").map(Number);
1925
+ const lParts = latest.replace(/^v/, "").split(".").map(Number);
1888
1926
  for (let i = 0; i < 3; i++) {
1889
1927
  const c = cParts[i] ?? 0;
1890
1928
  const l = lParts[i] ?? 0;
@@ -1893,40 +1931,159 @@ function isOutdated(current, latest) {
1893
1931
  }
1894
1932
  return false;
1895
1933
  }
1896
- function UpgradeUI() {
1897
- const [status, setStatus] = useState12("checking");
1898
- const [latestVersion, setLatestVersion] = useState12("");
1899
- const [errorMsg, setErrorMsg] = useState12("");
1934
+ function UpgradeUI({ target }) {
1935
+ const { exit } = useApp11();
1936
+ const shouldUpgradeCli = !target || target.toLowerCase() === "cli";
1937
+ const shouldUpgradeMcp = !target || target.toLowerCase() === "mcp";
1938
+ const [cliState, setCliState] = useState12({
1939
+ name: "TaskAir CLI",
1940
+ pkgName: "taskair-cli",
1941
+ current: CLI_VERSION,
1942
+ latest: "",
1943
+ status: shouldUpgradeCli ? "checking" : "up-to-date",
1944
+ errorMsg: ""
1945
+ });
1946
+ const [mcpState, setMcpState] = useState12({
1947
+ name: "TaskAir MCP Server",
1948
+ pkgName: "taskair-mcp",
1949
+ current: "",
1950
+ latest: "",
1951
+ status: shouldUpgradeMcp ? "checking" : "up-to-date",
1952
+ errorMsg: ""
1953
+ });
1900
1954
  useEffect13(() => {
1901
1955
  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
1956
+ const tasks = [];
1957
+ let mcpCurrent = "";
1958
+ if (shouldUpgradeMcp) {
1959
+ mcpCurrent = await new Promise((resolve) => {
1960
+ exec2("npm list -g taskair-mcp --json", (err, stdout) => {
1961
+ if (err) {
1962
+ resolve("");
1963
+ return;
1964
+ }
1965
+ try {
1966
+ const data = JSON.parse(stdout);
1967
+ resolve(data.dependencies?.["taskair-mcp"]?.version || "");
1968
+ } catch {
1969
+ resolve("");
1970
+ }
1971
+ });
1906
1972
  });
1907
- if (!res.ok) {
1908
- throw new Error(`Failed to fetch latest version from npm registry. Status: ${res.status}`);
1973
+ }
1974
+ let cliLatest = "";
1975
+ if (shouldUpgradeCli) {
1976
+ try {
1977
+ const res = await fetch("https://registry.npmjs.org/taskair-cli/latest", {
1978
+ signal: AbortSignal.timeout(5e3)
1979
+ });
1980
+ if (res.ok) {
1981
+ const data = await res.json();
1982
+ cliLatest = data.version || "";
1983
+ }
1984
+ } catch {
1985
+ cliLatest = "";
1909
1986
  }
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
- }
1987
+ }
1988
+ let mcpLatest = "";
1989
+ if (shouldUpgradeMcp) {
1990
+ try {
1991
+ const res = await fetch("https://registry.npmjs.org/taskair-mcp/latest", {
1992
+ signal: AbortSignal.timeout(5e3)
1922
1993
  });
1994
+ if (res.ok) {
1995
+ const data = await res.json();
1996
+ mcpLatest = data.version || "";
1997
+ }
1998
+ } catch {
1999
+ mcpLatest = "";
2000
+ }
2001
+ }
2002
+ if (shouldUpgradeCli) {
2003
+ if (!cliLatest) {
2004
+ setCliState((prev) => ({
2005
+ ...prev,
2006
+ status: "error",
2007
+ errorMsg: "Could not fetch latest version from registry."
2008
+ }));
1923
2009
  } else {
1924
- setStatus("up-to-date");
2010
+ setCliState((prev) => ({ ...prev, latest: cliLatest }));
2011
+ if (isOutdated(CLI_VERSION, cliLatest)) {
2012
+ setCliState((prev) => ({ ...prev, status: "upgrading" }));
2013
+ tasks.push(new Promise((resolve) => {
2014
+ exec2("npm install -g taskair-cli", (error, stdout, stderr) => {
2015
+ if (error) {
2016
+ setCliState((prev) => ({
2017
+ ...prev,
2018
+ status: "error",
2019
+ errorMsg: error.message || stderr || "Failed to install updates"
2020
+ }));
2021
+ } else {
2022
+ setCliState((prev) => ({ ...prev, status: "success" }));
2023
+ }
2024
+ resolve();
2025
+ });
2026
+ }));
2027
+ } else {
2028
+ setCliState((prev) => ({ ...prev, status: "up-to-date" }));
2029
+ }
2030
+ }
2031
+ }
2032
+ if (shouldUpgradeMcp) {
2033
+ if (!mcpLatest) {
2034
+ setMcpState((prev) => ({
2035
+ ...prev,
2036
+ status: "error",
2037
+ errorMsg: "Could not fetch latest version from registry."
2038
+ }));
2039
+ } else {
2040
+ setMcpState((prev) => ({ ...prev, latest: mcpLatest, current: mcpCurrent }));
2041
+ const isMcpInstalled = !!mcpCurrent;
2042
+ if (!isMcpInstalled) {
2043
+ if (target === "mcp") {
2044
+ setMcpState((prev) => ({ ...prev, status: "upgrading" }));
2045
+ tasks.push(new Promise((resolve) => {
2046
+ exec2("npm install -g taskair-mcp", (error, stdout, stderr) => {
2047
+ if (error) {
2048
+ setMcpState((prev) => ({
2049
+ ...prev,
2050
+ status: "error",
2051
+ errorMsg: error.message || stderr || "Failed to install"
2052
+ }));
2053
+ } else {
2054
+ setMcpState((prev) => ({ ...prev, status: "success" }));
2055
+ }
2056
+ resolve();
2057
+ });
2058
+ }));
2059
+ } else {
2060
+ setMcpState((prev) => ({ ...prev, status: "not-installed" }));
2061
+ }
2062
+ } else if (isOutdated(mcpCurrent, mcpLatest)) {
2063
+ setMcpState((prev) => ({ ...prev, status: "upgrading" }));
2064
+ tasks.push(new Promise((resolve) => {
2065
+ exec2("npm install -g taskair-mcp", (error, stdout, stderr) => {
2066
+ if (error) {
2067
+ setMcpState((prev) => ({
2068
+ ...prev,
2069
+ status: "error",
2070
+ errorMsg: error.message || stderr || "Failed to install updates"
2071
+ }));
2072
+ } else {
2073
+ setMcpState((prev) => ({ ...prev, status: "success" }));
2074
+ }
2075
+ resolve();
2076
+ });
2077
+ }));
2078
+ } else {
2079
+ setMcpState((prev) => ({ ...prev, status: "up-to-date" }));
2080
+ }
1925
2081
  }
1926
- } catch (err) {
1927
- setErrorMsg(err.message || "Network error checking for updates.");
1928
- setStatus("error");
1929
2082
  }
2083
+ if (tasks.length > 0) {
2084
+ await Promise.all(tasks);
2085
+ }
2086
+ setTimeout(() => exit(), 2500);
1930
2087
  }
1931
2088
  checkAndUpgrade();
1932
2089
  }, []);
@@ -1934,39 +2091,62 @@ function UpgradeUI() {
1934
2091
  /* @__PURE__ */ jsx16(MiniHeader, {}),
1935
2092
  /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", borderStyle: "round", borderColor: "#7B61FF", paddingX: 2, paddingY: 1, marginTop: 1, children: [
1936
2093
  /* @__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",
2094
+ shouldUpgradeCli && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", marginBottom: 1, children: [
2095
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: "white", children: "TaskAir CLI:" }),
2096
+ cliState.status === "checking" && /* @__PURE__ */ jsx16(Spinner, { label: `Checking latest version... (Current: v${CLI_VERSION})`, type: "orbit" }),
2097
+ cliState.status === "upgrading" && /* @__PURE__ */ jsx16(Spinner, { label: `Upgrading globally: v${CLI_VERSION} \u2192 v${cliState.latest}...`, type: "star", color: "yellow" }),
2098
+ cliState.status === "success" && /* @__PURE__ */ jsxs16(Text16, { color: "green", children: [
2099
+ "\u2713 Successfully upgraded to v",
2100
+ cliState.latest,
2101
+ "!"
2102
+ ] }),
2103
+ cliState.status === "up-to-date" && /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
2104
+ "\u2713 Already up to date (v",
1941
2105
  CLI_VERSION,
1942
- " \u2192 v",
1943
- latestVersion
2106
+ ")"
1944
2107
  ] }),
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}" }) })
2108
+ cliState.status === "error" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
2109
+ /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
2110
+ "\u2717 Upgrade failed: ",
2111
+ cliState.errorMsg
2112
+ ] }),
2113
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "Run manually: npm install -g taskair-cli" })
2114
+ ] })
1954
2115
  ] }),
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)." })
2116
+ shouldUpgradeMcp && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
2117
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: "white", children: "TaskAir MCP Server:" }),
2118
+ mcpState.status === "checking" && /* @__PURE__ */ jsx16(Spinner, { label: `Checking latest version... (Current: ${mcpState.current ? "v" + mcpState.current : "not installed"})`, type: "orbit" }),
2119
+ mcpState.status === "upgrading" && /* @__PURE__ */ jsx16(Spinner, { label: `Upgrading globally: ${mcpState.current ? "v" + mcpState.current : "installing"} \u2192 v${mcpState.latest}...`, type: "star", color: "yellow" }),
2120
+ mcpState.status === "success" && /* @__PURE__ */ jsxs16(Text16, { color: "green", children: [
2121
+ "\u2713 Successfully upgraded to v",
2122
+ mcpState.latest,
2123
+ "!"
2124
+ ] }),
2125
+ mcpState.status === "up-to-date" && /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
2126
+ "\u2713 Already up to date (v",
2127
+ mcpState.current,
2128
+ ")"
2129
+ ] }),
2130
+ mcpState.status === "not-installed" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
2131
+ /* @__PURE__ */ jsx16(Text16, { color: "yellow", children: "\u2601 Not installed globally." }),
2132
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "Install manually: npm install -g taskair-mcp" }),
2133
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "Or install now with: taskair upgrade mcp" })
2134
+ ] }),
2135
+ mcpState.status === "error" && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
2136
+ /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
2137
+ "\u2717 Upgrade failed: ",
2138
+ mcpState.errorMsg
2139
+ ] }),
2140
+ /* @__PURE__ */ jsx16(Text16, { color: "gray", dimColor: true, children: "Run manually: npm install -g taskair-mcp" })
1961
2141
  ] })
1962
2142
  ] })
1963
2143
  ] })
1964
2144
  ] });
1965
2145
  }
1966
2146
  function registerUpgrade(program2) {
1967
- program2.command("upgrade").description("Upgrade TaskAir CLI to the latest version").action(async () => {
2147
+ 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
2148
  const { render } = await import("ink");
1969
- render(React15.createElement(UpgradeUI));
2149
+ render(React15.createElement(UpgradeUI, { target }));
1970
2150
  });
1971
2151
  }
1972
2152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskair-cli",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Space-themed, privacy-first task management CLI with E2E encryption",
5
5
  "type": "module",
6
6
  "bin": {