roguelike-cli 1.0.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/INSTALL.md +108 -0
- package/bin/rlc +4 -0
- package/dist/ai/claude.js +115 -0
- package/dist/commands/init.js +175 -0
- package/dist/config/config.js +85 -0
- package/dist/index.js +29 -0
- package/dist/interactive/commands.js +552 -0
- package/dist/interactive/index.js +180 -0
- package/dist/interactive/startup.js +38 -0
- package/dist/storage/nodeConfig.js +109 -0
- package/dist/storage/storage.js +155 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/schemaParser.js +217 -0
- package/package.json +45 -0
- package/src/ai/claude.ts +139 -0
- package/src/commands/init.ts +159 -0
- package/src/config/config.ts +69 -0
- package/src/index.ts +37 -0
- package/src/interactive/commands.ts +625 -0
- package/src/interactive/index.ts +174 -0
- package/src/interactive/startup.ts +38 -0
- package/src/storage/nodeConfig.ts +109 -0
- package/src/storage/storage.ts +143 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/schemaParser.ts +259 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
export interface TreeNode {
|
|
2
|
+
name: string;
|
|
3
|
+
deadline?: string;
|
|
4
|
+
branch?: string;
|
|
5
|
+
zone?: string;
|
|
6
|
+
children: TreeNode[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ParsedInput {
|
|
10
|
+
title: string;
|
|
11
|
+
tree: TreeNode[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseInput(input: string): ParsedInput {
|
|
15
|
+
const originalInput = input.trim();
|
|
16
|
+
let cleanedInput = originalInput;
|
|
17
|
+
|
|
18
|
+
let title = 'Schema';
|
|
19
|
+
|
|
20
|
+
const todoTitleMatch = cleanedInput.match(/^(todo|tasks?)\s+(?:list\s+for\s+)?(.+?)(?:\s*:|\s*$)/i);
|
|
21
|
+
if (todoTitleMatch) {
|
|
22
|
+
title = `TODO ${todoTitleMatch[2].trim()}`;
|
|
23
|
+
cleanedInput = cleanedInput.replace(/^(todo|tasks?)\s+(?:list\s+for\s+)?[^:]+:\s*/i, '');
|
|
24
|
+
} else {
|
|
25
|
+
const archTitleMatch = cleanedInput.match(/^(architecture|structure|kubernetes|k8s|yandex|cloud)\s+(?:production\s+)?(.+?)(?:\s*:|\s*$)/i);
|
|
26
|
+
if (archTitleMatch) {
|
|
27
|
+
const prefix = archTitleMatch[1].charAt(0).toUpperCase() + archTitleMatch[1].slice(1);
|
|
28
|
+
title = `${prefix} ${archTitleMatch[2].trim()}`;
|
|
29
|
+
cleanedInput = cleanedInput.replace(/^(architecture|structure|kubernetes|k8s|yandex|cloud)\s+(?:production\s+)?[^:]+:\s*/i, '');
|
|
30
|
+
} else {
|
|
31
|
+
const colonMatch = cleanedInput.match(/^(.+?):\s*(.+)$/);
|
|
32
|
+
if (colonMatch) {
|
|
33
|
+
title = colonMatch[1].trim();
|
|
34
|
+
cleanedInput = colonMatch[2].trim();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
cleanedInput = cleanedInput.replace(/^(todo|tasks?)\s*:?\s*/i, '');
|
|
40
|
+
cleanedInput = cleanedInput.replace(/^(architecture|structure)\s*/i, '');
|
|
41
|
+
cleanedInput = cleanedInput.replace(/^(kubernetes\s+cluster\s+with|kubernetes\s+cluster|kubernetes|k8s)\s*/i, '');
|
|
42
|
+
|
|
43
|
+
cleanedInput = cleanedInput.trim();
|
|
44
|
+
|
|
45
|
+
if (!cleanedInput || cleanedInput.length === 0) {
|
|
46
|
+
return {
|
|
47
|
+
title: originalInput || 'Schema',
|
|
48
|
+
tree: [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parts = cleanedInput.split(',').map(p => p.trim()).filter(p => p.length > 0);
|
|
53
|
+
|
|
54
|
+
const tree: TreeNode[] = [];
|
|
55
|
+
const pathMap: Record<string, TreeNode> = {};
|
|
56
|
+
const branchNodes: Record<string, TreeNode> = {};
|
|
57
|
+
|
|
58
|
+
for (const part of parts) {
|
|
59
|
+
const metadata = extractMetadata(part);
|
|
60
|
+
const name = extractNodeName(part);
|
|
61
|
+
|
|
62
|
+
if (part.includes('/')) {
|
|
63
|
+
const pathParts = part.split('/').map(p => p.trim()).filter(p => p.length > 0);
|
|
64
|
+
if (pathParts.length > 0) {
|
|
65
|
+
let currentPath = '';
|
|
66
|
+
let parentNode: TreeNode | null = null;
|
|
67
|
+
|
|
68
|
+
pathParts.forEach((pathPart, index) => {
|
|
69
|
+
const cleanPart = extractNodeName(pathPart);
|
|
70
|
+
const fullPath = currentPath ? `${currentPath}/${cleanPart}` : cleanPart;
|
|
71
|
+
|
|
72
|
+
if (!pathMap[fullPath]) {
|
|
73
|
+
const newNode: TreeNode = {
|
|
74
|
+
name: cleanPart,
|
|
75
|
+
children: [],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (index === pathParts.length - 1) {
|
|
79
|
+
if (metadata.deadline) newNode.deadline = metadata.deadline;
|
|
80
|
+
if (metadata.branch) newNode.branch = metadata.branch;
|
|
81
|
+
if (metadata.zone) newNode.zone = metadata.zone;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pathMap[fullPath] = newNode;
|
|
85
|
+
|
|
86
|
+
if (parentNode) {
|
|
87
|
+
parentNode.children.push(newNode);
|
|
88
|
+
} else {
|
|
89
|
+
tree.push(newNode);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
parentNode = newNode;
|
|
93
|
+
} else {
|
|
94
|
+
parentNode = pathMap[fullPath];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
currentPath = fullPath;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
const newNode: TreeNode = {
|
|
102
|
+
name,
|
|
103
|
+
deadline: metadata.deadline,
|
|
104
|
+
branch: metadata.branch,
|
|
105
|
+
zone: metadata.zone,
|
|
106
|
+
children: [],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (metadata.branch) {
|
|
110
|
+
if (!branchNodes[metadata.branch]) {
|
|
111
|
+
const branchNode: TreeNode = {
|
|
112
|
+
name: `Branch: ${metadata.branch}`,
|
|
113
|
+
children: [],
|
|
114
|
+
};
|
|
115
|
+
tree.push(branchNode);
|
|
116
|
+
branchNodes[metadata.branch] = branchNode;
|
|
117
|
+
}
|
|
118
|
+
branchNodes[metadata.branch].children.push(newNode);
|
|
119
|
+
} else {
|
|
120
|
+
tree.push(newNode);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { title, tree };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function extractNodeName(text: string): string {
|
|
129
|
+
let cleaned = text.trim();
|
|
130
|
+
cleaned = cleaned.replace(/\[deadline:\s*[^\]]+\]|deadline:\s*[^\s,]+/gi, '').trim();
|
|
131
|
+
cleaned = cleaned.replace(/\[branch:\s*[^\]]+\]|branch:\s*[^\s,]+/gi, '').trim();
|
|
132
|
+
cleaned = cleaned.replace(/\[zone:\s*[^\]]+\]|zone:\s*[^\s,]+/gi, '').trim();
|
|
133
|
+
cleaned = cleaned.replace(/^\[|\]$/g, '').replace(/,\s*$/, '').trim();
|
|
134
|
+
return cleaned;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function extractMetadata(text: string): { deadline?: string; branch?: string; zone?: string } {
|
|
138
|
+
const metadata: { deadline?: string; branch?: string; zone?: string } = {};
|
|
139
|
+
|
|
140
|
+
const deadlineMatch = text.match(/\[deadline:\s*([^\]]+)\]|deadline:\s*([^\s,]+)/i);
|
|
141
|
+
if (deadlineMatch) {
|
|
142
|
+
metadata.deadline = (deadlineMatch[1] || deadlineMatch[2]).trim();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const branchMatch = text.match(/\[branch:\s*([^\]]+)\]|branch:\s*([^\s,]+)/i);
|
|
146
|
+
if (branchMatch) {
|
|
147
|
+
metadata.branch = (branchMatch[1] || branchMatch[2]).trim();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const zoneMatch = text.match(/\[zone:\s*([^\]]+)\]|zone:\s*([^\s,]+)/i);
|
|
151
|
+
if (zoneMatch) {
|
|
152
|
+
metadata.zone = (zoneMatch[1] || zoneMatch[2]).trim();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return metadata;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function generateSchema(parsed: ParsedInput): string[] {
|
|
159
|
+
if (parsed.tree.length === 0) {
|
|
160
|
+
return [
|
|
161
|
+
`┌─ ${parsed.title} ────────────────────┐`,
|
|
162
|
+
'│ No items │',
|
|
163
|
+
'└────────────────────────────┘',
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const lines: string[] = [];
|
|
168
|
+
const maxWidth = Math.max(parsed.title.length + 4, calculateMaxWidth(parsed.tree, 0));
|
|
169
|
+
const width = Math.min(maxWidth + 10, 75);
|
|
170
|
+
|
|
171
|
+
const titlePadding = width - parsed.title.length - 4;
|
|
172
|
+
lines.push(`┌─ ${parsed.title}${' '.repeat(Math.max(0, titlePadding))}┐`);
|
|
173
|
+
lines.push(`│${' '.repeat(width - 2)}│`);
|
|
174
|
+
|
|
175
|
+
parsed.tree.forEach((node, index) => {
|
|
176
|
+
const isLast = index === parsed.tree.length - 1;
|
|
177
|
+
const nodeLines = generateTreeNode(node, '│ ', isLast, width);
|
|
178
|
+
lines.push(...nodeLines);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
lines.push(`│${' '.repeat(width - 2)}│`);
|
|
182
|
+
lines.push('└' + '─'.repeat(width - 2) + '┘');
|
|
183
|
+
|
|
184
|
+
return lines;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function calculateMaxWidth(nodes: TreeNode[], depth: number): number {
|
|
188
|
+
let maxWidth = 20;
|
|
189
|
+
|
|
190
|
+
nodes.forEach(node => {
|
|
191
|
+
let nodeWidth = node.name.length;
|
|
192
|
+
if (node.deadline) nodeWidth += node.deadline.length + 3;
|
|
193
|
+
if (node.branch && !node.name.startsWith('Branch:')) nodeWidth += node.branch.length + 3;
|
|
194
|
+
if (node.zone && !node.name.startsWith('Zone:')) nodeWidth += node.zone.length + 3;
|
|
195
|
+
|
|
196
|
+
nodeWidth += depth * 4;
|
|
197
|
+
|
|
198
|
+
if (nodeWidth > maxWidth) {
|
|
199
|
+
maxWidth = nodeWidth;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (node.children.length > 0) {
|
|
203
|
+
const childWidth = calculateMaxWidth(node.children, depth + 1);
|
|
204
|
+
if (childWidth > maxWidth) {
|
|
205
|
+
maxWidth = childWidth;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return maxWidth;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function generateTreeNode(node: TreeNode, prefix: string, isLast: boolean, width: number): string[] {
|
|
214
|
+
const lines: string[] = [];
|
|
215
|
+
|
|
216
|
+
let nodeText = node.name;
|
|
217
|
+
if (node.deadline) {
|
|
218
|
+
nodeText += ` [${node.deadline}]`;
|
|
219
|
+
}
|
|
220
|
+
if (node.branch && !node.name.startsWith('Branch:')) {
|
|
221
|
+
nodeText += ` (${node.branch})`;
|
|
222
|
+
}
|
|
223
|
+
if (node.zone && !node.name.startsWith('Zone:')) {
|
|
224
|
+
nodeText += ` [zone: ${node.zone}]`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const connector = isLast ? '└──' : '├──';
|
|
228
|
+
const spacing = isLast ? ' ' : '│ ';
|
|
229
|
+
|
|
230
|
+
const borderPadding = 2;
|
|
231
|
+
const prefixLen = prefix.length;
|
|
232
|
+
const connectorLen = connector.length + 1;
|
|
233
|
+
const availableWidth = width - prefixLen - connectorLen - borderPadding;
|
|
234
|
+
|
|
235
|
+
let displayText = nodeText;
|
|
236
|
+
if (displayText.length > availableWidth) {
|
|
237
|
+
displayText = displayText.substring(0, availableWidth - 3) + '...';
|
|
238
|
+
} else {
|
|
239
|
+
displayText = displayText.padEnd(availableWidth);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
lines.push(`${prefix}${connector} ${displayText} │`);
|
|
243
|
+
|
|
244
|
+
if (node.children.length > 0) {
|
|
245
|
+
const childPrefixBase = prefix.replace(/│ $/, spacing);
|
|
246
|
+
node.children.forEach((child, index) => {
|
|
247
|
+
const isChildLast = index === node.children.length - 1;
|
|
248
|
+
const childPrefix = isLast
|
|
249
|
+
? childPrefixBase.replace(/│/g, ' ')
|
|
250
|
+
: childPrefixBase;
|
|
251
|
+
const childLines = generateTreeNode(child, childPrefix, isChildLast, width);
|
|
252
|
+
lines.push(...childLines);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return lines;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"moduleResolution": "node"
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|