create-byan-agent 2.59.2 → 2.60.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/bin/create-byan-agent-v2.js +94 -44
- package/install/lib/api-defaults.js +111 -0
- package/install/lib/byan-leantime-integration.js +1 -1
- package/install/lib/byan-web-integration.js +10 -3
- package/install/lib/claude-native-setup.js +1 -1
- package/install/lib/codex-native-setup.js +55 -20
- package/install/lib/gdoc-setup.js +1 -1
- package/install/lib/install-engine.js +490 -58
- package/install/lib/mcp-extensions/index.js +1 -1
- package/install/lib/native-helper.js +13 -2
- package/install/lib/ownership.js +489 -0
- package/install/lib/platforms/claude-code.js +8 -4
- package/install/lib/resolve-binary.js +398 -0
- package/install/lib/rtk-integration.js +9 -2
- package/install/lib/stt/engine.js +5 -6
- package/install/lib/target-user.js +373 -0
- package/install/lib/yanstaller/agent-launcher.js +7 -16
- package/install/package.json +1 -1
- package/install/packages/platform-config/lib/token-prompt.js +7 -1
- package/install/src/webui/api.js +14 -1
- package/install/src/webui/public/app.js +39 -0
- package/package.json +1 -2
- package/update-byan-agent/lib/migrate-mcp-config.js +1 -1
- package/update-byan-agent/package.json +0 -1
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TARGET USER — qui est le destinataire reel de l'installation.
|
|
5
|
+
*
|
|
6
|
+
* POURQUOI ce module : sous elevation de privilege (sudo, doas, pkexec, su),
|
|
7
|
+
* le processus voit os.homedir() a /root et un PATH de root. L'installateur
|
|
8
|
+
* cherche ~/.claude dans /root, ne trouve rien, et annonce une reussite en
|
|
9
|
+
* ayant saute l'etape Claude. Le probleme mesure le 2026-08-11 : aucune
|
|
10
|
+
* occurrence de SUDO_USER, SUDO_UID, DOAS_USER, PKEXEC_UID ou geteuid dans
|
|
11
|
+
* install/. Ce module rend l'humain derriere l'elevation, ou nomme
|
|
12
|
+
* explicitement le cas ou il n'y en a pas.
|
|
13
|
+
*
|
|
14
|
+
* L'echelle de resolution, chaque echelon nomme dans le champ `source` :
|
|
15
|
+
*
|
|
16
|
+
* sudo SUDO_UID numerique -> uid/gid/nom depuis l'environnement
|
|
17
|
+
* doas DOAS_USER -> nom seul, uid resolu via /etc/passwd
|
|
18
|
+
* pkexec PKEXEC_UID numerique -> uid seul, nom resolu via /etc/passwd
|
|
19
|
+
* cwd-owner processus root, dossier de travail a un humain (couvre su)
|
|
20
|
+
* root processus root, dossier de travail a root — resultat legitime
|
|
21
|
+
* (conteneur, integration continue, installation dans /opt)
|
|
22
|
+
* self processus non-root, la cible est celui qui lance
|
|
23
|
+
*
|
|
24
|
+
* POURQUOI une fonction pure : elle lit l'environnement, /etc/passwd et un stat
|
|
25
|
+
* du dossier de travail au travers des dependances injectees. Aucune ecriture,
|
|
26
|
+
* aucun spawn, aucune lecture de la machine hote en test.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const nodeFs = require('fs');
|
|
30
|
+
const os = require('os');
|
|
31
|
+
|
|
32
|
+
const PASSWD_PATH = '/etc/passwd';
|
|
33
|
+
|
|
34
|
+
// Home conventionnel du compte root, par famille de systeme. POURQUOI a part :
|
|
35
|
+
// root ne suit pas la convention /home/<nom> des comptes humains.
|
|
36
|
+
const ROOT_HOME = { darwin: '/var/root', linux: '/root' };
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Lit un uid/gid transmis par l'environnement. Rend un nombre ou null.
|
|
40
|
+
* POURQUOI un test de forme et pas Number() seul : Number('abc') rend NaN, et
|
|
41
|
+
* un NaN propage dans un uid produit un chown ou une comparaison silencieusement
|
|
42
|
+
* fausse. Une valeur qui n'est pas une suite de chiffres fait descendre d'un
|
|
43
|
+
* echelon plutot que de produire un uid inexploitable.
|
|
44
|
+
*/
|
|
45
|
+
function numericOrNull(value) {
|
|
46
|
+
if (typeof value !== 'string' && typeof value !== 'number') return null;
|
|
47
|
+
const text = String(value).trim();
|
|
48
|
+
if (!/^[0-9]+$/.test(text)) return null;
|
|
49
|
+
const parsed = Number(text);
|
|
50
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function nonEmptyString(value) {
|
|
54
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Parse /etc/passwd (format nom:x:uid:gid:gecos:home:shell). POURQUOI cette
|
|
59
|
+
* source : Node n'expose pas de binding getpwnam, et /etc/passwd est la source
|
|
60
|
+
* portable Linux + macOS pour les comptes locaux. Une lecture impossible rend
|
|
61
|
+
* une liste vide — l'appelant descend alors sur le repli conventionnel.
|
|
62
|
+
*/
|
|
63
|
+
function readPasswdEntries(fs, passwdPath) {
|
|
64
|
+
let raw;
|
|
65
|
+
try {
|
|
66
|
+
raw = fs.readFileSync(passwdPath, 'utf8');
|
|
67
|
+
} catch (_e) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
if (typeof raw !== 'string') return [];
|
|
71
|
+
|
|
72
|
+
const entries = [];
|
|
73
|
+
const lines = raw.split('\n');
|
|
74
|
+
for (let i = 0; i < lines.length; i++) {
|
|
75
|
+
const line = lines[i].trim();
|
|
76
|
+
if (line.length === 0 || line.charAt(0) === '#') continue;
|
|
77
|
+
const fields = line.split(':');
|
|
78
|
+
if (fields.length < 6) continue;
|
|
79
|
+
const uid = numericOrNull(fields[2]);
|
|
80
|
+
if (uid === null) continue;
|
|
81
|
+
entries.push({
|
|
82
|
+
name: fields[0],
|
|
83
|
+
uid,
|
|
84
|
+
gid: numericOrNull(fields[3]),
|
|
85
|
+
home: nonEmptyString(fields[5]),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return entries;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function findEntry(entries, { name, uid }) {
|
|
92
|
+
for (let i = 0; i < entries.length; i++) {
|
|
93
|
+
if (name !== null && name !== undefined && entries[i].name === name) return entries[i];
|
|
94
|
+
}
|
|
95
|
+
for (let i = 0; i < entries.length; i++) {
|
|
96
|
+
if (uid !== null && uid !== undefined && entries[i].uid === uid) return entries[i];
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function conventionalHome(name, uid, platform) {
|
|
102
|
+
if (uid === 0 || name === 'root') {
|
|
103
|
+
return ROOT_HOME[platform] || ROOT_HOME.linux;
|
|
104
|
+
}
|
|
105
|
+
if (name === null || name === undefined) return null;
|
|
106
|
+
return platform === 'darwin' ? '/Users/' + name : '/home/' + name;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Coeur de la resolution nom + home. Rend une forme unique, dans tous les cas :
|
|
111
|
+
* { name, uid, gid, home, homeSource }.
|
|
112
|
+
*
|
|
113
|
+
* homeSource nomme la source retenue, pour que le rapport final puisse la citer :
|
|
114
|
+
* 'passwd' — ligne trouvee dans /etc/passwd
|
|
115
|
+
* 'convention' — /home/<nom>, /Users/<nom>, ou le home de root
|
|
116
|
+
* 'env' — variable d'environnement (USERPROFILE) ou identite du
|
|
117
|
+
* processus courant (os.userInfo)
|
|
118
|
+
* 'unknown' — aucune des trois n'a pu conclure ; home vaut null. Ce n'est
|
|
119
|
+
* pas un repli, c'est l'absence honnete d'un resultat.
|
|
120
|
+
*/
|
|
121
|
+
function resolveHomeDetails({ name, uid, fs, platform, env, passwdPath }) {
|
|
122
|
+
if (platform === 'win32') {
|
|
123
|
+
const winHome = nonEmptyString(env && env.USERPROFILE);
|
|
124
|
+
return {
|
|
125
|
+
name: name || nonEmptyString(env && env.USERNAME),
|
|
126
|
+
uid: null,
|
|
127
|
+
gid: null,
|
|
128
|
+
home: winHome,
|
|
129
|
+
homeSource: winHome ? 'env' : 'unknown',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const entries = readPasswdEntries(fs, passwdPath);
|
|
134
|
+
const entry = findEntry(entries, { name, uid });
|
|
135
|
+
|
|
136
|
+
if (entry && entry.home) {
|
|
137
|
+
return {
|
|
138
|
+
name: entry.name,
|
|
139
|
+
uid: uid === null || uid === undefined ? entry.uid : uid,
|
|
140
|
+
gid: entry.gid,
|
|
141
|
+
home: entry.home,
|
|
142
|
+
homeSource: 'passwd',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const resolvedName = name || (entry ? entry.name : null) || (uid === 0 ? 'root' : null);
|
|
147
|
+
const home = conventionalHome(resolvedName, uid === undefined ? null : uid, platform);
|
|
148
|
+
return {
|
|
149
|
+
name: resolvedName,
|
|
150
|
+
uid: uid === null || uid === undefined ? (entry ? entry.uid : null) : uid,
|
|
151
|
+
gid: entry ? entry.gid : null,
|
|
152
|
+
home,
|
|
153
|
+
homeSource: home ? 'convention' : 'unknown',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Morceau reutilisable : resoudre le home d'un compte donne par son nom ou son
|
|
159
|
+
* uid. Une suite de chiffres est lue comme un uid — sur le chemin d'elevation,
|
|
160
|
+
* les sources numeriques (SUDO_UID, PKEXEC_UID) sont les seules a porter un uid.
|
|
161
|
+
*/
|
|
162
|
+
function resolveHomeFor(nameOrUid, {
|
|
163
|
+
fs = nodeFs,
|
|
164
|
+
platform = process.platform,
|
|
165
|
+
env = process.env,
|
|
166
|
+
passwdPath = PASSWD_PATH,
|
|
167
|
+
} = {}) {
|
|
168
|
+
let name = null;
|
|
169
|
+
let uid = null;
|
|
170
|
+
|
|
171
|
+
if (typeof nameOrUid === 'number') {
|
|
172
|
+
uid = Number.isFinite(nameOrUid) ? nameOrUid : null;
|
|
173
|
+
} else if (typeof nameOrUid === 'string') {
|
|
174
|
+
const asNumber = numericOrNull(nameOrUid);
|
|
175
|
+
if (asNumber === null) name = nonEmptyString(nameOrUid);
|
|
176
|
+
else uid = asNumber;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return resolveHomeDetails({ name, uid, fs, platform, env, passwdPath });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function safeUserInfo(userInfo) {
|
|
183
|
+
if (typeof userInfo !== 'function') return null;
|
|
184
|
+
try {
|
|
185
|
+
const info = userInfo();
|
|
186
|
+
return info && typeof info === 'object' ? info : null;
|
|
187
|
+
} catch (_e) {
|
|
188
|
+
// POURQUOI avaler : os.userInfo throw quand le compte du processus n'a pas
|
|
189
|
+
// d'entree passwd (conteneur avec un uid arbitraire). L'absence d'identite
|
|
190
|
+
// se traite comme un echelon qui ne conclut pas.
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function ownerUidOf(fs, cwd) {
|
|
196
|
+
try {
|
|
197
|
+
const stat = fs.statSync(cwd);
|
|
198
|
+
return stat && typeof stat.uid === 'number' ? stat.uid : null;
|
|
199
|
+
} catch (_e) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* resolveTargetUser — rend { uid, gid, name, home, homeSource, source, elevated }.
|
|
206
|
+
* Fonction pure : rien n'est ecrit, l'environnement fourni n'est pas modifie.
|
|
207
|
+
*/
|
|
208
|
+
function resolveTargetUser({
|
|
209
|
+
env = process.env,
|
|
210
|
+
platform = process.platform,
|
|
211
|
+
fs = nodeFs,
|
|
212
|
+
userInfo = os.userInfo,
|
|
213
|
+
cwd = process.cwd(),
|
|
214
|
+
geteuid = typeof process.geteuid === 'function' ? process.geteuid.bind(process) : null,
|
|
215
|
+
passwdPath = PASSWD_PATH,
|
|
216
|
+
} = {}) {
|
|
217
|
+
const safeEnv = env || {};
|
|
218
|
+
|
|
219
|
+
// Windows n'a pas d'uid/gid, et une elevation UAC ne laisse aucune variable
|
|
220
|
+
// d'environnement exploitable — on ne l'invente pas.
|
|
221
|
+
if (platform === 'win32') {
|
|
222
|
+
const details = resolveHomeDetails({
|
|
223
|
+
name: nonEmptyString(safeEnv.USERNAME),
|
|
224
|
+
uid: null,
|
|
225
|
+
fs,
|
|
226
|
+
platform,
|
|
227
|
+
env: safeEnv,
|
|
228
|
+
passwdPath,
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
uid: null,
|
|
232
|
+
gid: null,
|
|
233
|
+
name: details.name,
|
|
234
|
+
home: details.home,
|
|
235
|
+
homeSource: details.homeSource,
|
|
236
|
+
source: 'self',
|
|
237
|
+
elevated: false,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// L'ELEVATION EST UN FAIT, PAS UNE VARIABLE D'ENVIRONNEMENT.
|
|
242
|
+
//
|
|
243
|
+
// Les trois echelons ci-dessous lisaient SUDO_UID, DOAS_USER ou PKEXEC_UID et
|
|
244
|
+
// annoncaient elevated:true sans regarder l'uid effectif. Or ces variables
|
|
245
|
+
// survivent dans l'environnement d'un shell lance depuis une session elevee
|
|
246
|
+
// puis redescendue : le processus n'est plus root, mais il se declarait
|
|
247
|
+
// eleve. L'appelant lancait alors une reprise de droits qui ne pouvait que
|
|
248
|
+
// se faire refuser. On lit donc l'uid effectif AVANT de trancher.
|
|
249
|
+
const uidEffectif = typeof geteuid === 'function' ? geteuid() : null;
|
|
250
|
+
const estRoot = uidEffectif === 0;
|
|
251
|
+
|
|
252
|
+
const sudoUid = numericOrNull(safeEnv.SUDO_UID);
|
|
253
|
+
if (sudoUid !== null) {
|
|
254
|
+
const name = nonEmptyString(safeEnv.SUDO_USER);
|
|
255
|
+
const details = resolveHomeDetails({ name, uid: sudoUid, fs, platform, env: safeEnv, passwdPath });
|
|
256
|
+
const sudoGid = numericOrNull(safeEnv.SUDO_GID);
|
|
257
|
+
return {
|
|
258
|
+
uid: sudoUid,
|
|
259
|
+
gid: sudoGid !== null ? sudoGid : details.gid,
|
|
260
|
+
name: name || details.name,
|
|
261
|
+
home: details.home,
|
|
262
|
+
homeSource: details.homeSource,
|
|
263
|
+
source: 'sudo',
|
|
264
|
+
elevated: estRoot,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const doasUser = nonEmptyString(safeEnv.DOAS_USER);
|
|
269
|
+
if (doasUser !== null) {
|
|
270
|
+
// doas ne pose pas d'uid : le nom est la seule prise, l'uid vient de passwd.
|
|
271
|
+
const details = resolveHomeDetails({ name: doasUser, uid: null, fs, platform, env: safeEnv, passwdPath });
|
|
272
|
+
return {
|
|
273
|
+
uid: details.uid,
|
|
274
|
+
gid: details.gid,
|
|
275
|
+
name: doasUser,
|
|
276
|
+
home: details.home,
|
|
277
|
+
homeSource: details.homeSource,
|
|
278
|
+
source: 'doas',
|
|
279
|
+
elevated: estRoot,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const pkexecUid = numericOrNull(safeEnv.PKEXEC_UID);
|
|
284
|
+
if (pkexecUid !== null) {
|
|
285
|
+
const details = resolveHomeDetails({ name: null, uid: pkexecUid, fs, platform, env: safeEnv, passwdPath });
|
|
286
|
+
return {
|
|
287
|
+
uid: pkexecUid,
|
|
288
|
+
gid: details.gid,
|
|
289
|
+
name: details.name,
|
|
290
|
+
home: details.home,
|
|
291
|
+
homeSource: details.homeSource,
|
|
292
|
+
source: 'pkexec',
|
|
293
|
+
elevated: estRoot,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (estRoot) {
|
|
298
|
+
const ownerUid = ownerUidOf(fs, cwd);
|
|
299
|
+
|
|
300
|
+
// POURQUOI ce repli : su ne laisse aucune variable derriere lui. Le
|
|
301
|
+
// proprietaire du dossier de travail est alors la meilleure trace de
|
|
302
|
+
// l'humain a qui appartient le projet.
|
|
303
|
+
if (ownerUid !== null && ownerUid !== 0) {
|
|
304
|
+
const details = resolveHomeDetails({ name: null, uid: ownerUid, fs, platform, env: safeEnv, passwdPath });
|
|
305
|
+
return {
|
|
306
|
+
uid: ownerUid,
|
|
307
|
+
gid: details.gid,
|
|
308
|
+
name: details.name,
|
|
309
|
+
home: details.home,
|
|
310
|
+
homeSource: details.homeSource,
|
|
311
|
+
source: 'cwd-owner',
|
|
312
|
+
elevated: true,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Le dossier de travail appartient a root, ou son stat a echoue : la cible
|
|
317
|
+
// EST root. Resultat de premiere classe, distinguable par source === 'root'.
|
|
318
|
+
const rootDetails = resolveHomeDetails({ name: 'root', uid: 0, fs, platform, env: safeEnv, passwdPath });
|
|
319
|
+
return {
|
|
320
|
+
uid: 0,
|
|
321
|
+
gid: rootDetails.gid !== null ? rootDetails.gid : 0,
|
|
322
|
+
name: rootDetails.name || 'root',
|
|
323
|
+
home: rootDetails.home,
|
|
324
|
+
homeSource: rootDetails.homeSource,
|
|
325
|
+
source: 'root',
|
|
326
|
+
elevated: true,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const info = safeUserInfo(userInfo);
|
|
331
|
+
const selfUid = info && typeof info.uid === 'number' ? info.uid : uidEffectif;
|
|
332
|
+
const selfName = (info && nonEmptyString(info.username))
|
|
333
|
+
|| nonEmptyString(safeEnv.USER)
|
|
334
|
+
|| nonEmptyString(safeEnv.LOGNAME);
|
|
335
|
+
// $HOME PRIME SUR passwd QUAND ON N'EST PAS ELEVE.
|
|
336
|
+
//
|
|
337
|
+
// os.userInfo().homedir lit /etc/passwd. Sans elevation, l'environnement
|
|
338
|
+
// appartient a l'utilisateur lui-meme : s'il a deplace son HOME (compte de
|
|
339
|
+
// service, conteneur de developpement, home monte ailleurs), c'est $HOME qui
|
|
340
|
+
// dit la verite et passwd qui ment. Le resolveur de binaire applique deja
|
|
341
|
+
// cette regle dans safeHome ; les deux divergeaient. Sous elevation on ne
|
|
342
|
+
// touche pas a ca : $HOME y appartient a root, pas a la cible.
|
|
343
|
+
const selfHome = nonEmptyString(safeEnv.HOME) || (info && nonEmptyString(info.homedir));
|
|
344
|
+
|
|
345
|
+
if (selfHome) {
|
|
346
|
+
return {
|
|
347
|
+
uid: selfUid,
|
|
348
|
+
gid: info && typeof info.gid === 'number' ? info.gid : null,
|
|
349
|
+
name: selfName,
|
|
350
|
+
home: selfHome,
|
|
351
|
+
homeSource: 'env',
|
|
352
|
+
source: 'self',
|
|
353
|
+
elevated: false,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const details = resolveHomeDetails({ name: selfName, uid: selfUid, fs, platform, env: safeEnv, passwdPath });
|
|
358
|
+
return {
|
|
359
|
+
uid: selfUid !== null && selfUid !== undefined ? selfUid : details.uid,
|
|
360
|
+
gid: info && typeof info.gid === 'number' ? info.gid : details.gid,
|
|
361
|
+
name: selfName || details.name,
|
|
362
|
+
home: details.home,
|
|
363
|
+
homeSource: details.homeSource,
|
|
364
|
+
source: 'self',
|
|
365
|
+
elevated: false,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
module.exports = {
|
|
370
|
+
resolveTargetUser,
|
|
371
|
+
resolveHomeFor,
|
|
372
|
+
PASSWD_PATH,
|
|
373
|
+
};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const { execSync, spawn } = require('child_process');
|
|
11
11
|
const logger = require('../utils/logger');
|
|
12
|
+
const { commandExists } = require('../resolve-binary');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* @typedef {Object} LaunchOptions
|
|
@@ -64,14 +65,11 @@ const LAUNCH_CONFIGS = {
|
|
|
64
65
|
|
|
65
66
|
return args;
|
|
66
67
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
68
|
+
// Meme resolveur que le moteur d'installation. Avant, cette sonde lancait
|
|
69
|
+
// `which claude` : absente de Windows, et fausse des que le processus
|
|
70
|
+
// n'herite pas du PATH de l'utilisateur. Le commentaire en tete de
|
|
71
|
+
// webui-detection.test.js documente la panne que cette divergence a produite.
|
|
72
|
+
checkAvailable: () => commandExists('claude'),
|
|
75
73
|
},
|
|
76
74
|
|
|
77
75
|
'codex': {
|
|
@@ -98,14 +96,7 @@ const LAUNCH_CONFIGS = {
|
|
|
98
96
|
|
|
99
97
|
return args;
|
|
100
98
|
},
|
|
101
|
-
checkAvailable: () =>
|
|
102
|
-
try {
|
|
103
|
-
execSync('which codex', { stdio: 'ignore' });
|
|
104
|
-
return true;
|
|
105
|
-
} catch {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
99
|
+
checkAvailable: () => commandExists('codex'),
|
|
109
100
|
}
|
|
110
101
|
};
|
|
111
102
|
|
package/install/package.json
CHANGED
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
const inquirer = require('inquirer');
|
|
9
9
|
const chalk = require('chalk');
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// La valeur proposee par defaut vient de la source unique (install/lib/
|
|
12
|
+
// api-defaults.js). Elle valait localhost:3737 : la question proposait donc a
|
|
13
|
+
// l'utilisateur de pointer sur sa propre machine, ou byan_web ne tourne pas.
|
|
14
|
+
// Chargement par chemin relatif, comme le reste des liens entre install/lib et
|
|
15
|
+
// ce paquet — la dependance file: avait produit le defaut d'empaquetage
|
|
16
|
+
// corrige en 2.59.3.
|
|
17
|
+
const { PROD_API_URL: DEFAULT_API_URL } = require('../../../lib/api-defaults');
|
|
12
18
|
const ENV_KEYS = ['BYAN_API_TOKEN', 'BYAN_API_URL'];
|
|
13
19
|
const LEANTIME_ENV_KEYS = [
|
|
14
20
|
'LEANTIME_API_URL',
|
package/install/src/webui/api.js
CHANGED
|
@@ -216,11 +216,24 @@ const routes = {
|
|
|
216
216
|
ask: null,
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
+
// La face web recoit la MEME structure que le terminal, etapes sautees
|
|
220
|
+
// comprises. Sans elles, l'assistant continuerait d'annoncer une reussite
|
|
221
|
+
// sur un perimetre reduit en silence.
|
|
222
|
+
const sautees = result.skipped || [];
|
|
223
|
+
const messageOk = sautees.length
|
|
224
|
+
? `Installation BYAN terminee — ${sautees.length} etape(s) sautee(s)`
|
|
225
|
+
: 'Installation BYAN terminee';
|
|
219
226
|
server.broadcastComplete(result.ok, {
|
|
220
|
-
message: result.ok ?
|
|
227
|
+
message: result.ok ? messageOk : 'Installation terminee avec des etapes en echec',
|
|
221
228
|
projectRoot,
|
|
222
229
|
verify: result.verify,
|
|
223
230
|
steps: result.steps,
|
|
231
|
+
skipped: sautees,
|
|
232
|
+
// La page de fin affiche ces deux-la : sans eux, elle ne pouvait pas
|
|
233
|
+
// dire au nom de qui l'installation a tourne ni si les droits ont pu
|
|
234
|
+
// etre repris.
|
|
235
|
+
targetUser: result.targetUser,
|
|
236
|
+
ownership: result.ownership,
|
|
224
237
|
launch: result.launch,
|
|
225
238
|
});
|
|
226
239
|
} catch (err) {
|
|
@@ -449,6 +449,45 @@ class ByanApp {
|
|
|
449
449
|
if (s.projectRoot) html += `<p><strong>Projet :</strong> <code>${this.escapeHtml(s.projectRoot)}</code></p>`;
|
|
450
450
|
if (s.mode) html += `<p><strong>Mode :</strong> ${this.escapeHtml(s.mode)}</p>`;
|
|
451
451
|
if (s.platforms && s.platforms.length) html += `<p><strong>Plateformes :</strong> ${s.platforms.map(p => this.escapeHtml(p)).join(', ')}</p>`;
|
|
452
|
+
|
|
453
|
+
// CE QUI N'A PAS TOURNE SE DIT ICI AUSSI.
|
|
454
|
+
//
|
|
455
|
+
// Le moteur diffuse `skipped`, `verify.skipped` et `ownership` depuis la
|
|
456
|
+
// correction du rapport honnete, mais cette page n'en affichait rien :
|
|
457
|
+
// l'assistant web continuait d'annoncer une reussite pleine sur un
|
|
458
|
+
// perimetre reduit, exactement le defaut corrige cote terminal.
|
|
459
|
+
if (s.verify && typeof s.verify.passed === 'number') {
|
|
460
|
+
html += `<p><strong>Verification :</strong> ${s.verify.passed}/${s.verify.total} controles`;
|
|
461
|
+
if (s.verify.skipped && s.verify.skipped.length) {
|
|
462
|
+
html += ` — ${s.verify.skipped.length} saute(s) sur ${s.verify.intendedTotal || '?'}`;
|
|
463
|
+
}
|
|
464
|
+
html += '</p>';
|
|
465
|
+
}
|
|
466
|
+
if (s.skipped && s.skipped.length) {
|
|
467
|
+
html += '<p><strong>Etapes sautees :</strong></p><ul>';
|
|
468
|
+
for (const e of s.skipped) {
|
|
469
|
+
html += `<li><code>${this.escapeHtml(e.id)}</code> — ${this.escapeHtml(e.reason || 'sans raison donnee')}</li>`;
|
|
470
|
+
}
|
|
471
|
+
html += '</ul>';
|
|
472
|
+
}
|
|
473
|
+
if (s.targetUser && s.targetUser.elevated) {
|
|
474
|
+
const qui = s.targetUser.name || s.targetUser.uid;
|
|
475
|
+
html += `<p><strong>Utilisateur cible :</strong> ${this.escapeHtml(String(qui))} (${this.escapeHtml(s.targetUser.source || 'inconnu')})</p>`;
|
|
476
|
+
}
|
|
477
|
+
if (s.ownership) {
|
|
478
|
+
const issue = s.ownership.overall || s.ownership.outcome;
|
|
479
|
+
const couleur = issue === 'failed' ? 'var(--error)' : 'inherit';
|
|
480
|
+
html += `<p style="color:${couleur}"><strong>Droits :</strong> ${this.escapeHtml(String(issue))}`;
|
|
481
|
+
if (s.ownership.changed) html += ` — ${s.ownership.changed} entree(s) reprises`;
|
|
482
|
+
html += '</p>';
|
|
483
|
+
for (const p of s.ownership.homeFailed || []) {
|
|
484
|
+
html += `<p style="color:var(--error)">Echec de reprise sur <code>${this.escapeHtml(p)}</code></p>`;
|
|
485
|
+
}
|
|
486
|
+
if (s.ownership.homeSkipped) {
|
|
487
|
+
html += `<p style="color:var(--text-muted)">${this.escapeHtml(s.ownership.homeSkipped)}</p>`;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
452
491
|
html += '<p style="margin-top:1rem;color:var(--text-muted);">Tu peux fermer cette fenetre.</p>';
|
|
453
492
|
summary.innerHTML = html;
|
|
454
493
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-byan-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.60.0",
|
|
4
4
|
"description": "BYAN v2.8 - Intelligent AI agent creator with ELO trust system + scientific fact-check + Hermes universal dispatcher + native Claude Code integration (hooks, skills, MCP server). Multi-platform (Claude Code, Codex). Merise Agile + TDD + 71 Mantras. ~54% LLM cost savings.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -79,7 +79,6 @@
|
|
|
79
79
|
"@anthropic-ai/claude-code": "^2.1.114",
|
|
80
80
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
81
81
|
"better-sqlite3": "^12.9.0",
|
|
82
|
-
"byan-platform-config": "file:./install/packages/platform-config",
|
|
83
82
|
"chalk": "^4.1.2",
|
|
84
83
|
"commander": "^11.1.0",
|
|
85
84
|
"fs-extra": "^11.2.0",
|
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
mcpConfig: { readMcpConfig, ensureMcpConfig, TOKEN_PLACEHOLDER },
|
|
17
17
|
envConfig: { readEnvToken, updateDotenv },
|
|
18
18
|
urlUtils: { stripApiSuffix },
|
|
19
|
-
} = require('
|
|
19
|
+
} = require('../../install/packages/platform-config');
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* @param {string} projectRoot — absolute path to the project root
|