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.
- package/dist/index.js +756 -318
- 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
|
|
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
|
|
297
|
-
|
|
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) =>
|
|
300
|
-
|
|
301
|
-
|
|
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__ */
|
|
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] =
|
|
325
|
-
const [apiUrl, setApiUrl] =
|
|
326
|
-
const [currentInput, setCurrentInput] =
|
|
327
|
-
const [port, setPort] =
|
|
328
|
-
const [errorMsg, setErrorMsg] =
|
|
329
|
-
const [authDetails, setAuthDetails] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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] =
|
|
652
|
-
const [password, setPassword] =
|
|
653
|
-
const [stage, setStage] =
|
|
654
|
-
const [currentInput, setCurrentInput] =
|
|
655
|
-
const [errorMsg, setErrorMsg] =
|
|
656
|
-
|
|
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(
|
|
817
|
+
render(React5.createElement(LoginUI));
|
|
754
818
|
});
|
|
755
819
|
}
|
|
756
820
|
|
|
757
821
|
// src/commands/logout.tsx
|
|
758
|
-
import
|
|
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] =
|
|
764
|
-
const [message, setMessage] =
|
|
765
|
-
|
|
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(
|
|
856
|
+
render(React6.createElement(LogoutUI));
|
|
793
857
|
});
|
|
794
858
|
}
|
|
795
859
|
|
|
796
860
|
// src/commands/whoami.tsx
|
|
797
|
-
import
|
|
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(
|
|
921
|
+
render(React7.createElement(WhoamiUI));
|
|
858
922
|
});
|
|
859
923
|
}
|
|
860
924
|
|
|
861
925
|
// src/commands/add.tsx
|
|
862
|
-
import
|
|
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] =
|
|
994
|
-
const [
|
|
995
|
-
const [
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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__ */
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
/* @__PURE__ */
|
|
1056
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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] =
|
|
1251
|
-
|
|
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(
|
|
1461
|
+
render(React10.createElement(ListUI, { filter, format }));
|
|
1315
1462
|
});
|
|
1316
1463
|
}
|
|
1317
1464
|
|
|
1318
1465
|
// src/commands/done.tsx
|
|
1319
|
-
import
|
|
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 {
|
|
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] =
|
|
1325
|
-
const [
|
|
1326
|
-
const [
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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(
|
|
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
|
-
|
|
1358
|
-
/* @__PURE__ */
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
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
|
-
] })
|
|
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(
|
|
1579
|
+
render(React11.createElement(DoneUI, { id, note: options.note }));
|
|
1373
1580
|
});
|
|
1374
1581
|
}
|
|
1375
1582
|
|
|
1376
1583
|
// src/commands/remove.tsx
|
|
1377
|
-
import
|
|
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] =
|
|
1383
|
-
const [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 === "
|
|
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(
|
|
1729
|
+
render(React12.createElement(RemoveUI, { id, force: options.force }));
|
|
1454
1730
|
});
|
|
1455
1731
|
}
|
|
1456
1732
|
|
|
1457
1733
|
// src/commands/edit.tsx
|
|
1458
|
-
import
|
|
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 {
|
|
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] =
|
|
1464
|
-
const [
|
|
1465
|
-
const [
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
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(
|
|
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
|
-
|
|
1522
|
-
/* @__PURE__ */
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
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(
|
|
1868
|
+
render(React13.createElement(EditUI, { id, updates: options }));
|
|
1532
1869
|
});
|
|
1533
1870
|
}
|
|
1534
1871
|
|
|
1535
1872
|
// src/commands/stats.tsx
|
|
1536
|
-
import
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
1985
|
+
render(React14.createElement(StatsUI));
|
|
1649
1986
|
});
|
|
1650
1987
|
}
|
|
1651
1988
|
|
|
1652
1989
|
// src/commands/sync.tsx
|
|
1653
|
-
import
|
|
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] =
|
|
1705
|
-
const [message, setMessage] =
|
|
1706
|
-
const [details, setDetails] =
|
|
1707
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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.
|
|
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
|
-
|
|
1887
|
-
const
|
|
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
|
|
1898
|
-
const
|
|
1899
|
-
const
|
|
1900
|
-
|
|
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
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
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
|
-
|
|
1908
|
-
|
|
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
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1938
|
-
|
|
1939
|
-
/* @__PURE__ */
|
|
1940
|
-
|
|
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
|
-
"
|
|
1943
|
-
latestVersion
|
|
2360
|
+
")"
|
|
1944
2361
|
] }),
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
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
|
-
|
|
1956
|
-
/* @__PURE__ */ jsx16(
|
|
1957
|
-
/* @__PURE__ */
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
\
|
|
1994
|
-
\u2551
|
|
1995
|
-
\u2551
|
|
1996
|
-
\
|
|
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];
|