create-byan-agent 2.7.8 → 2.8.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/bin/create-byan-agent-v2.js +119 -184
- package/lib/stt/engine.js +277 -0
- package/lib/stt/parakeet-backend.js +262 -0
- package/lib/stt/whisper-backend.js +171 -0
- package/lib/utils/file-differ.js +110 -0
- package/lib/utils/manifest.js +118 -0
- package/lib/utils/version-compare.js +69 -0
- package/lib/yanstaller/backuper.js +101 -45
- package/lib/yanstaller/index.js +41 -4
- package/lib/yanstaller/updater.js +271 -0
- package/package.json +5 -2
- package/setup-parakeet.js +260 -0
- package/src/webui/api.js +293 -0
- package/src/webui/public/app.js +455 -0
- package/src/webui/public/index.html +192 -0
- package/src/webui/public/style.css +732 -0
- package/src/webui/server.js +215 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Comparison Utility
|
|
3
|
+
*
|
|
4
|
+
* Compares semver versions and fetches latest from npm registry.
|
|
5
|
+
*
|
|
6
|
+
* @module utils/version-compare
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const https = require('https');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compare two semver version strings.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} a - First version (e.g., '2.7.9')
|
|
15
|
+
* @param {string} b - Second version (e.g., '2.8.0')
|
|
16
|
+
* @returns {number} -1 if a < b, 0 if equal, 1 if a > b
|
|
17
|
+
*/
|
|
18
|
+
function compareVersions(a, b) {
|
|
19
|
+
const partsA = a.replace(/^v/, '').split('.').map(Number);
|
|
20
|
+
const partsB = b.replace(/^v/, '').split('.').map(Number);
|
|
21
|
+
const len = Math.max(partsA.length, partsB.length);
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < len; i++) {
|
|
24
|
+
const numA = partsA[i] || 0;
|
|
25
|
+
const numB = partsB[i] || 0;
|
|
26
|
+
if (numA < numB) return -1;
|
|
27
|
+
if (numA > numB) return 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Fetch the latest published version of a package from npm registry.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} packageName - npm package name
|
|
37
|
+
* @returns {Promise<string>} Latest version string
|
|
38
|
+
*/
|
|
39
|
+
function getLatestVersion(packageName) {
|
|
40
|
+
const url = `https://registry.npmjs.org/${packageName}/latest`;
|
|
41
|
+
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
https.get(url, { headers: { 'Accept': 'application/json' } }, (res) => {
|
|
44
|
+
if (res.statusCode !== 200) {
|
|
45
|
+
reject(new Error(`npm registry returned ${res.statusCode} for ${packageName}`));
|
|
46
|
+
res.resume();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let data = '';
|
|
51
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
52
|
+
res.on('end', () => {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(data);
|
|
55
|
+
resolve(parsed.version);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
reject(new Error(`Failed to parse npm registry response: ${err.message}`));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}).on('error', (err) => {
|
|
61
|
+
reject(new Error(`Failed to reach npm registry: ${err.message}`));
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
compareVersions,
|
|
68
|
+
getLatestVersion
|
|
69
|
+
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BACKUPER Module
|
|
3
|
-
*
|
|
4
|
-
* Backs up and restores
|
|
5
|
-
*
|
|
6
|
-
* Phase 6: 24h development
|
|
7
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Backs up and restores _byan/ directory with timestamp-based snapshots.
|
|
5
|
+
*
|
|
8
6
|
* @module yanstaller/backuper
|
|
9
7
|
*/
|
|
10
8
|
|
|
9
|
+
const fs = require('fs-extra');
|
|
11
10
|
const path = require('path');
|
|
12
|
-
|
|
11
|
+
|
|
12
|
+
const BACKUP_PREFIX = '_byan.backup-';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @typedef {Object} BackupResult
|
|
@@ -20,75 +20,130 @@ const fileUtils = require('../utils/file-utils');
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @param {string}
|
|
23
|
+
* Recursively count files and total size in a directory.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} dir - Directory to measure
|
|
26
|
+
* @returns {Promise<{count: number, size: number}>}
|
|
27
|
+
*/
|
|
28
|
+
async function measureDir(dir) {
|
|
29
|
+
let count = 0;
|
|
30
|
+
let size = 0;
|
|
31
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
32
|
+
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const fullPath = path.join(dir, entry.name);
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
const sub = await measureDir(fullPath);
|
|
37
|
+
count += sub.count;
|
|
38
|
+
size += sub.size;
|
|
39
|
+
} else if (entry.isFile()) {
|
|
40
|
+
const stat = await fs.stat(fullPath);
|
|
41
|
+
count++;
|
|
42
|
+
size += stat.size;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { count, size };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Backup _byan/ directory to a timestamped snapshot.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} byanPath - Absolute path to _byan/ directory
|
|
26
53
|
* @returns {Promise<BackupResult>}
|
|
27
54
|
*/
|
|
28
|
-
async function backup(
|
|
55
|
+
async function backup(byanPath) {
|
|
56
|
+
if (!await fs.pathExists(byanPath)) {
|
|
57
|
+
throw new BackupError(`Source directory does not exist: ${byanPath}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
const timestamp = Date.now();
|
|
30
|
-
const backupPath = `${
|
|
31
|
-
|
|
61
|
+
const backupPath = path.join(path.dirname(byanPath), `${BACKUP_PREFIX}${timestamp}`);
|
|
62
|
+
|
|
32
63
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
64
|
+
await fs.copy(byanPath, backupPath);
|
|
65
|
+
const { count, size } = await measureDir(backupPath);
|
|
66
|
+
|
|
36
67
|
return {
|
|
37
68
|
success: true,
|
|
38
69
|
backupPath,
|
|
39
|
-
filesBackedUp:
|
|
40
|
-
size
|
|
70
|
+
filesBackedUp: count,
|
|
71
|
+
size
|
|
41
72
|
};
|
|
42
73
|
} catch (error) {
|
|
43
|
-
throw new BackupError(`Failed to backup ${
|
|
74
|
+
throw new BackupError(`Failed to backup ${byanPath}`, { cause: error });
|
|
44
75
|
}
|
|
45
76
|
}
|
|
46
77
|
|
|
47
78
|
/**
|
|
48
|
-
* Restore from backup
|
|
49
|
-
*
|
|
50
|
-
* @param {string} backupPath -
|
|
51
|
-
* @param {string} targetPath -
|
|
79
|
+
* Restore from a backup, replacing the current _byan/ directory.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} backupPath - Absolute path to the backup directory
|
|
82
|
+
* @param {string} targetPath - Absolute path to restore into (e.g., _byan/)
|
|
52
83
|
* @returns {Promise<void>}
|
|
53
84
|
*/
|
|
54
85
|
async function restore(backupPath, targetPath) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
86
|
+
if (!await fs.pathExists(backupPath)) {
|
|
87
|
+
throw new BackupError(`Backup not found: ${backupPath}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await fs.remove(targetPath);
|
|
91
|
+
await fs.copy(backupPath, targetPath);
|
|
58
92
|
}
|
|
59
93
|
|
|
60
94
|
/**
|
|
61
|
-
* List available
|
|
62
|
-
*
|
|
95
|
+
* List available backup directories sorted by timestamp (newest first).
|
|
96
|
+
*
|
|
63
97
|
* @param {string} projectRoot - Project root directory
|
|
64
|
-
* @returns {Promise<string[]>}
|
|
98
|
+
* @returns {Promise<string[]>} Absolute paths to backup directories
|
|
65
99
|
*/
|
|
66
100
|
async function listBackups(projectRoot) {
|
|
67
|
-
|
|
68
|
-
|
|
101
|
+
if (!await fs.pathExists(projectRoot)) return [];
|
|
102
|
+
|
|
103
|
+
const entries = await fs.readdir(projectRoot, { withFileTypes: true });
|
|
104
|
+
const backups = entries
|
|
105
|
+
.filter(e => e.isDirectory() && e.name.startsWith(BACKUP_PREFIX))
|
|
106
|
+
.map(e => ({
|
|
107
|
+
name: e.name,
|
|
108
|
+
timestamp: parseInt(e.name.slice(BACKUP_PREFIX.length), 10),
|
|
109
|
+
path: path.join(projectRoot, e.name)
|
|
110
|
+
}))
|
|
111
|
+
.filter(b => !isNaN(b.timestamp))
|
|
112
|
+
.sort((a, b) => b.timestamp - a.timestamp);
|
|
113
|
+
|
|
114
|
+
return backups.map(b => b.path);
|
|
69
115
|
}
|
|
70
116
|
|
|
71
117
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
118
|
+
* Prune old backups, keeping only the N most recent.
|
|
119
|
+
*
|
|
74
120
|
* @param {string} projectRoot - Project root directory
|
|
75
|
-
* @param {number}
|
|
76
|
-
* @returns {Promise<number>}
|
|
121
|
+
* @param {number} [maxBackups=3] - Number of backups to keep
|
|
122
|
+
* @returns {Promise<number>} Number of backups deleted
|
|
77
123
|
*/
|
|
78
|
-
async function
|
|
79
|
-
|
|
80
|
-
|
|
124
|
+
async function pruneBackups(projectRoot, maxBackups = 3) {
|
|
125
|
+
const all = await listBackups(projectRoot);
|
|
126
|
+
|
|
127
|
+
if (all.length <= maxBackups) return 0;
|
|
128
|
+
|
|
129
|
+
const toDelete = all.slice(maxBackups);
|
|
130
|
+
for (const backupPath of toDelete) {
|
|
131
|
+
await fs.remove(backupPath);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return toDelete.length;
|
|
81
135
|
}
|
|
82
136
|
|
|
83
137
|
/**
|
|
84
|
-
* Get backup
|
|
85
|
-
*
|
|
86
|
-
* @param {string} backupPath -
|
|
87
|
-
* @returns {Promise<number>}
|
|
138
|
+
* Get total size of a backup directory in bytes.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} backupPath - Absolute path to backup directory
|
|
141
|
+
* @returns {Promise<number>} Size in bytes
|
|
88
142
|
*/
|
|
89
143
|
async function getBackupSize(backupPath) {
|
|
90
|
-
|
|
91
|
-
|
|
144
|
+
if (!await fs.pathExists(backupPath)) return 0;
|
|
145
|
+
const { size } = await measureDir(backupPath);
|
|
146
|
+
return size;
|
|
92
147
|
}
|
|
93
148
|
|
|
94
149
|
class BackupError extends Error {
|
|
@@ -102,7 +157,8 @@ module.exports = {
|
|
|
102
157
|
backup,
|
|
103
158
|
restore,
|
|
104
159
|
listBackups,
|
|
105
|
-
|
|
160
|
+
pruneBackups,
|
|
106
161
|
getBackupSize,
|
|
107
|
-
BackupError
|
|
162
|
+
BackupError,
|
|
163
|
+
BACKUP_PREFIX
|
|
108
164
|
};
|
package/lib/yanstaller/index.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @module yanstaller
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
const path = require('path');
|
|
9
10
|
const detector = require('./detector');
|
|
10
11
|
const recommender = require('./recommender');
|
|
11
12
|
const installer = require('./installer');
|
|
@@ -13,6 +14,7 @@ const validator = require('./validator');
|
|
|
13
14
|
const troubleshooter = require('./troubleshooter');
|
|
14
15
|
const interviewer = require('./interviewer');
|
|
15
16
|
const backuper = require('./backuper');
|
|
17
|
+
const updater = require('./updater');
|
|
16
18
|
const wizard = require('./wizard');
|
|
17
19
|
const platformSelector = require('./platform-selector');
|
|
18
20
|
const logger = require('../utils/logger');
|
|
@@ -122,18 +124,53 @@ async function uninstall() {
|
|
|
122
124
|
/**
|
|
123
125
|
* Update existing BYAN installation
|
|
124
126
|
*
|
|
125
|
-
* @param {string}
|
|
127
|
+
* @param {string} projectRoot - Project root directory
|
|
128
|
+
* @param {Object} [options={}] - Update options
|
|
129
|
+
* @param {boolean} [options.force] - Force update even if same version
|
|
130
|
+
* @param {boolean} [options.preview] - Show diff without applying
|
|
131
|
+
* @returns {Promise<import('./updater').UpdateResult>}
|
|
132
|
+
*/
|
|
133
|
+
async function update(projectRoot, options = {}) {
|
|
134
|
+
return updater.update(projectRoot, options);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Rollback to the most recent backup.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} projectRoot - Project root directory
|
|
126
141
|
* @returns {Promise<void>}
|
|
127
142
|
*/
|
|
128
|
-
async function
|
|
129
|
-
|
|
143
|
+
async function rollback(projectRoot) {
|
|
144
|
+
const backups = await backuper.listBackups(projectRoot);
|
|
145
|
+
if (backups.length === 0) {
|
|
146
|
+
throw new Error('No backups found to restore from.');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const latestBackup = backups[0];
|
|
150
|
+
const targetPath = path.join(projectRoot, '_byan');
|
|
151
|
+
logger.info(`Restoring from ${path.basename(latestBackup)}...`);
|
|
152
|
+
await backuper.restore(latestBackup, targetPath);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* List all available BYAN backups.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} projectRoot - Project root directory
|
|
159
|
+
* @returns {Promise<string[]>} Absolute paths, newest first
|
|
160
|
+
*/
|
|
161
|
+
async function listBackups(projectRoot) {
|
|
162
|
+
return backuper.listBackups(projectRoot);
|
|
130
163
|
}
|
|
131
164
|
|
|
132
165
|
module.exports = {
|
|
133
166
|
install,
|
|
134
167
|
uninstall,
|
|
135
168
|
update,
|
|
169
|
+
rollback,
|
|
170
|
+
listBackups,
|
|
136
171
|
// Expose for testing
|
|
137
172
|
detector,
|
|
138
|
-
platformSelector
|
|
173
|
+
platformSelector,
|
|
174
|
+
updater,
|
|
175
|
+
backuper
|
|
139
176
|
};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UPDATER Module
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the BYAN update lifecycle:
|
|
5
|
+
* version check -> diff -> backup -> apply -> manifest -> validate.
|
|
6
|
+
*
|
|
7
|
+
* @module yanstaller/updater
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { compareVersions, getLatestVersion } = require('../utils/version-compare');
|
|
13
|
+
const { diffFiles } = require('../utils/file-differ');
|
|
14
|
+
const { readManifest, writeManifest, generateManifest, detectUserModifications } = require('../utils/manifest');
|
|
15
|
+
const backuper = require('./backuper');
|
|
16
|
+
const logger = require('../utils/logger');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the template directory containing the canonical _byan/ files.
|
|
20
|
+
* Same logic as getTemplateDir() in create-byan-agent-v2.js.
|
|
21
|
+
*
|
|
22
|
+
* @returns {string|null} Absolute path to templates/ or null
|
|
23
|
+
*/
|
|
24
|
+
function getTemplateDir() {
|
|
25
|
+
const npmPackagePath = path.join(__dirname, '..', '..', 'templates');
|
|
26
|
+
if (fs.existsSync(npmPackagePath)) return npmPackagePath;
|
|
27
|
+
|
|
28
|
+
const devPath = path.join(__dirname, '..', '..', '..');
|
|
29
|
+
if (fs.existsSync(devPath)) return devPath;
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {Object} UpdateCheck
|
|
36
|
+
* @property {boolean} updateAvailable
|
|
37
|
+
* @property {string} installed - Currently installed version
|
|
38
|
+
* @property {string} latest - Latest version on npm
|
|
39
|
+
* @property {string[]} changes - List of files that would change
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check whether an update is available.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} projectRoot - Project root directory
|
|
46
|
+
* @returns {Promise<UpdateCheck>}
|
|
47
|
+
*/
|
|
48
|
+
async function checkForUpdate(projectRoot) {
|
|
49
|
+
const installedVersion = await getInstalledVersion(projectRoot);
|
|
50
|
+
const latest = await getLatestVersion('create-byan-agent');
|
|
51
|
+
const cmp = compareVersions(installedVersion, latest);
|
|
52
|
+
|
|
53
|
+
let changes = [];
|
|
54
|
+
if (cmp < 0) {
|
|
55
|
+
const templateDir = getTemplateDir();
|
|
56
|
+
if (templateDir) {
|
|
57
|
+
const templateByan = path.join(templateDir, '_byan');
|
|
58
|
+
const installedByan = path.join(projectRoot, '_byan');
|
|
59
|
+
if (await fs.pathExists(templateByan) && await fs.pathExists(installedByan)) {
|
|
60
|
+
const diff = await diffFiles(installedByan, templateByan);
|
|
61
|
+
changes = [...diff.toUpdate, ...diff.toAdd];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
updateAvailable: cmp < 0,
|
|
68
|
+
installed: installedVersion,
|
|
69
|
+
latest,
|
|
70
|
+
changes
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Preview what an update would do without applying changes.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} projectRoot - Project root directory
|
|
78
|
+
* @returns {Promise<{diff: import('../utils/file-differ').DiffResult, userModified: string[], installed: string, latest: string}>}
|
|
79
|
+
*/
|
|
80
|
+
async function preview(projectRoot) {
|
|
81
|
+
const installedVersion = await getInstalledVersion(projectRoot);
|
|
82
|
+
const latest = await getLatestVersion('create-byan-agent');
|
|
83
|
+
|
|
84
|
+
const templateDir = getTemplateDir();
|
|
85
|
+
if (!templateDir) {
|
|
86
|
+
throw new Error('Template directory not found. Is the package installed correctly?');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const templateByan = path.join(templateDir, '_byan');
|
|
90
|
+
const installedByan = path.join(projectRoot, '_byan');
|
|
91
|
+
|
|
92
|
+
if (!await fs.pathExists(installedByan)) {
|
|
93
|
+
throw new Error('No _byan/ directory found. Run install first.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const diff = await diffFiles(installedByan, templateByan);
|
|
97
|
+
const userModified = await detectUserModifications(projectRoot);
|
|
98
|
+
|
|
99
|
+
return { diff, userModified, installed: installedVersion, latest };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @typedef {Object} UpdateOptions
|
|
104
|
+
* @property {boolean} [force=false] - Force update even if same version
|
|
105
|
+
* @property {boolean} [preview=false] - Only show diff, don't apply
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @typedef {Object} UpdateResult
|
|
110
|
+
* @property {boolean} success
|
|
111
|
+
* @property {string} previousVersion
|
|
112
|
+
* @property {string} newVersion
|
|
113
|
+
* @property {string} backupPath
|
|
114
|
+
* @property {number} filesUpdated
|
|
115
|
+
* @property {number} filesAdded
|
|
116
|
+
* @property {number} filesSkipped - User-modified files left untouched
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Execute the full update flow.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} projectRoot - Project root directory
|
|
123
|
+
* @param {UpdateOptions} [options={}]
|
|
124
|
+
* @returns {Promise<UpdateResult>}
|
|
125
|
+
*/
|
|
126
|
+
async function update(projectRoot, options = {}) {
|
|
127
|
+
const installedVersion = await getInstalledVersion(projectRoot);
|
|
128
|
+
const latest = await getLatestVersion('create-byan-agent');
|
|
129
|
+
const cmp = compareVersions(installedVersion, latest);
|
|
130
|
+
|
|
131
|
+
if (cmp >= 0 && !options.force) {
|
|
132
|
+
logger.info(`Already up to date (${installedVersion})`);
|
|
133
|
+
return {
|
|
134
|
+
success: true,
|
|
135
|
+
previousVersion: installedVersion,
|
|
136
|
+
newVersion: installedVersion,
|
|
137
|
+
backupPath: null,
|
|
138
|
+
filesUpdated: 0,
|
|
139
|
+
filesAdded: 0,
|
|
140
|
+
filesSkipped: 0
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const templateDir = getTemplateDir();
|
|
145
|
+
if (!templateDir) {
|
|
146
|
+
throw new Error('Template directory not found. Is the package installed correctly?');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const templateByan = path.join(templateDir, '_byan');
|
|
150
|
+
const installedByan = path.join(projectRoot, '_byan');
|
|
151
|
+
|
|
152
|
+
if (!await fs.pathExists(templateByan)) {
|
|
153
|
+
throw new Error('Template _byan/ directory not found.');
|
|
154
|
+
}
|
|
155
|
+
if (!await fs.pathExists(installedByan)) {
|
|
156
|
+
throw new Error('No _byan/ directory found. Run install first.');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const diff = await diffFiles(installedByan, templateByan);
|
|
160
|
+
const userModified = new Set(await detectUserModifications(projectRoot));
|
|
161
|
+
|
|
162
|
+
if (options.preview) {
|
|
163
|
+
return formatPreviewResult(diff, userModified, installedVersion, latest);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Backup before applying changes
|
|
167
|
+
const backupResult = await backuper.backup(installedByan);
|
|
168
|
+
logger.debug(`Backup created: ${backupResult.backupPath} (${backupResult.filesBackedUp} files)`);
|
|
169
|
+
|
|
170
|
+
let filesUpdated = 0;
|
|
171
|
+
let filesAdded = 0;
|
|
172
|
+
let filesSkipped = 0;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Apply updated files (skip user-modified unless forced)
|
|
176
|
+
for (const file of diff.toUpdate) {
|
|
177
|
+
if (userModified.has(file) && !options.force) {
|
|
178
|
+
filesSkipped++;
|
|
179
|
+
logger.debug(`Skipped (user-modified): ${file}`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const src = path.join(templateByan, file);
|
|
183
|
+
const dest = path.join(installedByan, file);
|
|
184
|
+
await fs.ensureDir(path.dirname(dest));
|
|
185
|
+
await fs.copy(src, dest);
|
|
186
|
+
filesUpdated++;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Add new files
|
|
190
|
+
for (const file of diff.toAdd) {
|
|
191
|
+
const src = path.join(templateByan, file);
|
|
192
|
+
const dest = path.join(installedByan, file);
|
|
193
|
+
await fs.ensureDir(path.dirname(dest));
|
|
194
|
+
await fs.copy(src, dest);
|
|
195
|
+
filesAdded++;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Regenerate manifest with new state
|
|
199
|
+
const newManifest = await generateManifest(installedByan, latest);
|
|
200
|
+
await writeManifest(projectRoot, newManifest);
|
|
201
|
+
|
|
202
|
+
// Prune old backups
|
|
203
|
+
await backuper.pruneBackups(projectRoot);
|
|
204
|
+
|
|
205
|
+
} catch (error) {
|
|
206
|
+
// Rollback on failure
|
|
207
|
+
logger.error(`Update failed, restoring backup: ${error.message}`);
|
|
208
|
+
await backuper.restore(backupResult.backupPath, installedByan);
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
success: true,
|
|
214
|
+
previousVersion: installedVersion,
|
|
215
|
+
newVersion: latest,
|
|
216
|
+
backupPath: backupResult.backupPath,
|
|
217
|
+
filesUpdated,
|
|
218
|
+
filesAdded,
|
|
219
|
+
filesSkipped
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Read the installed BYAN version from the manifest or package.json fallback.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} projectRoot - Project root
|
|
227
|
+
* @returns {Promise<string>}
|
|
228
|
+
*/
|
|
229
|
+
async function getInstalledVersion(projectRoot) {
|
|
230
|
+
const manifest = await readManifest(projectRoot);
|
|
231
|
+
if (manifest && manifest.version) return manifest.version;
|
|
232
|
+
|
|
233
|
+
// Fallback: read from the package that shipped this code
|
|
234
|
+
const pkgPath = path.join(__dirname, '..', '..', 'package.json');
|
|
235
|
+
if (await fs.pathExists(pkgPath)) {
|
|
236
|
+
const pkg = await fs.readJSON(pkgPath);
|
|
237
|
+
return pkg.version;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return '0.0.0';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Format a preview-only result for display.
|
|
245
|
+
*
|
|
246
|
+
* @param {import('../utils/file-differ').DiffResult} diff
|
|
247
|
+
* @param {Set<string>} userModified
|
|
248
|
+
* @param {string} installedVersion
|
|
249
|
+
* @param {string} latest
|
|
250
|
+
* @returns {UpdateResult}
|
|
251
|
+
*/
|
|
252
|
+
function formatPreviewResult(diff, userModified, installedVersion, latest) {
|
|
253
|
+
const skippable = diff.toUpdate.filter(f => userModified.has(f));
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
previousVersion: installedVersion,
|
|
257
|
+
newVersion: latest,
|
|
258
|
+
backupPath: null,
|
|
259
|
+
filesUpdated: diff.toUpdate.length - skippable.length,
|
|
260
|
+
filesAdded: diff.toAdd.length,
|
|
261
|
+
filesSkipped: skippable.length
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
checkForUpdate,
|
|
267
|
+
preview,
|
|
268
|
+
update,
|
|
269
|
+
getInstalledVersion,
|
|
270
|
+
getTemplateDir
|
|
271
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-byan-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "BYAN v2.2.2 - Intelligent AI agent installer with multi-platform native support (GitHub Copilot CLI, Claude Code, Codex/OpenCode)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-byan-agent": "bin/create-byan-agent-v2.js"
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"fs-extra": "^11.2.0",
|
|
45
45
|
"inquirer": "^8.2.5",
|
|
46
46
|
"js-yaml": "^4.1.0",
|
|
47
|
-
"ora": "^5.4.1"
|
|
47
|
+
"ora": "^5.4.1",
|
|
48
|
+
"ws": "^8.20.0"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
50
51
|
"node": ">=18.0.0"
|
|
@@ -55,6 +56,8 @@
|
|
|
55
56
|
"src/",
|
|
56
57
|
"templates/",
|
|
57
58
|
"setup-turbo-whisper.js",
|
|
59
|
+
"setup-parakeet.js",
|
|
60
|
+
"docker-compose.parakeet.yml",
|
|
58
61
|
"README.md",
|
|
59
62
|
"CHANGELOG.md",
|
|
60
63
|
"LICENSE"
|