taskair-cli 1.0.4 → 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 +462 -86
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -383,9 +383,14 @@ function ConfigUI({ initialApiUrl }) {
383
383
  <title>TaskAir CLI Authenticated</title>
384
384
  <style>
385
385
  body {
386
- background: #0D0D11;
386
+ background:
387
+ radial-gradient(ellipse 80% 60% at 50% -20%, rgba(123, 97, 255, 0.15) 0%, transparent 70%),
388
+ radial-gradient(ellipse 60% 50% at 80% 20%, rgba(0, 112, 243, 0.12) 0%, transparent 60%),
389
+ radial-gradient(ellipse 50% 40% at 20% 30%, rgba(0, 223, 216, 0.1) 0%, transparent 60%),
390
+ radial-gradient(ellipse 70% 40% at 60% 60%, rgba(235, 54, 127, 0.08) 0%, transparent 70%),
391
+ #0D0D11;
387
392
  color: #FFFFFF;
388
- font-family: -apple-system, BlinkMacSystemFont, sans-serif;
393
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
389
394
  display: flex;
390
395
  align-items: center;
391
396
  justify-content: center;
@@ -393,28 +398,120 @@ function ConfigUI({ initialApiUrl }) {
393
398
  margin: 0;
394
399
  }
395
400
  .card {
396
- background: #13131A;
397
- border: 1px solid #23232F;
398
- border-radius: 12px;
401
+ background: rgba(19, 19, 26, 0.8);
402
+ backdrop-filter: blur(12px);
403
+ -webkit-backdrop-filter: blur(12px);
404
+ border: 1px solid rgba(255, 255, 255, 0.08);
405
+ border-radius: 16px;
399
406
  padding: 40px;
400
407
  text-align: center;
401
- box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
408
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
402
409
  max-width: 400px;
403
410
  width: 100%;
411
+ animation: fadeUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
404
412
  }
405
- h1 {
413
+ .logo-container {
414
+ background: #0D0D11;
415
+ border: 1px solid #1F1F2E;
416
+ border-radius: 8px;
417
+ padding: 8px 16px;
418
+ display: inline-flex;
419
+ align-items: center;
420
+ justify-content: center;
421
+ margin-bottom: 24px;
422
+ animation: pulseRing 2s infinite ease-in-out;
423
+ }
424
+ .text-logo {
425
+ display: inline-flex;
426
+ align-items: center;
427
+ gap: 6px;
428
+ text-decoration: none !important;
429
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
430
+ font-size: 16px;
431
+ font-weight: 600;
432
+ letter-spacing: -0.5px;
433
+ color: #FFFFFF;
434
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
435
+ flex-shrink: 0;
436
+ }
437
+ .text-logo-brand {
438
+ transition: letter-spacing 0.3s ease, color 0.3s ease;
439
+ }
440
+ .text-logo-accent {
441
+ background: linear-gradient(135deg, #7B61FF, #0070f3);
442
+ -webkit-background-clip: text;
443
+ -webkit-text-fill-color: transparent;
444
+ }
445
+ .text-logo-spark {
406
446
  color: #7B61FF;
407
- margin-top: 0;
408
- font-size: 24px;
447
+ font-size: 11px;
448
+ opacity: 0.7;
449
+ transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.3s ease, color 0.3s ease;
450
+ }
451
+ .text-logo:hover {
452
+ opacity: 0.95;
453
+ }
454
+ .text-logo:hover .text-logo-brand {
455
+ letter-spacing: 0.3px;
456
+ }
457
+ .text-logo:hover .text-logo-spark {
458
+ transform: scale(1.3) rotate(180deg);
459
+ opacity: 1;
460
+ color: #00dfd8;
461
+ }
462
+ .success-badge {
463
+ display: inline-flex;
464
+ align-items: center;
465
+ gap: 6px;
466
+ background: rgba(16, 185, 129, 0.1);
467
+ color: #10B981;
468
+ border: 1px solid rgba(16, 185, 129, 0.2);
469
+ padding: 6px 14px;
470
+ border-radius: 99px;
471
+ font-size: 13px;
472
+ font-weight: 500;
473
+ margin-bottom: 20px;
474
+ }
475
+ h1 {
476
+ color: #FFFFFF;
477
+ margin: 0 0 12px 0;
478
+ font-size: 22px;
479
+ font-weight: 600;
480
+ letter-spacing: -0.5px;
409
481
  }
410
482
  p {
411
483
  color: #A1A1B5;
412
484
  font-size: 14px;
485
+ line-height: 1.6;
486
+ margin: 0;
487
+ }
488
+ @keyframes fadeUp {
489
+ from { opacity: 0; transform: translateY(20px); }
490
+ to { opacity: 1; transform: translateY(0); }
491
+ }
492
+ @keyframes pulseRing {
493
+ 0% { transform: scale(1); box-shadow: 0 4px 14px rgba(123, 97, 255, 0.15); }
494
+ 50% { transform: scale(1.03); box-shadow: 0 6px 20px rgba(123, 97, 255, 0.35); }
495
+ 100% { transform: scale(1); box-shadow: 0 4px 14px rgba(123, 97, 255, 0.15); }
413
496
  }
414
497
  </style>
415
498
  </head>
416
499
  <body>
417
500
  <div class="card">
501
+ <div class="logo-container">
502
+ <a href="#" class="text-logo">
503
+ <span class="text-logo-brand" style="color: #FFFFFF;">Task<span class="text-logo-accent">Air</span></span>
504
+ <span class="text-logo-spark">\u2726</span>
505
+ </a>
506
+ </div>
507
+ <div>
508
+ <div class="success-badge">
509
+ <svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24" style="margin-top: 1px;">
510
+ <polyline points="20 6 9 17 4 12"/>
511
+ </svg>
512
+ <span>CLI Connected</span>
513
+ </div>
514
+ </div>
418
515
  <h1>Authenticated Successfully</h1>
419
516
  <p>You can close this tab and return to your terminal.</p>
420
517
  </div>
@@ -884,6 +981,50 @@ function computeStats() {
884
981
  };
885
982
  }
886
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
+
887
1028
  // src/commands/add.tsx
888
1029
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
889
1030
  function AddUI({
@@ -894,32 +1035,72 @@ function AddUI({
894
1035
  }) {
895
1036
  const { exit } = useApp4();
896
1037
  const [status, setStatus] = useState6("working");
1038
+ const [syncStatus, setSyncStatus] = useState6("idle");
1039
+ const [syncMessage, setSyncMessage] = useState6("");
897
1040
  const [taskId, setTaskId] = useState6("");
898
1041
  const [errorMsg, setErrorMsg] = useState6("");
899
1042
  useEffect6(() => {
900
- try {
901
- const auth = requireAuth();
902
- const task = {
903
- id: uuidv42(),
904
- description,
905
- priority,
906
- status: "pending",
907
- tags,
908
- due_date: due,
909
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
910
- updated_at: (/* @__PURE__ */ new Date()).toISOString(),
911
- device_id: auth.deviceId,
912
- vector_clock: { [auth.deviceId]: 1 }
913
- };
914
- insertTask(task);
915
- setTaskId(task.id.slice(0, 8));
916
- setStatus("success");
917
- setTimeout(() => exit(), 1500);
918
- } catch (e) {
919
- setErrorMsg(e.message);
920
- setStatus("error");
921
- 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
+ }
922
1102
  }
1103
+ createTaskAndSync();
923
1104
  }, []);
924
1105
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", padding: 1, children: [
925
1106
  /* @__PURE__ */ jsx8(MiniHeader, {}),
@@ -951,14 +1132,13 @@ function AddUI({
951
1132
  /* @__PURE__ */ jsx8(Text8, { color: "#7B61FF", children: tags.join(", ") })
952
1133
  ] }),
953
1134
  /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
954
- /* @__PURE__ */ jsxs8(Text8, { color: "gray", dimColor: true, children: [
955
- "Queued for sync \xB7 Run",
956
- " "
957
- ] }),
958
- /* @__PURE__ */ jsx8(Text8, { color: "cyan", dimColor: true, children: "taskair sync" }),
959
- /* @__PURE__ */ jsxs8(Text8, { color: "gray", dimColor: true, children: [
960
- " ",
961
- "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
962
1142
  ] })
963
1143
  ] })
964
1144
  ] })
@@ -1555,52 +1735,6 @@ function registerStats(program2) {
1555
1735
  // src/commands/sync.tsx
1556
1736
  import React14, { useState as useState11, useEffect as useEffect12 } from "react";
1557
1737
  import { Box as Box15, Text as Text15, useApp as useApp10 } from "ink";
1558
-
1559
- // src/lib/crypto.ts
1560
- import {
1561
- pbkdf2Sync,
1562
- createCipheriv,
1563
- createDecipheriv,
1564
- randomBytes,
1565
- createHash
1566
- } from "crypto";
1567
- var PBKDF2_ITERATIONS = 1e5;
1568
- var KEY_LENGTH = 32;
1569
- var SALT_LENGTH = 16;
1570
- var IV_LENGTH = 16;
1571
- function deriveMasterKey(password, salt) {
1572
- return pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
1573
- }
1574
- function deriveEncryptionKey(masterKey) {
1575
- const hash = createHash("sha256");
1576
- hash.update(masterKey);
1577
- hash.update("taskair-encryption");
1578
- return hash.digest();
1579
- }
1580
- function encrypt(plaintext, password) {
1581
- const salt = randomBytes(SALT_LENGTH);
1582
- const iv = randomBytes(IV_LENGTH);
1583
- const masterKey = deriveMasterKey(password, salt);
1584
- const encKey = deriveEncryptionKey(masterKey);
1585
- const cipher = createCipheriv("aes-256-gcm", encKey, iv);
1586
- const encrypted = Buffer.concat([
1587
- cipher.update(plaintext, "utf8"),
1588
- cipher.final()
1589
- ]);
1590
- const authTag = cipher.getAuthTag();
1591
- return {
1592
- version: "1",
1593
- salt: salt.toString("base64"),
1594
- iv: iv.toString("base64"),
1595
- ciphertext: encrypted.toString("base64"),
1596
- authTag: authTag.toString("base64")
1597
- };
1598
- }
1599
- function checksum(plaintext) {
1600
- return createHash("sha256").update(plaintext).digest("hex");
1601
- }
1602
-
1603
- // src/commands/sync.tsx
1604
1738
  import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1605
1739
  function SyncUI({ dryRun, password }) {
1606
1740
  const { exit } = useApp10();
@@ -1775,14 +1909,256 @@ function registerExport(program2) {
1775
1909
  });
1776
1910
  }
1777
1911
 
1912
+ // src/commands/upgrade.tsx
1913
+ import React15, { useState as useState12, useEffect as useEffect13 } from "react";
1914
+ import { Box as Box16, Text as Text16, useApp as useApp11 } from "ink";
1915
+ import { exec as exec2 } from "child_process";
1916
+
1917
+ // src/lib/version.ts
1918
+ var CLI_VERSION = "1.0.6";
1919
+
1920
+ // src/commands/upgrade.tsx
1921
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1922
+ function isOutdated(current, latest) {
1923
+ if (!current) return true;
1924
+ const cParts = current.replace(/^v/, "").split(".").map(Number);
1925
+ const lParts = latest.replace(/^v/, "").split(".").map(Number);
1926
+ for (let i = 0; i < 3; i++) {
1927
+ const c = cParts[i] ?? 0;
1928
+ const l = lParts[i] ?? 0;
1929
+ if (l > c) return true;
1930
+ if (c > l) return false;
1931
+ }
1932
+ return false;
1933
+ }
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
+ });
1954
+ useEffect13(() => {
1955
+ async function checkAndUpgrade() {
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
+ });
1972
+ });
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 = "";
1986
+ }
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)
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
+ }));
2009
+ } else {
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
+ }
2081
+ }
2082
+ }
2083
+ if (tasks.length > 0) {
2084
+ await Promise.all(tasks);
2085
+ }
2086
+ setTimeout(() => exit(), 2500);
2087
+ }
2088
+ checkAndUpgrade();
2089
+ }, []);
2090
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", padding: 1, children: [
2091
+ /* @__PURE__ */ jsx16(MiniHeader, {}),
2092
+ /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", borderStyle: "round", borderColor: "#7B61FF", paddingX: 2, paddingY: 1, marginTop: 1, children: [
2093
+ /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "cyan", bold: true, children: "\u2726 TaskAir Upgrade" }) }),
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",
2105
+ CLI_VERSION,
2106
+ ")"
2107
+ ] }),
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
+ ] })
2115
+ ] }),
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" })
2141
+ ] })
2142
+ ] })
2143
+ ] })
2144
+ ] });
2145
+ }
2146
+ function registerUpgrade(program2) {
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) => {
2148
+ const { render } = await import("ink");
2149
+ render(React15.createElement(UpgradeUI, { target }));
2150
+ });
2151
+ }
2152
+
1778
2153
  // src/index.ts
1779
2154
  program.name("taskair").description(
1780
2155
  "\u2726 Space-themed task management with E2E encryption \xB7 AI-native \xB7 Privacy-first"
1781
- ).version("1.0.4", "-v, --version", "Output the current version").helpOption("-h, --help", "Display help information");
2156
+ ).version(CLI_VERSION, "-v, --version", "Output the current version").helpOption("-h, --help", "Display help information");
1782
2157
  registerConfig(program);
1783
2158
  registerLogin(program);
1784
2159
  registerLogout(program);
1785
2160
  registerWhoami(program);
2161
+ registerUpgrade(program);
1786
2162
  registerAdd(program);
1787
2163
  registerList(program);
1788
2164
  registerDone(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskair-cli",
3
- "version": "1.0.4",
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": {