lytos-cli 0.2.1 → 0.3.1
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/cli.js +309 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command as Command3 } from "commander";
|
|
5
|
+
import { execSync } from "child_process";
|
|
5
6
|
|
|
6
7
|
// src/commands/init.ts
|
|
7
8
|
import { Command } from "commander";
|
|
@@ -611,8 +612,10 @@ function color(code, text) {
|
|
|
611
612
|
}
|
|
612
613
|
var green = (t) => color("32", t);
|
|
613
614
|
var red = (t) => color("31", t);
|
|
615
|
+
var yellow = (t) => color("33", t);
|
|
614
616
|
var blue = (t) => color("34", t);
|
|
615
617
|
var bold = (t) => color("1", t);
|
|
618
|
+
var dim = (t) => color("2", t);
|
|
616
619
|
function info(msg) {
|
|
617
620
|
console.error(`${blue("\u2192")} ${msg}`);
|
|
618
621
|
}
|
|
@@ -648,7 +651,7 @@ async function promptChoice(question, choices) {
|
|
|
648
651
|
}
|
|
649
652
|
console.error("");
|
|
650
653
|
const answer = await prompt("Choice");
|
|
651
|
-
const match = choices.find((
|
|
654
|
+
const match = choices.find((c2) => c2.key === answer);
|
|
652
655
|
return match ? match.key : choices[0].key;
|
|
653
656
|
}
|
|
654
657
|
var initCommand = new Command("init").description("Scaffold Lytos in your project").option("--name <name>", "Project name").option(
|
|
@@ -747,8 +750,8 @@ var initCommand = new Command("init").description("Scaffold Lytos in your projec
|
|
|
747
750
|
|
|
748
751
|
// src/commands/board.ts
|
|
749
752
|
import { Command as Command2 } from "commander";
|
|
750
|
-
import { existsSync as
|
|
751
|
-
import { join as
|
|
753
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
754
|
+
import { join as join5 } from "path";
|
|
752
755
|
|
|
753
756
|
// src/lib/board-generator.ts
|
|
754
757
|
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
|
|
@@ -927,18 +930,199 @@ function boardToJson(data) {
|
|
|
927
930
|
};
|
|
928
931
|
}
|
|
929
932
|
|
|
933
|
+
// src/lib/board-display.ts
|
|
934
|
+
import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
|
|
935
|
+
import { join as join4 } from "path";
|
|
936
|
+
var noColor2 = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
|
|
937
|
+
function c(code, text) {
|
|
938
|
+
if (noColor2) return text;
|
|
939
|
+
return `\x1B[${code}m${text}\x1B[0m`;
|
|
940
|
+
}
|
|
941
|
+
var bold2 = (t) => c("1", t);
|
|
942
|
+
var dim2 = (t) => c("2", t);
|
|
943
|
+
var green2 = (t) => c("32", t);
|
|
944
|
+
var yellow2 = (t) => c("33", t);
|
|
945
|
+
var magenta = (t) => c("35", t);
|
|
946
|
+
var cyan = (t) => c("36", t);
|
|
947
|
+
var white = (t) => c("37", t);
|
|
948
|
+
var bgRed = (t) => c("41", t);
|
|
949
|
+
var boldYellow = (t) => c("1;33", t);
|
|
950
|
+
var boldBlue = (t) => c("1;34", t);
|
|
951
|
+
var boldGreen = (t) => c("1;32", t);
|
|
952
|
+
var boldCyan = (t) => c("1;36", t);
|
|
953
|
+
var boldMagenta = (t) => c("1;35", t);
|
|
954
|
+
function colorPriority(p) {
|
|
955
|
+
if (p.startsWith("P0")) return bgRed(bold2(` ${p} `));
|
|
956
|
+
if (p.startsWith("P1")) return boldYellow(p);
|
|
957
|
+
if (p.startsWith("P2")) return boldBlue(p);
|
|
958
|
+
if (p.startsWith("P3")) return dim2(p);
|
|
959
|
+
return p;
|
|
960
|
+
}
|
|
961
|
+
function colorEffort(e) {
|
|
962
|
+
return dim2(e);
|
|
963
|
+
}
|
|
964
|
+
function colorStatus(status, count) {
|
|
965
|
+
const label = STATUS_DISPLAY[status] || status;
|
|
966
|
+
const countStr = `(${count})`;
|
|
967
|
+
switch (status) {
|
|
968
|
+
case "0-icebox":
|
|
969
|
+
return dim2(`\u25B8 ${label} ${countStr}`);
|
|
970
|
+
case "1-backlog":
|
|
971
|
+
return bold2(white(`\u25B8 ${label} ${countStr}`));
|
|
972
|
+
case "2-sprint":
|
|
973
|
+
return boldCyan(`\u25B8 ${label} ${countStr}`);
|
|
974
|
+
case "3-in-progress":
|
|
975
|
+
return boldYellow(`\u25B8 ${label} ${countStr}`);
|
|
976
|
+
case "4-review":
|
|
977
|
+
return boldMagenta(`\u25B8 ${label} ${countStr}`);
|
|
978
|
+
case "5-done":
|
|
979
|
+
return boldGreen(`\u25B8 ${label} ${countStr}`);
|
|
980
|
+
default:
|
|
981
|
+
return `\u25B8 ${label} ${countStr}`;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
var STATUS_DISPLAY = {
|
|
985
|
+
"0-icebox": "ICEBOX",
|
|
986
|
+
"1-backlog": "BACKLOG",
|
|
987
|
+
"2-sprint": "SPRINT",
|
|
988
|
+
"3-in-progress": "IN PROGRESS",
|
|
989
|
+
"4-review": "REVIEW",
|
|
990
|
+
"5-done": "DONE"
|
|
991
|
+
};
|
|
992
|
+
var ACTIVE_STATUSES = [
|
|
993
|
+
"0-icebox",
|
|
994
|
+
"1-backlog",
|
|
995
|
+
"2-sprint",
|
|
996
|
+
"3-in-progress",
|
|
997
|
+
"4-review"
|
|
998
|
+
];
|
|
999
|
+
function buildTree(issues) {
|
|
1000
|
+
const issueIds = new Set(issues.map((i) => String(i.frontmatter.id)));
|
|
1001
|
+
const result = [];
|
|
1002
|
+
const children = /* @__PURE__ */ new Map();
|
|
1003
|
+
const roots = [];
|
|
1004
|
+
for (const issue of issues) {
|
|
1005
|
+
const deps = issue.frontmatter.depends;
|
|
1006
|
+
const depList = Array.isArray(deps) ? deps : deps ? [deps] : [];
|
|
1007
|
+
const parentInGroup = depList.find((d) => issueIds.has(d));
|
|
1008
|
+
if (parentInGroup) {
|
|
1009
|
+
const existing = children.get(parentInGroup) || [];
|
|
1010
|
+
existing.push(issue);
|
|
1011
|
+
children.set(parentInGroup, existing);
|
|
1012
|
+
} else {
|
|
1013
|
+
roots.push(issue);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
function addIssue(issue, depth, isLast) {
|
|
1017
|
+
result.push({ issue, depth, isLast });
|
|
1018
|
+
const kids = children.get(String(issue.frontmatter.id)) || [];
|
|
1019
|
+
kids.forEach((kid, i) => {
|
|
1020
|
+
addIssue(kid, depth + 1, i === kids.length - 1);
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
roots.forEach((root, i) => {
|
|
1024
|
+
addIssue(root, 0, i === roots.length - 1);
|
|
1025
|
+
});
|
|
1026
|
+
return result;
|
|
1027
|
+
}
|
|
1028
|
+
function formatIssue(di) {
|
|
1029
|
+
const { issue, depth, isLast } = di;
|
|
1030
|
+
const id = String(issue.frontmatter.id || "?");
|
|
1031
|
+
const title = String(issue.frontmatter.title || "?");
|
|
1032
|
+
const priority = String(issue.frontmatter.priority || "?");
|
|
1033
|
+
const effort = String(issue.frontmatter.effort || "?");
|
|
1034
|
+
const maxTitle = 50;
|
|
1035
|
+
const displayTitle = title.length > maxTitle ? title.slice(0, maxTitle - 1) + "\u2026" : title;
|
|
1036
|
+
let prefix = " ";
|
|
1037
|
+
if (depth === 0) {
|
|
1038
|
+
prefix = " ";
|
|
1039
|
+
} else {
|
|
1040
|
+
prefix = " " + " ".repeat(depth - 1) + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ");
|
|
1041
|
+
}
|
|
1042
|
+
return `${prefix}${dim2(id)} ${colorPriority(priority)} ${colorEffort(effort)} ${displayTitle}`;
|
|
1043
|
+
}
|
|
1044
|
+
function detectProjectName(cwd) {
|
|
1045
|
+
try {
|
|
1046
|
+
const manifestPath = join4(cwd, ".lytos", "manifest.md");
|
|
1047
|
+
if (existsSync5(manifestPath)) {
|
|
1048
|
+
const content = readFileSync3(manifestPath, "utf-8");
|
|
1049
|
+
const nameMatch = content.match(/\|\s*Name\s*\|\s*(.+?)\s*\|/);
|
|
1050
|
+
if (nameMatch) return nameMatch[1].trim();
|
|
1051
|
+
}
|
|
1052
|
+
const pkgPath = join4(cwd, "package.json");
|
|
1053
|
+
if (existsSync5(pkgPath)) {
|
|
1054
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
1055
|
+
if (pkg.name) return pkg.name;
|
|
1056
|
+
}
|
|
1057
|
+
} catch {
|
|
1058
|
+
}
|
|
1059
|
+
return "project";
|
|
1060
|
+
}
|
|
1061
|
+
function displayBoard(data) {
|
|
1062
|
+
const projectName = detectProjectName(process.cwd());
|
|
1063
|
+
const line = "\u2500".repeat(52);
|
|
1064
|
+
console.log("");
|
|
1065
|
+
console.log(` ${boldCyan("\u2554")}${boldCyan("\u2550".repeat(52))}${boldCyan("\u2557")}`);
|
|
1066
|
+
console.log(` ${boldCyan("\u2551")} ${bold2("LYTOS BOARD")} \u2014 ${projectName}${" ".repeat(Math.max(0, 38 - projectName.length))}${boldCyan("\u2551")}`);
|
|
1067
|
+
console.log(` ${boldCyan("\u255A")}${boldCyan("\u2550".repeat(52))}${boldCyan("\u255D")}`);
|
|
1068
|
+
console.log("");
|
|
1069
|
+
const counts = {};
|
|
1070
|
+
for (const status of [...ACTIVE_STATUSES, "5-done"]) {
|
|
1071
|
+
counts[status] = data.issues.filter((i) => i.status === status).length;
|
|
1072
|
+
}
|
|
1073
|
+
const shownStatuses = ["1-backlog", "2-sprint", "3-in-progress", "4-review"];
|
|
1074
|
+
for (const status of shownStatuses) {
|
|
1075
|
+
const issues = data.issues.filter((i) => i.status === status);
|
|
1076
|
+
console.log(` ${colorStatus(status, issues.length)}`);
|
|
1077
|
+
if (issues.length === 0) {
|
|
1078
|
+
console.log("");
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
console.log(` ${dim2("\u2502")}`);
|
|
1082
|
+
const tree = buildTree(issues);
|
|
1083
|
+
for (const di of tree) {
|
|
1084
|
+
console.log(` ${dim2("\u2502")} ${formatIssue(di)}`);
|
|
1085
|
+
}
|
|
1086
|
+
console.log("");
|
|
1087
|
+
}
|
|
1088
|
+
const iceboxCount = counts["0-icebox"];
|
|
1089
|
+
if (iceboxCount > 0) {
|
|
1090
|
+
console.log(` ${colorStatus("0-icebox", iceboxCount)}`);
|
|
1091
|
+
const iceboxIssues = data.issues.filter((i) => i.status === "0-icebox");
|
|
1092
|
+
console.log(` ${dim2("\u2502")}`);
|
|
1093
|
+
const tree = buildTree(iceboxIssues);
|
|
1094
|
+
for (const di of tree) {
|
|
1095
|
+
console.log(` ${dim2("\u2502")} ${formatIssue(di)}`);
|
|
1096
|
+
}
|
|
1097
|
+
console.log("");
|
|
1098
|
+
}
|
|
1099
|
+
console.log(` ${colorStatus("5-done", counts["5-done"])}`);
|
|
1100
|
+
console.log("");
|
|
1101
|
+
console.log(` ${dim2(line)}`);
|
|
1102
|
+
const parts = [
|
|
1103
|
+
`${bold2(String(data.issues.length))} issues`,
|
|
1104
|
+
counts["1-backlog"] ? `${counts["1-backlog"]} backlog` : null,
|
|
1105
|
+
counts["2-sprint"] ? `${cyan(String(counts["2-sprint"]))} sprint` : null,
|
|
1106
|
+
counts["3-in-progress"] ? `${yellow2(String(counts["3-in-progress"]))} wip` : null,
|
|
1107
|
+
counts["4-review"] ? `${magenta(String(counts["4-review"]))} review` : null,
|
|
1108
|
+
counts["5-done"] ? `${green2(String(counts["5-done"]))} done ${green2("\u2713")}` : null
|
|
1109
|
+
].filter(Boolean);
|
|
1110
|
+
console.log(` ${parts.join(dim2(" \xB7 "))}`);
|
|
1111
|
+
console.log("");
|
|
1112
|
+
}
|
|
1113
|
+
|
|
930
1114
|
// src/commands/board.ts
|
|
931
1115
|
function findBoardDir(cwd) {
|
|
932
1116
|
const candidates = [
|
|
933
|
-
|
|
934
|
-
|
|
1117
|
+
join5(cwd, ".lytos", "issue-board"),
|
|
1118
|
+
join5(cwd, "issue-board")
|
|
935
1119
|
];
|
|
936
1120
|
for (const candidate of candidates) {
|
|
937
|
-
if (
|
|
1121
|
+
if (existsSync6(candidate)) return candidate;
|
|
938
1122
|
}
|
|
939
1123
|
return null;
|
|
940
1124
|
}
|
|
941
|
-
var boardCommand = new Command2("board").description("
|
|
1125
|
+
var boardCommand = new Command2("board").description("Display board overview and regenerate BOARD.md").option(
|
|
942
1126
|
"--check",
|
|
943
1127
|
"Check if BOARD.md is up to date (exit 1 if not)",
|
|
944
1128
|
false
|
|
@@ -961,12 +1145,12 @@ var boardCommand = new Command2("board").description("Regenerate BOARD.md from i
|
|
|
961
1145
|
}
|
|
962
1146
|
const newContent = generateBoardMarkdown(data);
|
|
963
1147
|
if (opts.check) {
|
|
964
|
-
const boardPath2 =
|
|
965
|
-
if (!
|
|
1148
|
+
const boardPath2 = join5(boardDir, "BOARD.md");
|
|
1149
|
+
if (!existsSync6(boardPath2)) {
|
|
966
1150
|
error("BOARD.md does not exist.");
|
|
967
1151
|
process.exit(1);
|
|
968
1152
|
}
|
|
969
|
-
const existing =
|
|
1153
|
+
const existing = readFileSync4(boardPath2, "utf-8");
|
|
970
1154
|
const normalize = (s) => s.replace(/\*\*Last generated\*\*:.*/, "").trim();
|
|
971
1155
|
if (normalize(existing) === normalize(newContent)) {
|
|
972
1156
|
ok("BOARD.md is up to date.");
|
|
@@ -978,18 +1162,129 @@ var boardCommand = new Command2("board").description("Regenerate BOARD.md from i
|
|
|
978
1162
|
process.exit(1);
|
|
979
1163
|
}
|
|
980
1164
|
}
|
|
981
|
-
|
|
1165
|
+
displayBoard(data);
|
|
1166
|
+
const boardPath = join5(boardDir, "BOARD.md");
|
|
982
1167
|
writeFileSync2(boardPath, newContent, "utf-8");
|
|
983
1168
|
ok(
|
|
984
|
-
`BOARD.md
|
|
1169
|
+
`BOARD.md regenerated`
|
|
985
1170
|
);
|
|
986
1171
|
});
|
|
987
1172
|
|
|
1173
|
+
// src/lib/update-check.ts
|
|
1174
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
1175
|
+
import { join as join6 } from "path";
|
|
1176
|
+
import { homedir } from "os";
|
|
1177
|
+
import { get } from "https";
|
|
1178
|
+
var PACKAGE_NAME = "lytos-cli";
|
|
1179
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1180
|
+
var CACHE_DIR = join6(homedir(), ".lytos");
|
|
1181
|
+
var CACHE_FILE = join6(CACHE_DIR, "last-update-check");
|
|
1182
|
+
function isNewer(remote, local) {
|
|
1183
|
+
const r = remote.split(".").map(Number);
|
|
1184
|
+
const l = local.split(".").map(Number);
|
|
1185
|
+
for (let i = 0; i < 3; i++) {
|
|
1186
|
+
if ((r[i] || 0) > (l[i] || 0)) return true;
|
|
1187
|
+
if ((r[i] || 0) < (l[i] || 0)) return false;
|
|
1188
|
+
}
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
function shouldCheck() {
|
|
1192
|
+
try {
|
|
1193
|
+
if (!existsSync7(CACHE_FILE)) return true;
|
|
1194
|
+
const lastCheck = parseInt(readFileSync5(CACHE_FILE, "utf-8").trim(), 10);
|
|
1195
|
+
return Date.now() - lastCheck > CHECK_INTERVAL_MS;
|
|
1196
|
+
} catch {
|
|
1197
|
+
return true;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function saveCheckTimestamp() {
|
|
1201
|
+
try {
|
|
1202
|
+
if (!existsSync7(CACHE_DIR)) {
|
|
1203
|
+
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
1204
|
+
}
|
|
1205
|
+
writeFileSync3(CACHE_FILE, String(Date.now()), "utf-8");
|
|
1206
|
+
} catch {
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function fetchLatestVersion() {
|
|
1210
|
+
return new Promise((resolve2) => {
|
|
1211
|
+
const timeout = setTimeout(() => resolve2(null), 3e3);
|
|
1212
|
+
get(
|
|
1213
|
+
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
1214
|
+
{ headers: { Accept: "application/json" } },
|
|
1215
|
+
(res) => {
|
|
1216
|
+
let data = "";
|
|
1217
|
+
res.on("data", (chunk) => data += chunk);
|
|
1218
|
+
res.on("end", () => {
|
|
1219
|
+
clearTimeout(timeout);
|
|
1220
|
+
try {
|
|
1221
|
+
const json = JSON.parse(data);
|
|
1222
|
+
resolve2(json.version || null);
|
|
1223
|
+
} catch {
|
|
1224
|
+
resolve2(null);
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
res.on("error", () => {
|
|
1228
|
+
clearTimeout(timeout);
|
|
1229
|
+
resolve2(null);
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
).on("error", () => {
|
|
1233
|
+
clearTimeout(timeout);
|
|
1234
|
+
resolve2(null);
|
|
1235
|
+
});
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
async function checkForUpdates(currentVersion) {
|
|
1239
|
+
if (!shouldCheck()) return;
|
|
1240
|
+
saveCheckTimestamp();
|
|
1241
|
+
const latest = await fetchLatestVersion();
|
|
1242
|
+
if (!latest) return;
|
|
1243
|
+
if (isNewer(latest, currentVersion)) {
|
|
1244
|
+
console.error("");
|
|
1245
|
+
console.error(
|
|
1246
|
+
` ${yellow("\u26A0")} Update available: ${dim(currentVersion)} \u2192 ${bold(latest)}`
|
|
1247
|
+
);
|
|
1248
|
+
console.error(
|
|
1249
|
+
` Run ${bold("lyt update")} to update`
|
|
1250
|
+
);
|
|
1251
|
+
console.error("");
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
988
1255
|
// src/cli.ts
|
|
989
1256
|
var program = new Command3();
|
|
990
|
-
program.name("
|
|
1257
|
+
program.name("lyt").description(
|
|
991
1258
|
"CLI tool for Lytos \u2014 a human-first method for working with AI agents"
|
|
992
|
-
).version("0.
|
|
1259
|
+
).version("0.2.1");
|
|
993
1260
|
program.addCommand(initCommand);
|
|
994
1261
|
program.addCommand(boardCommand);
|
|
1262
|
+
program.command("lint").description("Validate .lytos/ structure and content (coming soon)").action(() => {
|
|
1263
|
+
console.error("Coming soon. Follow https://github.com/getlytos/lytos-cli for updates.");
|
|
1264
|
+
process.exit(0);
|
|
1265
|
+
});
|
|
1266
|
+
program.command("doctor").description("Full diagnostic \u2014 missing files, broken links, stale memory (coming soon)").action(() => {
|
|
1267
|
+
console.error("Coming soon. Follow https://github.com/getlytos/lytos-cli for updates.");
|
|
1268
|
+
process.exit(0);
|
|
1269
|
+
});
|
|
1270
|
+
program.command("status").description("Display sprint DAG in terminal (coming soon)").action(() => {
|
|
1271
|
+
console.error("Coming soon. Follow https://github.com/getlytos/lytos-cli for updates.");
|
|
1272
|
+
process.exit(0);
|
|
1273
|
+
});
|
|
1274
|
+
program.command("update").description("Update lytos-cli to the latest version").action(() => {
|
|
1275
|
+
console.error(`
|
|
1276
|
+
${bold("Updating lytos-cli...")}
|
|
1277
|
+
`);
|
|
1278
|
+
try {
|
|
1279
|
+
execSync("npm install -g lytos-cli@latest", { stdio: "inherit" });
|
|
1280
|
+
const newVersion = execSync("lyt --version", { encoding: "utf-8" }).trim();
|
|
1281
|
+
console.error("");
|
|
1282
|
+
ok(`Updated to ${green(newVersion)}`);
|
|
1283
|
+
} catch {
|
|
1284
|
+
error("Update failed. Try manually: npm install -g lytos-cli@latest");
|
|
1285
|
+
process.exit(1);
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
var VERSION = "0.2.1";
|
|
995
1289
|
program.parse();
|
|
1290
|
+
checkForUpdates(VERSION);
|