lytos-cli 0.2.0 → 0.3.0
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/README.md +84 -19
- package/dist/cli.js +294 -14
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,36 +1,53 @@
|
|
|
1
1
|
# Lytos — CLI
|
|
2
2
|
|
|
3
3
|
[](https://github.com/getlytos/lytos-cli/actions/workflows/ci.yml)
|
|
4
|
-
[](https://www.npmjs.com/package/lytos-cli)
|
|
4
|
+
[](https://www.npmjs.com/package/lytos-cli)
|
|
5
5
|
|
|
6
6
|
> The command-line tool for [Lytos](https://github.com/getlytos/lytos-method) — a human-first method for working with AI agents.
|
|
7
7
|
|
|
8
|
+
**Lytos** gives your AI agents a structured context: what the project is, how to work, what "done" means, what's in progress, and what's been learned. Everything in markdown. No vendor lock-in, no API, no account.
|
|
9
|
+
|
|
8
10
|
---
|
|
9
11
|
|
|
10
|
-
##
|
|
12
|
+
## The problem
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
AI agents (Claude, Cursor, GPT...) are powerful but stateless. Every session starts from zero. They don't know your project's conventions, your sprint priorities, or what was tried last week.
|
|
15
|
+
|
|
16
|
+
**Lytos** solves this by creating a `.lytos/` directory in your project — a structured context that any AI can read, and that you own.
|
|
17
|
+
|
|
18
|
+
---
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
## The 5 pillars
|
|
21
|
+
|
|
22
|
+
`lytos init` scaffolds a `.lytos/` directory built on 5 pillars:
|
|
23
|
+
|
|
24
|
+
| Pillar | Purpose | File / Directory |
|
|
25
|
+
|--------|---------|-----------------|
|
|
26
|
+
| **Intent** | Why the project exists — its constitution | `manifest.md` |
|
|
27
|
+
| **Design** | Procedures for recurring tasks (code review, testing, deployment...) | `skills/` |
|
|
28
|
+
| **Standards** | Non-negotiable quality criteria | `rules/` |
|
|
29
|
+
| **Progress** | Issues, sprint, what's moving and what's blocked | `issue-board/` |
|
|
30
|
+
| **Memory** | What's been learned — sovereign, portable, model-independent | `memory/` |
|
|
31
|
+
|
|
32
|
+
These 5 pillars are the method. The AI agent reads them at the start of each session and follows them.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Install
|
|
17
37
|
|
|
18
38
|
```bash
|
|
19
|
-
|
|
20
|
-
lytos board # Regenerate BOARD.md from issue frontmatter
|
|
39
|
+
npm install -g lytos-cli
|
|
21
40
|
```
|
|
22
41
|
|
|
23
42
|
Or use without installing:
|
|
24
43
|
|
|
25
44
|
```bash
|
|
26
|
-
npx lytos init
|
|
45
|
+
npx lytos-cli init
|
|
27
46
|
```
|
|
28
47
|
|
|
29
48
|
---
|
|
30
49
|
|
|
31
|
-
##
|
|
32
|
-
|
|
33
|
-
One command to install the method, one command to validate your setup, one command to see your sprint.
|
|
50
|
+
## Commands
|
|
34
51
|
|
|
35
52
|
| Command | What it does |
|
|
36
53
|
|---------|-------------|
|
|
@@ -42,22 +59,70 @@ One command to install the method, one command to validate your setup, one comma
|
|
|
42
59
|
|
|
43
60
|
---
|
|
44
61
|
|
|
62
|
+
## What `lytos init` generates
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
your-project/
|
|
66
|
+
└── .lytos/
|
|
67
|
+
├── manifest.md # Intent — project identity and constraints
|
|
68
|
+
├── LYTOS.md # Method reference
|
|
69
|
+
├── sprint.md # Current sprint
|
|
70
|
+
├── skills/ # Design — 9 reusable procedures
|
|
71
|
+
│ ├── session-start.md
|
|
72
|
+
│ ├── code-structure.md
|
|
73
|
+
│ ├── code-review.md
|
|
74
|
+
│ ├── testing.md
|
|
75
|
+
│ ├── documentation.md
|
|
76
|
+
│ ├── git-workflow.md
|
|
77
|
+
│ ├── deployment.md
|
|
78
|
+
│ ├── security.md
|
|
79
|
+
│ └── api-design.md
|
|
80
|
+
├── rules/ # Standards — quality criteria
|
|
81
|
+
│ └── default-rules.md
|
|
82
|
+
├── issue-board/ # Progress — kanban board
|
|
83
|
+
│ ├── BOARD.md
|
|
84
|
+
│ ├── 0-icebox/
|
|
85
|
+
│ ├── 1-backlog/
|
|
86
|
+
│ ├── 2-sprint/
|
|
87
|
+
│ ├── 3-in-progress/
|
|
88
|
+
│ ├── 4-review/
|
|
89
|
+
│ └── 5-done/
|
|
90
|
+
└── memory/ # Memory — accumulated knowledge
|
|
91
|
+
├── MEMORY.md
|
|
92
|
+
└── cortex/
|
|
93
|
+
├── architecture.md
|
|
94
|
+
├── patterns.md
|
|
95
|
+
├── bugs.md
|
|
96
|
+
└── ...
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Design principles
|
|
102
|
+
|
|
103
|
+
- **Offline-first** — no network needed (except `lytos init` to download templates)
|
|
104
|
+
- **Zero lock-in** — plain markdown files, works with any AI tool
|
|
105
|
+
- **No telemetry** — no tracking, no analytics, ever
|
|
106
|
+
- **Human-first** — the human defines the method, the AI follows it
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
45
110
|
## Built with Lytos
|
|
46
111
|
|
|
47
112
|
This project uses Lytos to develop itself. The `.lytos/` directory contains the real manifest, sprint, issues, and memory for this project — not templates.
|
|
48
113
|
|
|
49
|
-
If you want to contribute, open this repo in Claude Code and say: **"Help me understand this project."**
|
|
50
|
-
|
|
51
114
|
---
|
|
52
115
|
|
|
53
|
-
##
|
|
116
|
+
## Links
|
|
117
|
+
|
|
118
|
+
- [Lytos Method](https://github.com/getlytos/lytos-method) — the method itself
|
|
119
|
+
- [Documentation](https://github.com/getlytos/lytos-website) — full docs (EN/FR)
|
|
54
120
|
|
|
55
|
-
|
|
121
|
+
---
|
|
56
122
|
|
|
57
|
-
|
|
58
|
-
- X: [@fred](https://x.com/fred)
|
|
123
|
+
## Author
|
|
59
124
|
|
|
60
|
-
|
|
125
|
+
Created by **Frederic Galline** — [ubeez.com](https://ubeez.com)
|
|
61
126
|
|
|
62
127
|
---
|
|
63
128
|
|
package/dist/cli.js
CHANGED
|
@@ -611,8 +611,10 @@ function color(code, text) {
|
|
|
611
611
|
}
|
|
612
612
|
var green = (t) => color("32", t);
|
|
613
613
|
var red = (t) => color("31", t);
|
|
614
|
+
var yellow = (t) => color("33", t);
|
|
614
615
|
var blue = (t) => color("34", t);
|
|
615
616
|
var bold = (t) => color("1", t);
|
|
617
|
+
var dim = (t) => color("2", t);
|
|
616
618
|
function info(msg) {
|
|
617
619
|
console.error(`${blue("\u2192")} ${msg}`);
|
|
618
620
|
}
|
|
@@ -648,7 +650,7 @@ async function promptChoice(question, choices) {
|
|
|
648
650
|
}
|
|
649
651
|
console.error("");
|
|
650
652
|
const answer = await prompt("Choice");
|
|
651
|
-
const match = choices.find((
|
|
653
|
+
const match = choices.find((c2) => c2.key === answer);
|
|
652
654
|
return match ? match.key : choices[0].key;
|
|
653
655
|
}
|
|
654
656
|
var initCommand = new Command("init").description("Scaffold Lytos in your project").option("--name <name>", "Project name").option(
|
|
@@ -747,8 +749,8 @@ var initCommand = new Command("init").description("Scaffold Lytos in your projec
|
|
|
747
749
|
|
|
748
750
|
// src/commands/board.ts
|
|
749
751
|
import { Command as Command2 } from "commander";
|
|
750
|
-
import { existsSync as
|
|
751
|
-
import { join as
|
|
752
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
753
|
+
import { join as join5 } from "path";
|
|
752
754
|
|
|
753
755
|
// src/lib/board-generator.ts
|
|
754
756
|
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
|
|
@@ -927,18 +929,199 @@ function boardToJson(data) {
|
|
|
927
929
|
};
|
|
928
930
|
}
|
|
929
931
|
|
|
932
|
+
// src/lib/board-display.ts
|
|
933
|
+
import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
|
|
934
|
+
import { join as join4 } from "path";
|
|
935
|
+
var noColor2 = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
|
|
936
|
+
function c(code, text) {
|
|
937
|
+
if (noColor2) return text;
|
|
938
|
+
return `\x1B[${code}m${text}\x1B[0m`;
|
|
939
|
+
}
|
|
940
|
+
var bold2 = (t) => c("1", t);
|
|
941
|
+
var dim2 = (t) => c("2", t);
|
|
942
|
+
var green2 = (t) => c("32", t);
|
|
943
|
+
var yellow2 = (t) => c("33", t);
|
|
944
|
+
var magenta = (t) => c("35", t);
|
|
945
|
+
var cyan = (t) => c("36", t);
|
|
946
|
+
var white = (t) => c("37", t);
|
|
947
|
+
var bgRed = (t) => c("41", t);
|
|
948
|
+
var boldYellow = (t) => c("1;33", t);
|
|
949
|
+
var boldBlue = (t) => c("1;34", t);
|
|
950
|
+
var boldGreen = (t) => c("1;32", t);
|
|
951
|
+
var boldCyan = (t) => c("1;36", t);
|
|
952
|
+
var boldMagenta = (t) => c("1;35", t);
|
|
953
|
+
function colorPriority(p) {
|
|
954
|
+
if (p.startsWith("P0")) return bgRed(bold2(` ${p} `));
|
|
955
|
+
if (p.startsWith("P1")) return boldYellow(p);
|
|
956
|
+
if (p.startsWith("P2")) return boldBlue(p);
|
|
957
|
+
if (p.startsWith("P3")) return dim2(p);
|
|
958
|
+
return p;
|
|
959
|
+
}
|
|
960
|
+
function colorEffort(e) {
|
|
961
|
+
return dim2(e);
|
|
962
|
+
}
|
|
963
|
+
function colorStatus(status, count) {
|
|
964
|
+
const label = STATUS_DISPLAY[status] || status;
|
|
965
|
+
const countStr = `(${count})`;
|
|
966
|
+
switch (status) {
|
|
967
|
+
case "0-icebox":
|
|
968
|
+
return dim2(`\u25B8 ${label} ${countStr}`);
|
|
969
|
+
case "1-backlog":
|
|
970
|
+
return bold2(white(`\u25B8 ${label} ${countStr}`));
|
|
971
|
+
case "2-sprint":
|
|
972
|
+
return boldCyan(`\u25B8 ${label} ${countStr}`);
|
|
973
|
+
case "3-in-progress":
|
|
974
|
+
return boldYellow(`\u25B8 ${label} ${countStr}`);
|
|
975
|
+
case "4-review":
|
|
976
|
+
return boldMagenta(`\u25B8 ${label} ${countStr}`);
|
|
977
|
+
case "5-done":
|
|
978
|
+
return boldGreen(`\u25B8 ${label} ${countStr}`);
|
|
979
|
+
default:
|
|
980
|
+
return `\u25B8 ${label} ${countStr}`;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
var STATUS_DISPLAY = {
|
|
984
|
+
"0-icebox": "ICEBOX",
|
|
985
|
+
"1-backlog": "BACKLOG",
|
|
986
|
+
"2-sprint": "SPRINT",
|
|
987
|
+
"3-in-progress": "IN PROGRESS",
|
|
988
|
+
"4-review": "REVIEW",
|
|
989
|
+
"5-done": "DONE"
|
|
990
|
+
};
|
|
991
|
+
var ACTIVE_STATUSES = [
|
|
992
|
+
"0-icebox",
|
|
993
|
+
"1-backlog",
|
|
994
|
+
"2-sprint",
|
|
995
|
+
"3-in-progress",
|
|
996
|
+
"4-review"
|
|
997
|
+
];
|
|
998
|
+
function buildTree(issues) {
|
|
999
|
+
const issueIds = new Set(issues.map((i) => String(i.frontmatter.id)));
|
|
1000
|
+
const result = [];
|
|
1001
|
+
const children = /* @__PURE__ */ new Map();
|
|
1002
|
+
const roots = [];
|
|
1003
|
+
for (const issue of issues) {
|
|
1004
|
+
const deps = issue.frontmatter.depends;
|
|
1005
|
+
const depList = Array.isArray(deps) ? deps : deps ? [deps] : [];
|
|
1006
|
+
const parentInGroup = depList.find((d) => issueIds.has(d));
|
|
1007
|
+
if (parentInGroup) {
|
|
1008
|
+
const existing = children.get(parentInGroup) || [];
|
|
1009
|
+
existing.push(issue);
|
|
1010
|
+
children.set(parentInGroup, existing);
|
|
1011
|
+
} else {
|
|
1012
|
+
roots.push(issue);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function addIssue(issue, depth, isLast) {
|
|
1016
|
+
result.push({ issue, depth, isLast });
|
|
1017
|
+
const kids = children.get(String(issue.frontmatter.id)) || [];
|
|
1018
|
+
kids.forEach((kid, i) => {
|
|
1019
|
+
addIssue(kid, depth + 1, i === kids.length - 1);
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
roots.forEach((root, i) => {
|
|
1023
|
+
addIssue(root, 0, i === roots.length - 1);
|
|
1024
|
+
});
|
|
1025
|
+
return result;
|
|
1026
|
+
}
|
|
1027
|
+
function formatIssue(di) {
|
|
1028
|
+
const { issue, depth, isLast } = di;
|
|
1029
|
+
const id = String(issue.frontmatter.id || "?");
|
|
1030
|
+
const title = String(issue.frontmatter.title || "?");
|
|
1031
|
+
const priority = String(issue.frontmatter.priority || "?");
|
|
1032
|
+
const effort = String(issue.frontmatter.effort || "?");
|
|
1033
|
+
const maxTitle = 50;
|
|
1034
|
+
const displayTitle = title.length > maxTitle ? title.slice(0, maxTitle - 1) + "\u2026" : title;
|
|
1035
|
+
let prefix = " ";
|
|
1036
|
+
if (depth === 0) {
|
|
1037
|
+
prefix = " ";
|
|
1038
|
+
} else {
|
|
1039
|
+
prefix = " " + " ".repeat(depth - 1) + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ");
|
|
1040
|
+
}
|
|
1041
|
+
return `${prefix}${dim2(id)} ${colorPriority(priority)} ${colorEffort(effort)} ${displayTitle}`;
|
|
1042
|
+
}
|
|
1043
|
+
function detectProjectName(cwd) {
|
|
1044
|
+
try {
|
|
1045
|
+
const manifestPath = join4(cwd, ".lytos", "manifest.md");
|
|
1046
|
+
if (existsSync5(manifestPath)) {
|
|
1047
|
+
const content = readFileSync3(manifestPath, "utf-8");
|
|
1048
|
+
const nameMatch = content.match(/\|\s*Name\s*\|\s*(.+?)\s*\|/);
|
|
1049
|
+
if (nameMatch) return nameMatch[1].trim();
|
|
1050
|
+
}
|
|
1051
|
+
const pkgPath = join4(cwd, "package.json");
|
|
1052
|
+
if (existsSync5(pkgPath)) {
|
|
1053
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
1054
|
+
if (pkg.name) return pkg.name;
|
|
1055
|
+
}
|
|
1056
|
+
} catch {
|
|
1057
|
+
}
|
|
1058
|
+
return "project";
|
|
1059
|
+
}
|
|
1060
|
+
function displayBoard(data) {
|
|
1061
|
+
const projectName = detectProjectName(process.cwd());
|
|
1062
|
+
const line = "\u2500".repeat(52);
|
|
1063
|
+
console.log("");
|
|
1064
|
+
console.log(` ${boldCyan("\u2554")}${boldCyan("\u2550".repeat(52))}${boldCyan("\u2557")}`);
|
|
1065
|
+
console.log(` ${boldCyan("\u2551")} ${bold2("LYTOS BOARD")} \u2014 ${projectName}${" ".repeat(Math.max(0, 38 - projectName.length))}${boldCyan("\u2551")}`);
|
|
1066
|
+
console.log(` ${boldCyan("\u255A")}${boldCyan("\u2550".repeat(52))}${boldCyan("\u255D")}`);
|
|
1067
|
+
console.log("");
|
|
1068
|
+
const counts = {};
|
|
1069
|
+
for (const status of [...ACTIVE_STATUSES, "5-done"]) {
|
|
1070
|
+
counts[status] = data.issues.filter((i) => i.status === status).length;
|
|
1071
|
+
}
|
|
1072
|
+
const shownStatuses = ["1-backlog", "2-sprint", "3-in-progress", "4-review"];
|
|
1073
|
+
for (const status of shownStatuses) {
|
|
1074
|
+
const issues = data.issues.filter((i) => i.status === status);
|
|
1075
|
+
console.log(` ${colorStatus(status, issues.length)}`);
|
|
1076
|
+
if (issues.length === 0) {
|
|
1077
|
+
console.log("");
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
console.log(` ${dim2("\u2502")}`);
|
|
1081
|
+
const tree = buildTree(issues);
|
|
1082
|
+
for (const di of tree) {
|
|
1083
|
+
console.log(` ${dim2("\u2502")} ${formatIssue(di)}`);
|
|
1084
|
+
}
|
|
1085
|
+
console.log("");
|
|
1086
|
+
}
|
|
1087
|
+
const iceboxCount = counts["0-icebox"];
|
|
1088
|
+
if (iceboxCount > 0) {
|
|
1089
|
+
console.log(` ${colorStatus("0-icebox", iceboxCount)}`);
|
|
1090
|
+
const iceboxIssues = data.issues.filter((i) => i.status === "0-icebox");
|
|
1091
|
+
console.log(` ${dim2("\u2502")}`);
|
|
1092
|
+
const tree = buildTree(iceboxIssues);
|
|
1093
|
+
for (const di of tree) {
|
|
1094
|
+
console.log(` ${dim2("\u2502")} ${formatIssue(di)}`);
|
|
1095
|
+
}
|
|
1096
|
+
console.log("");
|
|
1097
|
+
}
|
|
1098
|
+
console.log(` ${colorStatus("5-done", counts["5-done"])}`);
|
|
1099
|
+
console.log("");
|
|
1100
|
+
console.log(` ${dim2(line)}`);
|
|
1101
|
+
const parts = [
|
|
1102
|
+
`${bold2(String(data.issues.length))} issues`,
|
|
1103
|
+
counts["1-backlog"] ? `${counts["1-backlog"]} backlog` : null,
|
|
1104
|
+
counts["2-sprint"] ? `${cyan(String(counts["2-sprint"]))} sprint` : null,
|
|
1105
|
+
counts["3-in-progress"] ? `${yellow2(String(counts["3-in-progress"]))} wip` : null,
|
|
1106
|
+
counts["4-review"] ? `${magenta(String(counts["4-review"]))} review` : null,
|
|
1107
|
+
counts["5-done"] ? `${green2(String(counts["5-done"]))} done ${green2("\u2713")}` : null
|
|
1108
|
+
].filter(Boolean);
|
|
1109
|
+
console.log(` ${parts.join(dim2(" \xB7 "))}`);
|
|
1110
|
+
console.log("");
|
|
1111
|
+
}
|
|
1112
|
+
|
|
930
1113
|
// src/commands/board.ts
|
|
931
1114
|
function findBoardDir(cwd) {
|
|
932
1115
|
const candidates = [
|
|
933
|
-
|
|
934
|
-
|
|
1116
|
+
join5(cwd, ".lytos", "issue-board"),
|
|
1117
|
+
join5(cwd, "issue-board")
|
|
935
1118
|
];
|
|
936
1119
|
for (const candidate of candidates) {
|
|
937
|
-
if (
|
|
1120
|
+
if (existsSync6(candidate)) return candidate;
|
|
938
1121
|
}
|
|
939
1122
|
return null;
|
|
940
1123
|
}
|
|
941
|
-
var boardCommand = new Command2("board").description("
|
|
1124
|
+
var boardCommand = new Command2("board").description("Display board overview and regenerate BOARD.md").option(
|
|
942
1125
|
"--check",
|
|
943
1126
|
"Check if BOARD.md is up to date (exit 1 if not)",
|
|
944
1127
|
false
|
|
@@ -961,12 +1144,12 @@ var boardCommand = new Command2("board").description("Regenerate BOARD.md from i
|
|
|
961
1144
|
}
|
|
962
1145
|
const newContent = generateBoardMarkdown(data);
|
|
963
1146
|
if (opts.check) {
|
|
964
|
-
const boardPath2 =
|
|
965
|
-
if (!
|
|
1147
|
+
const boardPath2 = join5(boardDir, "BOARD.md");
|
|
1148
|
+
if (!existsSync6(boardPath2)) {
|
|
966
1149
|
error("BOARD.md does not exist.");
|
|
967
1150
|
process.exit(1);
|
|
968
1151
|
}
|
|
969
|
-
const existing =
|
|
1152
|
+
const existing = readFileSync4(boardPath2, "utf-8");
|
|
970
1153
|
const normalize = (s) => s.replace(/\*\*Last generated\*\*:.*/, "").trim();
|
|
971
1154
|
if (normalize(existing) === normalize(newContent)) {
|
|
972
1155
|
ok("BOARD.md is up to date.");
|
|
@@ -978,18 +1161,115 @@ var boardCommand = new Command2("board").description("Regenerate BOARD.md from i
|
|
|
978
1161
|
process.exit(1);
|
|
979
1162
|
}
|
|
980
1163
|
}
|
|
981
|
-
|
|
1164
|
+
displayBoard(data);
|
|
1165
|
+
const boardPath = join5(boardDir, "BOARD.md");
|
|
982
1166
|
writeFileSync2(boardPath, newContent, "utf-8");
|
|
983
1167
|
ok(
|
|
984
|
-
`BOARD.md
|
|
1168
|
+
`BOARD.md regenerated`
|
|
985
1169
|
);
|
|
986
1170
|
});
|
|
987
1171
|
|
|
1172
|
+
// src/lib/update-check.ts
|
|
1173
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
1174
|
+
import { join as join6 } from "path";
|
|
1175
|
+
import { homedir } from "os";
|
|
1176
|
+
import { get } from "https";
|
|
1177
|
+
var PACKAGE_NAME = "lytos-cli";
|
|
1178
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1179
|
+
var CACHE_DIR = join6(homedir(), ".lytos");
|
|
1180
|
+
var CACHE_FILE = join6(CACHE_DIR, "last-update-check");
|
|
1181
|
+
function isNewer(remote, local) {
|
|
1182
|
+
const r = remote.split(".").map(Number);
|
|
1183
|
+
const l = local.split(".").map(Number);
|
|
1184
|
+
for (let i = 0; i < 3; i++) {
|
|
1185
|
+
if ((r[i] || 0) > (l[i] || 0)) return true;
|
|
1186
|
+
if ((r[i] || 0) < (l[i] || 0)) return false;
|
|
1187
|
+
}
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
function shouldCheck() {
|
|
1191
|
+
try {
|
|
1192
|
+
if (!existsSync7(CACHE_FILE)) return true;
|
|
1193
|
+
const lastCheck = parseInt(readFileSync5(CACHE_FILE, "utf-8").trim(), 10);
|
|
1194
|
+
return Date.now() - lastCheck > CHECK_INTERVAL_MS;
|
|
1195
|
+
} catch {
|
|
1196
|
+
return true;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function saveCheckTimestamp() {
|
|
1200
|
+
try {
|
|
1201
|
+
if (!existsSync7(CACHE_DIR)) {
|
|
1202
|
+
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
1203
|
+
}
|
|
1204
|
+
writeFileSync3(CACHE_FILE, String(Date.now()), "utf-8");
|
|
1205
|
+
} catch {
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
function fetchLatestVersion() {
|
|
1209
|
+
return new Promise((resolve2) => {
|
|
1210
|
+
const timeout = setTimeout(() => resolve2(null), 3e3);
|
|
1211
|
+
get(
|
|
1212
|
+
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
1213
|
+
{ headers: { Accept: "application/json" } },
|
|
1214
|
+
(res) => {
|
|
1215
|
+
let data = "";
|
|
1216
|
+
res.on("data", (chunk) => data += chunk);
|
|
1217
|
+
res.on("end", () => {
|
|
1218
|
+
clearTimeout(timeout);
|
|
1219
|
+
try {
|
|
1220
|
+
const json = JSON.parse(data);
|
|
1221
|
+
resolve2(json.version || null);
|
|
1222
|
+
} catch {
|
|
1223
|
+
resolve2(null);
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
res.on("error", () => {
|
|
1227
|
+
clearTimeout(timeout);
|
|
1228
|
+
resolve2(null);
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
).on("error", () => {
|
|
1232
|
+
clearTimeout(timeout);
|
|
1233
|
+
resolve2(null);
|
|
1234
|
+
});
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
async function checkForUpdates(currentVersion) {
|
|
1238
|
+
if (!shouldCheck()) return;
|
|
1239
|
+
saveCheckTimestamp();
|
|
1240
|
+
const latest = await fetchLatestVersion();
|
|
1241
|
+
if (!latest) return;
|
|
1242
|
+
if (isNewer(latest, currentVersion)) {
|
|
1243
|
+
console.error("");
|
|
1244
|
+
console.error(
|
|
1245
|
+
` ${yellow("\u26A0")} Update available: ${dim(currentVersion)} \u2192 ${bold(latest)}`
|
|
1246
|
+
);
|
|
1247
|
+
console.error(
|
|
1248
|
+
` Run ${bold("npm install -g lytos-cli")} to update`
|
|
1249
|
+
);
|
|
1250
|
+
console.error("");
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
988
1254
|
// src/cli.ts
|
|
989
1255
|
var program = new Command3();
|
|
990
|
-
program.name("
|
|
1256
|
+
program.name("lyt").description(
|
|
991
1257
|
"CLI tool for Lytos \u2014 a human-first method for working with AI agents"
|
|
992
|
-
).version("0.
|
|
1258
|
+
).version("0.2.1");
|
|
993
1259
|
program.addCommand(initCommand);
|
|
994
1260
|
program.addCommand(boardCommand);
|
|
1261
|
+
program.command("lint").description("Validate .lytos/ structure and content (coming soon)").action(() => {
|
|
1262
|
+
console.error("Coming soon. Follow https://github.com/getlytos/lytos-cli for updates.");
|
|
1263
|
+
process.exit(0);
|
|
1264
|
+
});
|
|
1265
|
+
program.command("doctor").description("Full diagnostic \u2014 missing files, broken links, stale memory (coming soon)").action(() => {
|
|
1266
|
+
console.error("Coming soon. Follow https://github.com/getlytos/lytos-cli for updates.");
|
|
1267
|
+
process.exit(0);
|
|
1268
|
+
});
|
|
1269
|
+
program.command("status").description("Display sprint DAG in terminal (coming soon)").action(() => {
|
|
1270
|
+
console.error("Coming soon. Follow https://github.com/getlytos/lytos-cli for updates.");
|
|
1271
|
+
process.exit(0);
|
|
1272
|
+
});
|
|
1273
|
+
var VERSION = "0.2.1";
|
|
995
1274
|
program.parse();
|
|
1275
|
+
checkForUpdates(VERSION);
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lytos-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI tool for Lytos — a human-first method for working with AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
+
"lyt": "dist/cli.js",
|
|
7
8
|
"lytos": "dist/cli.js",
|
|
8
9
|
"lytos-cli": "dist/cli.js"
|
|
9
10
|
},
|