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.
@@ -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
- checkAvailable: () => {
68
- try {
69
- execSync('which claude', { stdio: 'ignore' });
70
- return true;
71
- } catch {
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "2.51.0",
3
+ "version": "2.60.0",
4
4
  "description": "BYAN - Intelligent AI agent installer with multi-platform native support (Claude Code, Codex/OpenCode)",
5
5
  "bin": {
6
6
  "create-byan-agent": "bin/create-byan-agent-v2.js"
@@ -8,7 +8,13 @@
8
8
  const inquirer = require('inquirer');
9
9
  const chalk = require('chalk');
10
10
 
11
- const DEFAULT_API_URL = 'http://localhost:3737';
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',
@@ -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 ? 'Installation BYAN terminee' : 'Installation terminee avec des etapes en echec',
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.59.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('byan-platform-config');
19
+ } = require('../../install/packages/platform-config');
20
20
 
21
21
  /**
22
22
  * @param {string} projectRoot — absolute path to the project root
@@ -26,7 +26,6 @@
26
26
  "author": "Yan",
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
- "byan-platform-config": "file:../install/packages/platform-config",
30
29
  "chalk": "^4.1.2",
31
30
  "commander": "^11.1.0",
32
31
  "diff": "^5.1.0",