@agile-vibe-coding/avc 0.1.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/LICENSE +21 -0
- package/README.md +3 -0
- package/cli/index.js +28 -0
- package/cli/init.js +321 -0
- package/cli/logger.js +138 -0
- package/cli/repl-ink.js +764 -0
- package/cli/repl-old.js +353 -0
- package/cli/template-processor.js +491 -0
- package/cli/templates/project.md +62 -0
- package/cli/update-checker.js +218 -0
- package/cli/update-installer.js +184 -0
- package/cli/update-notifier.js +170 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nacho Coll (@NachoColl)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/cli/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { startRepl, executeCommand } from './repl-ink.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AVC CLI - Main entry point
|
|
7
|
+
*
|
|
8
|
+
* Supports both interactive REPL mode and direct command execution
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* avc - Start interactive REPL
|
|
12
|
+
* avc help - Show help and exit
|
|
13
|
+
* avc init - Run init command and exit
|
|
14
|
+
* avc status - Run status command and exit
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
|
|
19
|
+
if (args.length === 0) {
|
|
20
|
+
// No arguments - start interactive REPL
|
|
21
|
+
startRepl();
|
|
22
|
+
} else {
|
|
23
|
+
// Command provided - execute non-interactively
|
|
24
|
+
const command = args[0].startsWith('/') ? args[0] : `/${args[0]}`;
|
|
25
|
+
executeCommand(command).catch(() => {
|
|
26
|
+
process.exit(1);
|
|
27
|
+
});
|
|
28
|
+
}
|
package/cli/init.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { TemplateProcessor } from './template-processor.js';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* AVC Project Initiator
|
|
14
|
+
*
|
|
15
|
+
* Checks if an AVC project exists in the current directory and creates
|
|
16
|
+
* the necessary files and folders if they don't exist:
|
|
17
|
+
* - .avc/ folder
|
|
18
|
+
* - .avc/avc.json settings file
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
class ProjectInitiator {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.projectRoot = process.cwd();
|
|
24
|
+
this.avcDir = path.join(this.projectRoot, '.avc');
|
|
25
|
+
this.avcConfigPath = path.join(this.avcDir, 'avc.json');
|
|
26
|
+
this.progressPath = path.join(this.avcDir, 'init-progress.json');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get the project name from the current folder name
|
|
31
|
+
*/
|
|
32
|
+
getProjectName() {
|
|
33
|
+
return path.basename(this.projectRoot);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if .avc folder exists
|
|
38
|
+
*/
|
|
39
|
+
hasAvcFolder() {
|
|
40
|
+
return fs.existsSync(this.avcDir);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if avc.json exists
|
|
45
|
+
*/
|
|
46
|
+
hasAvcConfig() {
|
|
47
|
+
return fs.existsSync(this.avcConfigPath);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create .avc folder
|
|
52
|
+
*/
|
|
53
|
+
createAvcFolder() {
|
|
54
|
+
if (!this.hasAvcFolder()) {
|
|
55
|
+
fs.mkdirSync(this.avcDir, { recursive: true });
|
|
56
|
+
console.log('✓ Created .avc/ folder');
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
console.log('✓ .avc/ folder already exists');
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create avc.json with default settings
|
|
65
|
+
*/
|
|
66
|
+
createAvcConfig() {
|
|
67
|
+
if (!this.hasAvcConfig()) {
|
|
68
|
+
const defaultConfig = {
|
|
69
|
+
version: '1.0.0',
|
|
70
|
+
projectName: this.getProjectName(),
|
|
71
|
+
framework: 'avc',
|
|
72
|
+
created: new Date().toISOString(),
|
|
73
|
+
settings: {
|
|
74
|
+
contextScopes: ['epic', 'story', 'task', 'subtask'],
|
|
75
|
+
workItemStatuses: ['ready', 'pending', 'implementing', 'implemented', 'testing', 'completed', 'blocked', 'feedback'],
|
|
76
|
+
agentTypes: ['product-owner', 'server', 'client', 'infrastructure', 'testing'],
|
|
77
|
+
model: 'claude-sonnet-4-5-20250929'
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
fs.writeFileSync(
|
|
82
|
+
this.avcConfigPath,
|
|
83
|
+
JSON.stringify(defaultConfig, null, 2),
|
|
84
|
+
'utf8'
|
|
85
|
+
);
|
|
86
|
+
console.log('✓ Created .avc/avc.json configuration file');
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
console.log('✓ .avc/avc.json already exists');
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if current directory is a git repository
|
|
95
|
+
*/
|
|
96
|
+
isGitRepository() {
|
|
97
|
+
try {
|
|
98
|
+
execSync('git rev-parse --git-dir', { stdio: 'ignore' });
|
|
99
|
+
return true;
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create .env file for API keys
|
|
107
|
+
*/
|
|
108
|
+
createEnvFile() {
|
|
109
|
+
const envPath = path.join(this.projectRoot, '.env');
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(envPath)) {
|
|
112
|
+
const envContent = `# Anthropic API Key for AI-powered Sponsor Call ceremony
|
|
113
|
+
# Get your key at: https://console.anthropic.com/settings/keys
|
|
114
|
+
ANTHROPIC_API_KEY=
|
|
115
|
+
|
|
116
|
+
# Add other API keys below as needed
|
|
117
|
+
`;
|
|
118
|
+
fs.writeFileSync(envPath, envContent, 'utf8');
|
|
119
|
+
console.log('✓ Created .env file for API keys');
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
console.log('✓ .env file already exists');
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Add .env to .gitignore if git repository
|
|
128
|
+
*/
|
|
129
|
+
addToGitignore() {
|
|
130
|
+
if (!this.isGitRepository()) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const gitignorePath = path.join(this.projectRoot, '.gitignore');
|
|
135
|
+
|
|
136
|
+
let gitignoreContent = '';
|
|
137
|
+
if (fs.existsSync(gitignorePath)) {
|
|
138
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check if .env is already in .gitignore
|
|
142
|
+
if (gitignoreContent.includes('.env')) {
|
|
143
|
+
console.log('✓ .env already in .gitignore');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Add .env to .gitignore
|
|
148
|
+
const newContent = gitignoreContent
|
|
149
|
+
? `${gitignoreContent}\n# Environment variables\n.env\n`
|
|
150
|
+
: '# Environment variables\n.env\n';
|
|
151
|
+
|
|
152
|
+
fs.writeFileSync(gitignorePath, newContent, 'utf8');
|
|
153
|
+
console.log('✓ Added .env to .gitignore');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check if there's an incomplete init in progress
|
|
158
|
+
*/
|
|
159
|
+
hasIncompleteInit() {
|
|
160
|
+
return fs.existsSync(this.progressPath);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Read progress from file
|
|
165
|
+
*/
|
|
166
|
+
readProgress() {
|
|
167
|
+
try {
|
|
168
|
+
const content = fs.readFileSync(this.progressPath, 'utf8');
|
|
169
|
+
return JSON.parse(content);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Write progress to file
|
|
177
|
+
*/
|
|
178
|
+
writeProgress(progress) {
|
|
179
|
+
if (!fs.existsSync(this.avcDir)) {
|
|
180
|
+
fs.mkdirSync(this.avcDir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
fs.writeFileSync(this.progressPath, JSON.stringify(progress, null, 2), 'utf8');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Clear progress file (init completed successfully)
|
|
187
|
+
*/
|
|
188
|
+
clearProgress() {
|
|
189
|
+
if (fs.existsSync(this.progressPath)) {
|
|
190
|
+
fs.unlinkSync(this.progressPath);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generate project document via Sponsor Call ceremony
|
|
197
|
+
*/
|
|
198
|
+
async generateProjectDocument(progress = null) {
|
|
199
|
+
const processor = new TemplateProcessor(this.progressPath);
|
|
200
|
+
await processor.processTemplate(progress);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if the current directory is an AVC project
|
|
205
|
+
*/
|
|
206
|
+
isAvcProject() {
|
|
207
|
+
return this.hasAvcFolder() && this.hasAvcConfig();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Initialize the AVC project
|
|
212
|
+
*/
|
|
213
|
+
async init() {
|
|
214
|
+
console.log('\n🚀 AVC Project Initiator - Sponsor Call Ceremony\n');
|
|
215
|
+
console.log(`Project directory: ${this.projectRoot}\n`);
|
|
216
|
+
|
|
217
|
+
let progress = null;
|
|
218
|
+
|
|
219
|
+
// Check for incomplete initialization
|
|
220
|
+
if (this.hasIncompleteInit()) {
|
|
221
|
+
progress = this.readProgress();
|
|
222
|
+
|
|
223
|
+
if (progress && progress.stage !== 'completed') {
|
|
224
|
+
console.log('⚠️ Found incomplete initialization from previous session');
|
|
225
|
+
console.log(` Last activity: ${new Date(progress.lastUpdate).toLocaleString()}`);
|
|
226
|
+
console.log(` Stage: ${progress.stage}`);
|
|
227
|
+
console.log(` Progress: ${progress.answeredQuestions || 0}/${progress.totalQuestions || 0} questions answered`);
|
|
228
|
+
console.log('\n▶️ Continuing from where you left off...\n');
|
|
229
|
+
}
|
|
230
|
+
} else if (this.isAvcProject()) {
|
|
231
|
+
// No incomplete progress but project exists - already initialized
|
|
232
|
+
console.log('✓ AVC project already initialized');
|
|
233
|
+
console.log('\nProject is ready to use.');
|
|
234
|
+
return;
|
|
235
|
+
} else {
|
|
236
|
+
// Fresh start
|
|
237
|
+
console.log('Initializing AVC project...\n');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Create .avc folder
|
|
241
|
+
this.createAvcFolder();
|
|
242
|
+
|
|
243
|
+
// Create avc.json
|
|
244
|
+
this.createAvcConfig();
|
|
245
|
+
|
|
246
|
+
// Create .env file for API keys
|
|
247
|
+
this.createEnvFile();
|
|
248
|
+
|
|
249
|
+
// Add .env to .gitignore if git repository
|
|
250
|
+
this.addToGitignore();
|
|
251
|
+
|
|
252
|
+
// Save initial progress
|
|
253
|
+
if (!progress) {
|
|
254
|
+
progress = {
|
|
255
|
+
stage: 'questionnaire',
|
|
256
|
+
totalQuestions: 5,
|
|
257
|
+
answeredQuestions: 0,
|
|
258
|
+
collectedValues: {},
|
|
259
|
+
lastUpdate: new Date().toISOString()
|
|
260
|
+
};
|
|
261
|
+
this.writeProgress(progress);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Generate project document via Sponsor Call ceremony
|
|
265
|
+
await this.generateProjectDocument(progress);
|
|
266
|
+
|
|
267
|
+
// Mark as completed and clean up
|
|
268
|
+
progress.stage = 'completed';
|
|
269
|
+
progress.lastUpdate = new Date().toISOString();
|
|
270
|
+
this.writeProgress(progress);
|
|
271
|
+
this.clearProgress();
|
|
272
|
+
|
|
273
|
+
console.log('\n✅ AVC project initialized successfully!');
|
|
274
|
+
console.log('\nNext steps:');
|
|
275
|
+
console.log(' 1. Add your ANTHROPIC_API_KEY to .env file');
|
|
276
|
+
console.log(' 2. Review .avc/project/doc.md for your project definition');
|
|
277
|
+
console.log(' 3. Review .avc/avc.json configuration');
|
|
278
|
+
console.log(' 4. Create your project context and work items');
|
|
279
|
+
console.log(' 5. Use AI agents to implement features');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Display current project status
|
|
284
|
+
*/
|
|
285
|
+
status() {
|
|
286
|
+
console.log('\n📊 AVC Project Status\n');
|
|
287
|
+
console.log(`Project directory: ${this.projectRoot}`);
|
|
288
|
+
console.log(`Project name: ${this.getProjectName()}\n`);
|
|
289
|
+
|
|
290
|
+
console.log('Components:');
|
|
291
|
+
console.log(` .avc/ folder: ${this.hasAvcFolder() ? '✓' : '✗'}`);
|
|
292
|
+
console.log(` avc.json: ${this.hasAvcConfig() ? '✓' : '✗'}`);
|
|
293
|
+
|
|
294
|
+
console.log(`\nStatus: ${this.isAvcProject() ? '✅ Initialized' : '⚠️ Not initialized'}`);
|
|
295
|
+
|
|
296
|
+
if (!this.isAvcProject()) {
|
|
297
|
+
console.log('\nRun "avc init" to initialize the project.');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Export for use in REPL
|
|
303
|
+
export { ProjectInitiator };
|
|
304
|
+
|
|
305
|
+
// CLI execution (only when run directly, not when imported)
|
|
306
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
307
|
+
const command = process.argv[2] || 'init';
|
|
308
|
+
const initiator = new ProjectInitiator();
|
|
309
|
+
|
|
310
|
+
switch (command) {
|
|
311
|
+
case 'init':
|
|
312
|
+
initiator.init();
|
|
313
|
+
break;
|
|
314
|
+
case 'status':
|
|
315
|
+
initiator.status();
|
|
316
|
+
break;
|
|
317
|
+
default:
|
|
318
|
+
console.log('Unknown command. Available commands: init, status');
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
}
|
package/cli/logger.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logger - Writes debug and error logs to file
|
|
7
|
+
*
|
|
8
|
+
* Log location: ~/.avc/logs/avc.log
|
|
9
|
+
* Log rotation: Keeps last 10MB, rotates to avc.log.old
|
|
10
|
+
*/
|
|
11
|
+
export class Logger {
|
|
12
|
+
constructor(componentName = 'AVC') {
|
|
13
|
+
this.componentName = componentName;
|
|
14
|
+
this.logDir = path.join(os.homedir(), '.avc', 'logs');
|
|
15
|
+
this.logFile = path.join(this.logDir, 'avc.log');
|
|
16
|
+
this.maxLogSize = 10 * 1024 * 1024; // 10MB
|
|
17
|
+
|
|
18
|
+
this.ensureLogDir();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
ensureLogDir() {
|
|
22
|
+
try {
|
|
23
|
+
if (!fs.existsSync(this.logDir)) {
|
|
24
|
+
fs.mkdirSync(this.logDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
// Silently fail if we can't create log directory
|
|
28
|
+
console.error('Failed to create log directory:', error.message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
rotateLogIfNeeded() {
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(this.logFile)) {
|
|
35
|
+
const stats = fs.statSync(this.logFile);
|
|
36
|
+
if (stats.size > this.maxLogSize) {
|
|
37
|
+
const oldLogFile = this.logFile + '.old';
|
|
38
|
+
if (fs.existsSync(oldLogFile)) {
|
|
39
|
+
fs.unlinkSync(oldLogFile);
|
|
40
|
+
}
|
|
41
|
+
fs.renameSync(this.logFile, oldLogFile);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// Silently fail rotation
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
formatMessage(level, message, data = null) {
|
|
50
|
+
const timestamp = new Date().toISOString();
|
|
51
|
+
let logLine = `[${timestamp}] [${level}] [${this.componentName}] ${message}`;
|
|
52
|
+
|
|
53
|
+
if (data) {
|
|
54
|
+
if (data instanceof Error) {
|
|
55
|
+
logLine += `\n Error: ${data.message}`;
|
|
56
|
+
if (data.stack) {
|
|
57
|
+
logLine += `\n Stack: ${data.stack}`;
|
|
58
|
+
}
|
|
59
|
+
} else if (typeof data === 'object') {
|
|
60
|
+
try {
|
|
61
|
+
logLine += `\n Data: ${JSON.stringify(data, null, 2)}`;
|
|
62
|
+
} catch (e) {
|
|
63
|
+
logLine += `\n Data: [Unable to stringify]`;
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
logLine += `\n Data: ${data}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return logLine + '\n';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
writeLog(level, message, data = null) {
|
|
74
|
+
try {
|
|
75
|
+
this.rotateLogIfNeeded();
|
|
76
|
+
const logMessage = this.formatMessage(level, message, data);
|
|
77
|
+
fs.appendFileSync(this.logFile, logMessage, 'utf8');
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// Silently fail if we can't write to log
|
|
80
|
+
console.error('Failed to write log:', error.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
debug(message, data = null) {
|
|
85
|
+
this.writeLog('DEBUG', message, data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
info(message, data = null) {
|
|
89
|
+
this.writeLog('INFO', message, data);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
warn(message, data = null) {
|
|
93
|
+
this.writeLog('WARN', message, data);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
error(message, data = null) {
|
|
97
|
+
this.writeLog('ERROR', message, data);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Read recent logs (last N lines)
|
|
101
|
+
readRecentLogs(lines = 50) {
|
|
102
|
+
try {
|
|
103
|
+
if (!fs.existsSync(this.logFile)) {
|
|
104
|
+
return 'No logs available yet.';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const content = fs.readFileSync(this.logFile, 'utf8');
|
|
108
|
+
const allLines = content.split('\n').filter(line => line.trim());
|
|
109
|
+
const recentLines = allLines.slice(-lines);
|
|
110
|
+
|
|
111
|
+
return recentLines.join('\n');
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return `Error reading logs: ${error.message}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Clear all logs
|
|
118
|
+
clearLogs() {
|
|
119
|
+
try {
|
|
120
|
+
if (fs.existsSync(this.logFile)) {
|
|
121
|
+
fs.unlinkSync(this.logFile);
|
|
122
|
+
}
|
|
123
|
+
const oldLogFile = this.logFile + '.old';
|
|
124
|
+
if (fs.existsSync(oldLogFile)) {
|
|
125
|
+
fs.unlinkSync(oldLogFile);
|
|
126
|
+
}
|
|
127
|
+
this.info('Logs cleared');
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Failed to clear logs:', error.message);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Export singleton instances for common components
|
|
135
|
+
export const updateLogger = new Logger('UpdateChecker');
|
|
136
|
+
export const installerLogger = new Logger('UpdateInstaller');
|
|
137
|
+
export const replLogger = new Logger('REPL');
|
|
138
|
+
export const initLogger = new Logger('Init');
|