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.
- package/dist/index.js +462 -86
- 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:
|
|
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:
|
|
397
|
-
|
|
398
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
408
|
-
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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__ */
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
/* @__PURE__ */
|
|
959
|
-
|
|
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(
|
|
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);
|