@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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import { updateLogger } from './logger.js';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* UpdateChecker - Checks npm registry for new versions
|
|
13
|
+
* Runs in background and updates state file
|
|
14
|
+
*/
|
|
15
|
+
export class UpdateChecker {
|
|
16
|
+
constructor() {
|
|
17
|
+
// Use user's home directory for state (works across installations)
|
|
18
|
+
this.stateDir = path.join(os.homedir(), '.avc');
|
|
19
|
+
this.stateFile = path.join(this.stateDir, 'update-state.json');
|
|
20
|
+
this.settingsFile = path.join(this.stateDir, 'settings.json');
|
|
21
|
+
this.packageName = '@agile-vibe-coding/avc';
|
|
22
|
+
this.defaultCheckInterval = 60 * 60 * 1000; // 1 hour default
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Initialize state directory
|
|
26
|
+
initStateDir() {
|
|
27
|
+
if (!existsSync(this.stateDir)) {
|
|
28
|
+
mkdirSync(this.stateDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Read settings
|
|
33
|
+
readSettings() {
|
|
34
|
+
if (!existsSync(this.settingsFile)) {
|
|
35
|
+
return {
|
|
36
|
+
autoUpdate: {
|
|
37
|
+
enabled: true,
|
|
38
|
+
checkInterval: this.defaultCheckInterval,
|
|
39
|
+
silent: true,
|
|
40
|
+
notifyUser: true
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(readFileSync(this.settingsFile, 'utf8'));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
autoUpdate: {
|
|
50
|
+
enabled: true,
|
|
51
|
+
checkInterval: this.defaultCheckInterval,
|
|
52
|
+
silent: true,
|
|
53
|
+
notifyUser: true
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Write settings
|
|
60
|
+
writeSettings(settings) {
|
|
61
|
+
this.initStateDir();
|
|
62
|
+
writeFileSync(this.settingsFile, JSON.stringify(settings, null, 2));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Get current version from package.json
|
|
66
|
+
getCurrentVersion() {
|
|
67
|
+
try {
|
|
68
|
+
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
69
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
70
|
+
updateLogger.debug(`Current version: ${packageJson.version}`);
|
|
71
|
+
return packageJson.version;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
updateLogger.error('Failed to read current version', error);
|
|
74
|
+
console.error('Failed to read current version:', error.message);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check npm registry for latest version
|
|
80
|
+
async getLatestVersion() {
|
|
81
|
+
try {
|
|
82
|
+
updateLogger.debug('Checking npm registry for latest version');
|
|
83
|
+
const result = execSync(`npm view ${this.packageName} version`, {
|
|
84
|
+
encoding: 'utf8',
|
|
85
|
+
timeout: 10000,
|
|
86
|
+
stdio: ['pipe', 'pipe', 'ignore'] // Suppress stderr
|
|
87
|
+
});
|
|
88
|
+
const version = result.trim();
|
|
89
|
+
updateLogger.debug(`Latest version on npm: ${version}`);
|
|
90
|
+
return version;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
updateLogger.error('Failed to check npm registry', error);
|
|
93
|
+
// Silently fail - will retry on next check
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Compare versions (returns true if remote is newer)
|
|
99
|
+
isNewerVersion(current, latest) {
|
|
100
|
+
if (!current || !latest) return false;
|
|
101
|
+
|
|
102
|
+
const currentParts = current.split('.').map(Number);
|
|
103
|
+
const latestParts = latest.split('.').map(Number);
|
|
104
|
+
|
|
105
|
+
for (let i = 0; i < 3; i++) {
|
|
106
|
+
if (latestParts[i] > currentParts[i]) return true;
|
|
107
|
+
if (latestParts[i] < currentParts[i]) return false;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Read current state
|
|
113
|
+
readState() {
|
|
114
|
+
if (!existsSync(this.stateFile)) {
|
|
115
|
+
const currentVersion = this.getCurrentVersion();
|
|
116
|
+
return {
|
|
117
|
+
currentVersion: currentVersion,
|
|
118
|
+
latestVersion: null,
|
|
119
|
+
lastChecked: null,
|
|
120
|
+
updateAvailable: false,
|
|
121
|
+
updateReady: false,
|
|
122
|
+
downloadedVersion: null,
|
|
123
|
+
updateStatus: 'idle',
|
|
124
|
+
errorMessage: null,
|
|
125
|
+
userDismissed: false,
|
|
126
|
+
dismissedAt: null
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(readFileSync(this.stateFile, 'utf8'));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return this.readState(); // Return default if parse fails
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Write state
|
|
138
|
+
writeState(state) {
|
|
139
|
+
this.initStateDir();
|
|
140
|
+
writeFileSync(this.stateFile, JSON.stringify(state, null, 2));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check for updates
|
|
144
|
+
async checkForUpdates() {
|
|
145
|
+
updateLogger.info('Checking for updates');
|
|
146
|
+
const settings = this.readSettings();
|
|
147
|
+
|
|
148
|
+
// Don't check if auto-update disabled
|
|
149
|
+
if (!settings.autoUpdate.enabled) {
|
|
150
|
+
updateLogger.info('Auto-update disabled in settings');
|
|
151
|
+
return { updateAvailable: false, disabled: true };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const currentVersion = this.getCurrentVersion();
|
|
155
|
+
const latestVersion = await this.getLatestVersion();
|
|
156
|
+
|
|
157
|
+
if (!currentVersion) {
|
|
158
|
+
updateLogger.error('Failed to read current version');
|
|
159
|
+
return { updateAvailable: false, error: 'Failed to read current version' };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!latestVersion) {
|
|
163
|
+
updateLogger.error('Failed to check for updates from npm');
|
|
164
|
+
return { updateAvailable: false, error: 'Failed to check for updates' };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const updateAvailable = this.isNewerVersion(currentVersion, latestVersion);
|
|
168
|
+
|
|
169
|
+
updateLogger.info(`Version check: current=${currentVersion}, latest=${latestVersion}, updateAvailable=${updateAvailable}`);
|
|
170
|
+
|
|
171
|
+
const state = this.readState();
|
|
172
|
+
state.currentVersion = currentVersion;
|
|
173
|
+
state.latestVersion = latestVersion;
|
|
174
|
+
state.lastChecked = new Date().toISOString();
|
|
175
|
+
state.updateAvailable = updateAvailable;
|
|
176
|
+
|
|
177
|
+
// Reset dismissed flag if new version available
|
|
178
|
+
if (updateAvailable && state.downloadedVersion !== latestVersion) {
|
|
179
|
+
updateLogger.info(`New version available: ${latestVersion}`);
|
|
180
|
+
state.userDismissed = false;
|
|
181
|
+
state.updateReady = false;
|
|
182
|
+
state.updateStatus = 'pending';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.writeState(state);
|
|
186
|
+
|
|
187
|
+
return { updateAvailable, currentVersion, latestVersion };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Start background checker
|
|
191
|
+
startBackgroundChecker() {
|
|
192
|
+
const settings = this.readSettings();
|
|
193
|
+
|
|
194
|
+
// Don't start if disabled
|
|
195
|
+
if (!settings.autoUpdate.enabled) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Check immediately on start (async, don't block)
|
|
200
|
+
this.checkForUpdates().catch(() => {
|
|
201
|
+
// Silently fail
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Check at configured interval
|
|
205
|
+
const interval = settings.autoUpdate.checkInterval || this.defaultCheckInterval;
|
|
206
|
+
setInterval(() => {
|
|
207
|
+
this.checkForUpdates().catch(() => {
|
|
208
|
+
// Silently fail
|
|
209
|
+
});
|
|
210
|
+
}, interval);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Get check interval from settings
|
|
214
|
+
getCheckInterval() {
|
|
215
|
+
const settings = this.readSettings();
|
|
216
|
+
return settings.autoUpdate.checkInterval || this.defaultCheckInterval;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import { UpdateChecker } from './update-checker.js';
|
|
3
|
+
import { installerLogger } from './logger.js';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* UpdateInstaller - Handles npm package installation
|
|
10
|
+
* Runs npm install in background
|
|
11
|
+
*/
|
|
12
|
+
export class UpdateInstaller {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.checker = new UpdateChecker();
|
|
15
|
+
this.packageName = '@agile-vibe-coding/avc';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check if npm is available
|
|
19
|
+
checkNpmAvailable() {
|
|
20
|
+
try {
|
|
21
|
+
execSync('npm --version', { stdio: 'ignore' });
|
|
22
|
+
return true;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
installerLogger.error('npm availability check failed', error);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Install update in background
|
|
30
|
+
async installUpdate(version) {
|
|
31
|
+
installerLogger.info(`Starting update installation for version ${version}`);
|
|
32
|
+
const state = this.checker.readState();
|
|
33
|
+
|
|
34
|
+
// Don't install if already in progress
|
|
35
|
+
if (state.updateStatus === 'downloading') {
|
|
36
|
+
installerLogger.warn('Update already in progress');
|
|
37
|
+
return { success: false, message: 'Update already in progress' };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if npm is available
|
|
41
|
+
if (!this.checkNpmAvailable()) {
|
|
42
|
+
installerLogger.error('npm not found on system');
|
|
43
|
+
state.updateStatus = 'failed';
|
|
44
|
+
state.errorMessage = 'npm not found. Please install npm to enable auto-updates.';
|
|
45
|
+
this.checker.writeState(state);
|
|
46
|
+
return { success: false, message: state.errorMessage };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Update state to downloading
|
|
50
|
+
installerLogger.debug('Setting status to downloading');
|
|
51
|
+
state.updateStatus = 'downloading';
|
|
52
|
+
state.errorMessage = null;
|
|
53
|
+
this.checker.writeState(state);
|
|
54
|
+
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
// Prepare log files for npm output
|
|
57
|
+
const logDir = path.join(os.homedir(), '.avc', 'logs');
|
|
58
|
+
if (!fs.existsSync(logDir)) {
|
|
59
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const npmLogFile = path.join(logDir, 'npm-install.log');
|
|
63
|
+
const npmErrorLogFile = path.join(logDir, 'npm-install-error.log');
|
|
64
|
+
|
|
65
|
+
// Use shell redirection for reliable log capture with detached process
|
|
66
|
+
const npmCmd = `npm install -g "${this.packageName}@${version}" >> "${npmLogFile}" 2>> "${npmErrorLogFile}"`;
|
|
67
|
+
|
|
68
|
+
installerLogger.info(`Executing: npm install -g ${this.packageName}@${version}`);
|
|
69
|
+
installerLogger.debug(`npm output will be logged to ${npmLogFile}`);
|
|
70
|
+
|
|
71
|
+
const npmProcess = spawn(npmCmd, [], {
|
|
72
|
+
detached: true,
|
|
73
|
+
stdio: 'ignore',
|
|
74
|
+
shell: true
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
npmProcess.unref(); // Allow parent to exit independently
|
|
78
|
+
|
|
79
|
+
npmProcess.on('close', (code) => {
|
|
80
|
+
const state = this.checker.readState();
|
|
81
|
+
installerLogger.debug(`npm install exited with code ${code}`);
|
|
82
|
+
|
|
83
|
+
if (code === 0) {
|
|
84
|
+
// Success
|
|
85
|
+
installerLogger.info(`Update installed successfully: ${version}`);
|
|
86
|
+
state.updateStatus = 'ready';
|
|
87
|
+
state.updateReady = true;
|
|
88
|
+
state.downloadedVersion = version;
|
|
89
|
+
state.errorMessage = null;
|
|
90
|
+
this.checker.writeState(state);
|
|
91
|
+
|
|
92
|
+
resolve({ success: true, version });
|
|
93
|
+
} else if (code === 243 || code === 1) {
|
|
94
|
+
// Permission error (common code 243 on Unix, 1 on Windows)
|
|
95
|
+
const errorMsg = `Permission denied. Try: sudo npm install -g ${this.packageName}@${version}\nLogs: ${npmLogFile}`;
|
|
96
|
+
installerLogger.error(`Installation failed with permission error (code ${code})`, { error: errorMsg, logFile: npmLogFile });
|
|
97
|
+
state.updateStatus = 'failed';
|
|
98
|
+
state.updateReady = false;
|
|
99
|
+
state.errorMessage = errorMsg;
|
|
100
|
+
this.checker.writeState(state);
|
|
101
|
+
|
|
102
|
+
resolve({ success: false, message: state.errorMessage, needsSudo: true });
|
|
103
|
+
} else {
|
|
104
|
+
// Other error
|
|
105
|
+
const errorMsg = `Installation failed with code ${code}. Check logs: ${npmLogFile}`;
|
|
106
|
+
installerLogger.error(errorMsg, { logFile: npmLogFile, errorLogFile: npmErrorLogFile });
|
|
107
|
+
state.updateStatus = 'failed';
|
|
108
|
+
state.updateReady = false;
|
|
109
|
+
state.errorMessage = errorMsg;
|
|
110
|
+
this.checker.writeState(state);
|
|
111
|
+
|
|
112
|
+
resolve({ success: false, message: state.errorMessage });
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
npmProcess.on('error', (error) => {
|
|
117
|
+
const state = this.checker.readState();
|
|
118
|
+
installerLogger.error('npm process error', error);
|
|
119
|
+
|
|
120
|
+
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
121
|
+
const errorMsg = `Permission denied. Try: sudo npm install -g ${this.packageName}@${version}\nLogs: ${npmLogFile}`;
|
|
122
|
+
installerLogger.error(`Permission error: ${error.code}`, { error: errorMsg, logFile: npmLogFile });
|
|
123
|
+
state.updateStatus = 'failed';
|
|
124
|
+
state.updateReady = false;
|
|
125
|
+
state.errorMessage = errorMsg;
|
|
126
|
+
this.checker.writeState(state);
|
|
127
|
+
|
|
128
|
+
resolve({ success: false, message: state.errorMessage, needsSudo: true });
|
|
129
|
+
} else {
|
|
130
|
+
const errorMsg = `${error.message}. Check logs: ${npmLogFile}`;
|
|
131
|
+
installerLogger.error(`Unexpected npm process error: ${error.message}`, { error, logFile: npmLogFile });
|
|
132
|
+
state.updateStatus = 'failed';
|
|
133
|
+
state.updateReady = false;
|
|
134
|
+
state.errorMessage = errorMsg;
|
|
135
|
+
this.checker.writeState(state);
|
|
136
|
+
|
|
137
|
+
resolve({ success: false, message: state.errorMessage });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Trigger update installation
|
|
144
|
+
async triggerUpdate() {
|
|
145
|
+
const state = this.checker.readState();
|
|
146
|
+
|
|
147
|
+
if (!state.updateAvailable) {
|
|
148
|
+
return { success: false, message: 'No update available' };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (state.updateReady) {
|
|
152
|
+
return { success: true, message: 'Update already installed', alreadyReady: true };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (state.updateStatus === 'downloading') {
|
|
156
|
+
return { success: false, message: 'Update already in progress', inProgress: true };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return await this.installUpdate(state.latestVersion);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Auto-trigger update if available and not dismissed
|
|
163
|
+
async autoTriggerUpdate() {
|
|
164
|
+
const state = this.checker.readState();
|
|
165
|
+
const settings = this.checker.readSettings();
|
|
166
|
+
|
|
167
|
+
// Don't auto-install if disabled or user dismissed
|
|
168
|
+
if (!settings.autoUpdate.enabled || state.userDismissed) {
|
|
169
|
+
return { success: false, message: 'Auto-update disabled or dismissed' };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Don't install if already ready or downloading
|
|
173
|
+
if (state.updateReady || state.updateStatus === 'downloading') {
|
|
174
|
+
return { success: false, message: 'Update already processed' };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Only auto-install if update available and in idle/pending state
|
|
178
|
+
if (state.updateAvailable && (state.updateStatus === 'idle' || state.updateStatus === 'pending')) {
|
|
179
|
+
return await this.triggerUpdate();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { success: false, message: 'No action needed' };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { UpdateChecker } from './update-checker.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Update notification components for Ink
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Update notification banner (full notification)
|
|
10
|
+
export const UpdateNotification = ({ onDismiss }) => {
|
|
11
|
+
const checker = new UpdateChecker();
|
|
12
|
+
const state = checker.readState();
|
|
13
|
+
|
|
14
|
+
// Don't show if no update available
|
|
15
|
+
if (!state.updateAvailable) return null;
|
|
16
|
+
|
|
17
|
+
// Don't show if user dismissed
|
|
18
|
+
if (state.userDismissed) return null;
|
|
19
|
+
|
|
20
|
+
// Update downloading
|
|
21
|
+
if (state.updateStatus === 'downloading') {
|
|
22
|
+
return React.createElement(Box, {
|
|
23
|
+
borderStyle: 'round',
|
|
24
|
+
borderColor: 'blue',
|
|
25
|
+
paddingX: 2,
|
|
26
|
+
paddingY: 1,
|
|
27
|
+
marginBottom: 1
|
|
28
|
+
},
|
|
29
|
+
React.createElement(Box, { flexDirection: 'column' },
|
|
30
|
+
React.createElement(Text, { bold: true, color: 'blue' },
|
|
31
|
+
`⬇️ Downloading update v${state.latestVersion}...`
|
|
32
|
+
),
|
|
33
|
+
React.createElement(Text, { dimColor: true },
|
|
34
|
+
'This happens in the background. Continue working!'
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Update ready to use
|
|
41
|
+
if (state.updateReady) {
|
|
42
|
+
return React.createElement(Box, {
|
|
43
|
+
borderStyle: 'round',
|
|
44
|
+
borderColor: 'green',
|
|
45
|
+
paddingX: 2,
|
|
46
|
+
paddingY: 1,
|
|
47
|
+
marginBottom: 1
|
|
48
|
+
},
|
|
49
|
+
React.createElement(Box, { flexDirection: 'column' },
|
|
50
|
+
React.createElement(Text, { bold: true, color: 'green' },
|
|
51
|
+
`✅ Update v${state.downloadedVersion} ready!`
|
|
52
|
+
),
|
|
53
|
+
React.createElement(Text, null,
|
|
54
|
+
'Restart to use the new version'
|
|
55
|
+
),
|
|
56
|
+
React.createElement(Box, { marginTop: 1, flexDirection: 'row' },
|
|
57
|
+
React.createElement(Text, { dimColor: true },
|
|
58
|
+
'Type '
|
|
59
|
+
),
|
|
60
|
+
React.createElement(Text, { color: 'cyan', bold: true },
|
|
61
|
+
'/restart'
|
|
62
|
+
),
|
|
63
|
+
React.createElement(Text, { dimColor: true },
|
|
64
|
+
' or press '
|
|
65
|
+
),
|
|
66
|
+
React.createElement(Text, { color: 'cyan', bold: true },
|
|
67
|
+
'Ctrl+R'
|
|
68
|
+
)
|
|
69
|
+
),
|
|
70
|
+
React.createElement(Box, { marginTop: 1, flexDirection: 'row' },
|
|
71
|
+
React.createElement(Text, { dimColor: true, italic: true },
|
|
72
|
+
'Press '
|
|
73
|
+
),
|
|
74
|
+
React.createElement(Text, { color: 'yellow' },
|
|
75
|
+
'Esc'
|
|
76
|
+
),
|
|
77
|
+
React.createElement(Text, { dimColor: true, italic: true },
|
|
78
|
+
' to dismiss'
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Update failed
|
|
86
|
+
if (state.updateStatus === 'failed') {
|
|
87
|
+
const needsSudo = state.errorMessage && state.errorMessage.includes('sudo');
|
|
88
|
+
|
|
89
|
+
return React.createElement(Box, {
|
|
90
|
+
borderStyle: 'round',
|
|
91
|
+
borderColor: 'red',
|
|
92
|
+
paddingX: 2,
|
|
93
|
+
paddingY: 1,
|
|
94
|
+
marginBottom: 1
|
|
95
|
+
},
|
|
96
|
+
React.createElement(Box, { flexDirection: 'column' },
|
|
97
|
+
React.createElement(Text, { bold: true, color: 'red' },
|
|
98
|
+
'❌ Update failed'
|
|
99
|
+
),
|
|
100
|
+
React.createElement(Text, null,
|
|
101
|
+
state.errorMessage || 'Unknown error'
|
|
102
|
+
),
|
|
103
|
+
needsSudo && React.createElement(Box, { marginTop: 1 },
|
|
104
|
+
React.createElement(Text, { dimColor: true },
|
|
105
|
+
'Manual install: '
|
|
106
|
+
),
|
|
107
|
+
React.createElement(Text, { color: 'cyan' },
|
|
108
|
+
state.errorMessage.split('Try: ')[1]
|
|
109
|
+
)
|
|
110
|
+
),
|
|
111
|
+
React.createElement(Text, { dimColor: true, marginTop: 1 },
|
|
112
|
+
'Will retry on next check'
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Update pending (just discovered)
|
|
119
|
+
if (state.updateStatus === 'pending' || state.updateStatus === 'idle') {
|
|
120
|
+
return React.createElement(Box, {
|
|
121
|
+
borderStyle: 'round',
|
|
122
|
+
borderColor: 'yellow',
|
|
123
|
+
paddingX: 2,
|
|
124
|
+
paddingY: 1,
|
|
125
|
+
marginBottom: 1
|
|
126
|
+
},
|
|
127
|
+
React.createElement(Box, { flexDirection: 'column' },
|
|
128
|
+
React.createElement(Text, { bold: true, color: 'yellow' },
|
|
129
|
+
`📦 Update available: v${state.latestVersion}`
|
|
130
|
+
),
|
|
131
|
+
React.createElement(Text, null,
|
|
132
|
+
'Installing in background...'
|
|
133
|
+
),
|
|
134
|
+
React.createElement(Text, { dimColor: true, italic: true },
|
|
135
|
+
'You will be notified when ready'
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Update status badge (compact, for banner)
|
|
145
|
+
export const UpdateStatusBadge = () => {
|
|
146
|
+
const checker = new UpdateChecker();
|
|
147
|
+
const state = checker.readState();
|
|
148
|
+
|
|
149
|
+
if (!state.updateAvailable || state.userDismissed) return null;
|
|
150
|
+
|
|
151
|
+
if (state.updateReady) {
|
|
152
|
+
return React.createElement(Text, { color: 'green', bold: true },
|
|
153
|
+
' [Update Ready!]'
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (state.updateStatus === 'downloading') {
|
|
158
|
+
return React.createElement(Text, { color: 'blue', bold: true },
|
|
159
|
+
' [Updating...]'
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (state.updateStatus === 'pending' || state.updateStatus === 'idle') {
|
|
164
|
+
return React.createElement(Text, { color: 'yellow', bold: true },
|
|
165
|
+
' [Update Available]'
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return null;
|
|
170
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agile-vibe-coding/avc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agile Vibe Coding (AVC) - Framework for managing AI agent-based software development projects",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "cli/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"avc": "./cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"cli/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "echo \"No tests specified yet\" && exit 0",
|
|
20
|
+
"prepublishOnly": "echo \"Running pre-publish checks...\" && npm test"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"avc",
|
|
24
|
+
"agile-vibe-coding",
|
|
25
|
+
"ai-agents",
|
|
26
|
+
"llm",
|
|
27
|
+
"claude",
|
|
28
|
+
"chatgpt",
|
|
29
|
+
"agile",
|
|
30
|
+
"framework",
|
|
31
|
+
"project-management",
|
|
32
|
+
"automation",
|
|
33
|
+
"cli",
|
|
34
|
+
"workflow"
|
|
35
|
+
],
|
|
36
|
+
"author": "Nacho Coll",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"homepage": "https://agilevibecoding.org",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/NachoColl/agilevibecoding.git",
|
|
42
|
+
"directory": "src"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/NachoColl/agilevibecoding/issues"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@anthropic-ai/sdk": "^0.20.0",
|
|
52
|
+
"dotenv": "^16.4.0",
|
|
53
|
+
"ink": "^5.0.1",
|
|
54
|
+
"ink-select-input": "^6.0.0",
|
|
55
|
+
"ink-spinner": "^5.0.0",
|
|
56
|
+
"react": "^18.3.1"
|
|
57
|
+
}
|
|
58
|
+
}
|