create-byan-agent 2.54.0 → 2.58.1
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/CHANGELOG.md +142 -0
- package/README.md +27 -11
- package/install/bin/create-byan-agent-v2.js +155 -14
- package/install/lib/desktop-app.js +58 -0
- package/install/lib/global-skills-sync.js +118 -0
- package/install/lib/home-credentials.js +91 -0
- package/install/lib/install-engine.js +297 -0
- package/install/lib/install-mode.js +37 -0
- package/install/lib/yanstaller/updater.js +52 -3
- package/install/src/webui/api.js +70 -48
- package/install/src/webui/public/app.js +15 -1
- package/install/src/webui/server.js +6 -1
- package/install/templates/.claude/hooks/lib/plain-language.js +19 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-sync.js +8 -2
- package/install/templates/docs/intelligent-dispatch.md +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,148 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [2.58.1] - 2026-07-21
|
|
13
|
+
|
|
14
|
+
### Fixed — l'install auto en terminal gelait au controle des copies globales de skills
|
|
15
|
+
- Symptome : `create-byan-agent --cli` restait bloque en chargement infini sur
|
|
16
|
+
`[6/8] Controle des copies globales de skills` quand des copies globales
|
|
17
|
+
`~/.claude/skills` divergeaient.
|
|
18
|
+
- Cause : l'etape passait une question interactive (`inquirer`) au moteur, mais
|
|
19
|
+
l'install auto garde un indicateur de progression (`ora`) qui tourne pendant
|
|
20
|
+
chaque etape. La question s'affichait SOUS l'indicateur : invisible, en attente
|
|
21
|
+
d'une saisie que l'utilisateur ne voyait pas -> gel apparent.
|
|
22
|
+
- Correctif : l'install automatique n'ouvre plus de question interactive. Par
|
|
23
|
+
defaut, elle SIGNALE les copies divergentes et affiche la commande de
|
|
24
|
+
synchronisation exacte, sans rien ecrire dans `~/.claude` et sans bloquer
|
|
25
|
+
(ligne rouge conservee : pas d'ecriture silencieuse dans le home). Nouveau
|
|
26
|
+
drapeau `--sync-skills` pour synchroniser sans demander (le drapeau EST le
|
|
27
|
+
consentement explicite), en approbation automatique sans prompt.
|
|
28
|
+
- Nouvelle fonction pure `skillsSyncConsent` dans `install/lib/install-mode.js`,
|
|
29
|
+
testee (`install/__tests__/install-mode.test.js`) ; preuve de non-blocage des
|
|
30
|
+
deux branches (signalement seul / synchro opt-in) sur une maquette de
|
|
31
|
+
divergence.
|
|
32
|
+
|
|
33
|
+
## [2.58.0] - 2026-07-21
|
|
34
|
+
|
|
35
|
+
### Added — l'assistant par defaut ouvre l'app Desktop si elle est installee
|
|
36
|
+
- `create-byan-agent` (sans argument) tente d'abord d'ouvrir l'app Desktop BYAN
|
|
37
|
+
dans une fenetre native ; si aucune app n'est detectee, il ouvre le meme
|
|
38
|
+
assistant dans le navigateur. Les deux visages partagent le meme serveur webui
|
|
39
|
+
— seule la fenetre change. Aucun blocage quand l'app n'est pas la.
|
|
40
|
+
- Detection par emplacements d'installation concrets (electron-builder,
|
|
41
|
+
productName BYAN) : `/opt/BYAN/byan`, `/usr/bin/byan`, `/usr/local/bin/byan`
|
|
42
|
+
(Linux) ; `BYAN.app` (macOS, systeme et `~/Applications`) ; `BYAN.exe` sous
|
|
43
|
+
`Programs`/`PROGRAMFILES` (Windows). La variable `BYAN_DESKTOP_BIN` surcharge
|
|
44
|
+
pour une AppImage ou un chemin personnalise. Pas de recherche d'un `byan` nu
|
|
45
|
+
sur le PATH (trop de faux positifs).
|
|
46
|
+
- `create-byan-agent web` force desormais le navigateur (echappatoire explicite,
|
|
47
|
+
ignore l'app Desktop).
|
|
48
|
+
- Nouveau module pur `install/lib/desktop-app.js` (`detectDesktopApp`,
|
|
49
|
+
`candidatePaths`), injectable (platform/env/homedir/existsSync) et teste
|
|
50
|
+
(`install/__tests__/desktop-app.test.js`, 7 cas couvrant Linux/macOS/Windows,
|
|
51
|
+
l'override et le repli).
|
|
52
|
+
- Note : les binaires de l'app Desktop ne sont pas encore distribues ; la
|
|
53
|
+
detection tombe donc sur le navigateur aujourd'hui, par conception. Le vrai
|
|
54
|
+
installateur (distribution des binaires) est un chantier separe a venir.
|
|
55
|
+
|
|
56
|
+
## [2.57.0] - 2026-07-21
|
|
57
|
+
|
|
58
|
+
### Changed — l'assistant web devient l'installation par defaut
|
|
59
|
+
- `create-byan-agent` (sans argument) lance desormais l'assistant graphique dans
|
|
60
|
+
le navigateur : il demarre le serveur local (lie a `127.0.0.1`) et ouvre la
|
|
61
|
+
page d'installation. C'est l'inversion demandee — le graphique par defaut, le
|
|
62
|
+
terminal en option.
|
|
63
|
+
- Les deux chemins terminal restent accessibles par drapeau :
|
|
64
|
+
- `--cli` : installation automatique en terminal, zero question (le comportement
|
|
65
|
+
qui etait le defaut en 2.56.0).
|
|
66
|
+
- `--legacy` : interview complet d'origine, question par question.
|
|
67
|
+
- `create-byan-agent web` conserve son role d'alias explicite de l'action par
|
|
68
|
+
defaut ; il accepte `--port` et `--dir`.
|
|
69
|
+
- Nouveau module pur `install/lib/install-mode.js` (`chooseInstallMode`) : la
|
|
70
|
+
decision de routage (web / cli / legacy) est isolee et testee
|
|
71
|
+
(`install/__tests__/install-mode.test.js`, 5 cas). `--legacy` prime sur `--cli`
|
|
72
|
+
si les deux sont passes.
|
|
73
|
+
|
|
74
|
+
## [2.56.0] - 2026-07-21
|
|
75
|
+
|
|
76
|
+
### Changed — installation refondue : un moteur, deux visages
|
|
77
|
+
- Nouveau moteur d'installation `install/lib/install-engine.js` : options ->
|
|
78
|
+
actions reelles (detection, copie `_byan` + `.claude`, configurations natives
|
|
79
|
+
Claude/Codex, dependances MCP, controle des copies globales de skills,
|
|
80
|
+
verification finale). La progression est honnete par construction : un
|
|
81
|
+
evenement d'etape = une action reellement executee. Defauts sans question :
|
|
82
|
+
Claude + Codex quand detectes, tous les agents, soul createur, rtk installe
|
|
83
|
+
s'il manque, configuration memorisee reutilisee. Nom du projet et repertoire
|
|
84
|
+
cible en options. Teste sur fixtures disque (7 cas).
|
|
85
|
+
- `npx create-byan-agent` = installation automatique directe (plus d'interview).
|
|
86
|
+
Options : `--name`, `--dir`, `--no-launch`, `--no-rtk` ; l'ancienne interview
|
|
87
|
+
reste accessible via `--legacy`. En fin d'installation, Claude Code est lance
|
|
88
|
+
(canal byan-channel quand la version du CLI le supporte, sinon `claude`
|
|
89
|
+
normal ; commande affichee en terminal non interactif).
|
|
90
|
+
- WebUI d'installation branchee sur le MEME moteur : les boutons installer et
|
|
91
|
+
mettre a jour executaient une simulation (etapes diffusees autour de pauses,
|
|
92
|
+
echec de l'updater avale puis succes affiche) — ils executent desormais le
|
|
93
|
+
moteur et l'updater reels, la progression WebSocket relaie chaque etape
|
|
94
|
+
executee, et l'ecran d'options porte nom du projet + repertoire. 3 tests par
|
|
95
|
+
injection d'un moteur factice.
|
|
96
|
+
- Memoire d'installation dans le home : `install/lib/home-credentials.js`
|
|
97
|
+
(`~/.byan/credentials.json`, memes cles que le resolveur du serveur MCP,
|
|
98
|
+
fichier en mode 600, fusion sans effacement de secret). L'installation
|
|
99
|
+
reutilise ce qui est memorise et n'exige plus de re-saisie machine par
|
|
100
|
+
machine.
|
|
101
|
+
- Le client Leantime du serveur MCP resout sa config via `resolve-config`
|
|
102
|
+
(env -> `~/.byan/credentials.json`) au lieu de lire seulement
|
|
103
|
+
l'environnement de lancement — le fichier memorise l'atteint enfin. Cadrage
|
|
104
|
+
Google documente (cle de service account memorisee ; OAuth interactif hors
|
|
105
|
+
perimetre serveur).
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## [2.55.0] - 2026-07-21
|
|
109
|
+
|
|
110
|
+
### Added — l'installation propose la synchro des copies globales divergentes
|
|
111
|
+
- Constate sur le terrain : l'installation pose des skills projet a jour, mais
|
|
112
|
+
une copie manuelle homonyme sous `~/.claude/skills/` (plus ancienne) peut
|
|
113
|
+
rester celle que la commande `/<skill>` charge — les nouveautes fraichement
|
|
114
|
+
installees restent invisibles. La garde de fraicheur (2.54.0) le signale a la
|
|
115
|
+
session suivante ; l'installeur, lui, voit la divergence au moment ou il la
|
|
116
|
+
cree et a un terminal pour poser la question.
|
|
117
|
+
- Nouveau module `install/lib/global-skills-sync.js` : en fin d'installation
|
|
118
|
+
Claude, compare les skills fraichement poses avec les copies globales
|
|
119
|
+
homonymes (reutilise `compareSkills` du payload `.claude` livre par le meme
|
|
120
|
+
paquet — zero duplication de la comparaison) et, en terminal interactif,
|
|
121
|
+
propose la synchronisation projet -> global avec consentement explicite.
|
|
122
|
+
Refus ou terminal non interactif : signalement clair + la commande exacte a
|
|
123
|
+
copier ; rien n'est ecrit dans le home sans accord recueilli pendant ce run.
|
|
124
|
+
- Le chemin `update` (corrige en 2.54.1) recoit le meme controle en mode
|
|
125
|
+
signalement : le resultat porte la liste des skills globaux divergents et la
|
|
126
|
+
commande de synchro est affichee.
|
|
127
|
+
- Tests jest : 6 cas (divergence detectee via le comparateur livre, oui ->
|
|
128
|
+
copie effective, non -> rien ecrit, non-interactif -> rien ecrit + commande,
|
|
129
|
+
pas de dossier global -> silencieux, copies identiques -> silencieux).
|
|
130
|
+
|
|
131
|
+
## [2.54.1] - 2026-07-21
|
|
132
|
+
|
|
133
|
+
### Fixed — la commande update ne deployait pas .claude/
|
|
134
|
+
- Cause racine constatee sur le terrain et verifiee dans le code (updater.js
|
|
135
|
+
lignes 30-31, manifest.js : seule la racine `_byan/` etait comparee et
|
|
136
|
+
copiee). Tout ce qui vit sous `.claude/` — les skills (dont le rail
|
|
137
|
+
auto-dispatch 2.53.0), les workflows natifs, les hooks (dont la garde de
|
|
138
|
+
fraicheur 2.54.0), settings.json et le `.mcp.json` regenere par le setup
|
|
139
|
+
natif — restait hors du perimetre de `update`. Un projet pouvait donc etre
|
|
140
|
+
"a jour" avec un `.claude/` vieux de plusieurs mois : rail absent, MCP
|
|
141
|
+
absent, garde absente.
|
|
142
|
+
- Correctif : `update()` rafraichit aussi `.claude/` comme le fait
|
|
143
|
+
l'installation complete — sauvegarde dediee `.claude.backup-<ts>` (prefixe
|
|
144
|
+
distinct de `_byan.backup-` pour ne pas polluer la purge/rollback), copie
|
|
145
|
+
`templates/.claude` -> projet avec ecrasement, puis `setupClaudeNative`
|
|
146
|
+
(regenere `.mcp.json`, installe les dependances du serveur MCP, cable les
|
|
147
|
+
githooks). En cas d'echec, le `.claude/` d'origine est restaure.
|
|
148
|
+
- Testabilite : `options.templateDir` et `options.nativeSetup` injectables ;
|
|
149
|
+
nouveau test fonctionnel `install/__tests__/yanstaller/updater.test.js`
|
|
150
|
+
(4 cas : deploiement du rail, projet sans `.claude`, rollback, deja a jour).
|
|
151
|
+
- La sortie de la commande affiche desormais le rafraichissement `.claude` et
|
|
152
|
+
le chemin de sa sauvegarde.
|
|
153
|
+
|
|
12
154
|
## [2.54.0] - 2026-07-21
|
|
13
155
|
|
|
14
156
|
### Added — Garde de fraicheur des skills (SessionStart)
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ Diversifier les personas elargit la surface de competence collective.
|
|
|
76
76
|
Aucune installation préalable n'est nécessaire. Lancez simplement :
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
#
|
|
79
|
+
# Assistant d'installation graphique dans le navigateur (par défaut)
|
|
80
80
|
npx create-byan-agent
|
|
81
81
|
|
|
82
82
|
# Ou installation globale
|
|
@@ -84,16 +84,32 @@ npm install -g create-byan-agent
|
|
|
84
84
|
create-byan-agent
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
Par défaut, `create-byan-agent` ouvre l'assistant graphique. S'il détecte l'app
|
|
88
|
+
Desktop BYAN installée, il l'ouvre dans une fenêtre native ; sinon il démarre un
|
|
89
|
+
petit serveur local (lié à `127.0.0.1`) et ouvre la page d'installation dans le
|
|
90
|
+
navigateur. Dans les deux cas c'est le même assistant : vous y choisissez le nom
|
|
91
|
+
du projet, le répertoire et les options.
|
|
92
|
+
|
|
93
|
+
Modes selon vos besoins :
|
|
94
|
+
|
|
95
|
+
| Commande | Mode |
|
|
96
|
+
|----------|------|
|
|
97
|
+
| `create-byan-agent` | Assistant graphique : app Desktop si installée, sinon navigateur (par défaut) |
|
|
98
|
+
| `create-byan-agent web` | Force l'assistant dans le navigateur (ignore l'app Desktop) |
|
|
99
|
+
| `create-byan-agent --cli` | Installation automatique en terminal, zéro question (Claude+Codex, tous les agents, rtk) |
|
|
100
|
+
| `create-byan-agent --cli --sync-skills` | Idem, en synchronisant sans demander les copies globales `~/.claude/skills` divergentes |
|
|
101
|
+
| `create-byan-agent --legacy` | Interview complet d'origine, question par question en terminal |
|
|
102
|
+
|
|
103
|
+
En mode `--cli`, si des copies globales de skills sous `~/.claude/skills`
|
|
104
|
+
divergent de l'installation, l'assistant le signale et affiche la commande de
|
|
105
|
+
synchronisation — sans bloquer et sans rien écrire dans votre home. Ajoutez
|
|
106
|
+
`--sync-skills` pour qu'il les synchronise directement.
|
|
107
|
+
|
|
108
|
+
Détection de l'app Desktop : emplacements d'installation standards par système
|
|
109
|
+
(`/opt/BYAN/byan` et `/usr/bin/byan` sous Linux, `BYAN.app` sous macOS,
|
|
110
|
+
`BYAN.exe` sous Windows). Pour une AppImage ou un chemin personnalisé, pointez la
|
|
111
|
+
variable `BYAN_DESKTOP_BIN` sur l'exécutable. Tant que l'app Desktop n'est pas
|
|
112
|
+
installée, l'assistant s'ouvre dans le navigateur — aucun blocage.
|
|
97
113
|
|
|
98
114
|
### Structure du projet après installation
|
|
99
115
|
|
|
@@ -24,6 +24,7 @@ const { setupCodexAutodelegate, DEVICE_FLOW_INSTRUCTION } = require('../lib/code
|
|
|
24
24
|
const { setupMcpExtensions } = require('../lib/mcp-extensions');
|
|
25
25
|
const { setupStagingConsent } = require('../lib/staging-consent');
|
|
26
26
|
const { getLatestVersion, compareVersions } = require('../lib/utils/version-compare');
|
|
27
|
+
const { chooseInstallMode, skillsSyncConsent } = require('../lib/install-mode');
|
|
27
28
|
|
|
28
29
|
// Version source-of-truth is the root package.json (the one npm publishes).
|
|
29
30
|
// install/package.json used to be read here, but it carries an unrelated
|
|
@@ -1320,6 +1321,20 @@ async function install(options = {}) {
|
|
|
1320
1321
|
)
|
|
1321
1322
|
);
|
|
1322
1323
|
}
|
|
1324
|
+
|
|
1325
|
+
// Stale global copies check : a same-named ~/.claude/skills/<n> that
|
|
1326
|
+
// diverges from what was JUST installed can be the copy the slash command
|
|
1327
|
+
// loads — the fresh features would stay invisible. Ask (TTY) or notice
|
|
1328
|
+
// (non-TTY) ; no silent write into the user's home, ever.
|
|
1329
|
+
try {
|
|
1330
|
+
const { offerGlobalSkillsSync } = require('../lib/global-skills-sync');
|
|
1331
|
+
const ask = process.stdout.isTTY
|
|
1332
|
+
? async (message) => (await inquirer.prompt([{ type: 'confirm', name: 'ok', message, default: true }])).ok
|
|
1333
|
+
: null;
|
|
1334
|
+
await offerGlobalSkillsSync(projectRoot, templateDir, { ask, log: (l) => console.log(chalk.yellow(l)) });
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
console.log(chalk.gray(` [INFO] Global skills check skipped: ${error.message}`));
|
|
1337
|
+
}
|
|
1323
1338
|
}
|
|
1324
1339
|
|
|
1325
1340
|
if (needsCodex) {
|
|
@@ -1887,13 +1902,132 @@ async function install(options = {}) {
|
|
|
1887
1902
|
console.log(chalk.blue('Happy agent building! '));
|
|
1888
1903
|
}
|
|
1889
1904
|
|
|
1905
|
+
// Default path since 2.56.0: the zero-question automatic install running the
|
|
1906
|
+
// shared engine (install/lib/install-engine.js) — the same engine the web
|
|
1907
|
+
// wizard calls. Claude+Codex by default (when detected), every agent, creator
|
|
1908
|
+
// soul, rtk installed when missing, stored home credentials reused. The legacy
|
|
1909
|
+
// ~20-question interview stays reachable behind --legacy only.
|
|
1910
|
+
async function installAuto(options) {
|
|
1911
|
+
const engine = require('../lib/install-engine');
|
|
1912
|
+
const projectRoot = path.resolve(options.dir || process.cwd());
|
|
1913
|
+
|
|
1914
|
+
console.log(chalk.cyan.bold(`\n BYAN ${BYAN_VERSION} — installation automatique (terminal)`));
|
|
1915
|
+
console.log(chalk.gray(` Projet : ${options.name || path.basename(projectRoot)} | Repertoire : ${projectRoot}`));
|
|
1916
|
+
console.log(chalk.gray(' (assistant web par defaut : create-byan-agent ; interview complet : --legacy)\n'));
|
|
1917
|
+
|
|
1918
|
+
let spinner = null;
|
|
1919
|
+
const isTTY = Boolean(process.stdout.isTTY);
|
|
1920
|
+
// The automatic install never opens an interactive prompt: a question drawn
|
|
1921
|
+
// under the live progress spinner froze the terminal at the skills-sync step.
|
|
1922
|
+
// Default is notice-only ; --sync-skills opts in to an unattended sync.
|
|
1923
|
+
const ask = skillsSyncConsent(options);
|
|
1924
|
+
|
|
1925
|
+
try {
|
|
1926
|
+
const result = await engine.runInstall({
|
|
1927
|
+
projectRoot,
|
|
1928
|
+
projectName: options.name || undefined,
|
|
1929
|
+
rtk: options.rtk !== false,
|
|
1930
|
+
}, {
|
|
1931
|
+
onStep: ({ index, total, label }) => {
|
|
1932
|
+
if (spinner) spinner.succeed();
|
|
1933
|
+
spinner = ora(`[${index}/${total}] ${label}`).start();
|
|
1934
|
+
},
|
|
1935
|
+
log: (line) => { if (spinner) spinner.info(chalk.gray(line)).start(); else console.log(chalk.gray(line)); },
|
|
1936
|
+
ask,
|
|
1937
|
+
});
|
|
1938
|
+
if (spinner) spinner.succeed();
|
|
1939
|
+
|
|
1940
|
+
console.log('');
|
|
1941
|
+
console.log(chalk.green.bold(` Installation terminee — verification ${result.verify.passed}/${result.verify.total}`));
|
|
1942
|
+
|
|
1943
|
+
// End-of-install launch (F5): start Claude Code, with the byan-channel
|
|
1944
|
+
// when this CLI version supports it. --no-launch or a non-interactive
|
|
1945
|
+
// terminal prints the command instead of running it.
|
|
1946
|
+
if (result.launch) {
|
|
1947
|
+
if (options.launch !== false && isTTY) {
|
|
1948
|
+
console.log(chalk.cyan(` Lancement de Claude Code : ${result.launch.command}\n`));
|
|
1949
|
+
const { spawnSync } = require('child_process');
|
|
1950
|
+
spawnSync(result.launch.command, { stdio: 'inherit', shell: true });
|
|
1951
|
+
} else {
|
|
1952
|
+
console.log(chalk.gray(` Pour lancer Claude Code : ${result.launch.command}`));
|
|
1953
|
+
}
|
|
1954
|
+
} else {
|
|
1955
|
+
console.log(chalk.gray(' Claude Code introuvable sur cette machine — installe-le puis lance `claude` dans le projet.'));
|
|
1956
|
+
}
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
if (spinner) spinner.fail();
|
|
1959
|
+
console.error(chalk.red.bold('\nInstallation en echec :'));
|
|
1960
|
+
console.error(chalk.red(` ${error.message}`));
|
|
1961
|
+
process.exit(1);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// Default path since 2.58.0: prefer the native Desktop app when it is installed
|
|
1966
|
+
// (option A), otherwise fall back to the browser wizard. Both wrap the SAME
|
|
1967
|
+
// webui server — the only difference is the window (native shell vs browser
|
|
1968
|
+
// tab). When no Desktop app is found, print a one-line discovery tip and open
|
|
1969
|
+
// the browser without blocking. The `web` subcommand skips this and forces the
|
|
1970
|
+
// browser (explicit escape hatch).
|
|
1971
|
+
function launchDesktopOrWeb(options = {}) {
|
|
1972
|
+
const { detectDesktopApp } = require('../lib/desktop-app');
|
|
1973
|
+
const desktop = detectDesktopApp();
|
|
1974
|
+
|
|
1975
|
+
if (desktop.found) {
|
|
1976
|
+
console.log(chalk.cyan.bold(`\n BYAN ${BYAN_VERSION} — ouverture de l app Desktop\n`));
|
|
1977
|
+
console.log(chalk.gray(` ${desktop.bin}`));
|
|
1978
|
+
try {
|
|
1979
|
+
const { spawn } = require('child_process');
|
|
1980
|
+
const child = spawn(desktop.bin, [], { detached: true, stdio: 'ignore' });
|
|
1981
|
+
child.unref();
|
|
1982
|
+
return;
|
|
1983
|
+
} catch (error) {
|
|
1984
|
+
console.log(chalk.yellow(` Echec du lancement de l app Desktop (${error.message}) — repli navigateur.`));
|
|
1985
|
+
// fall through to the browser wizard
|
|
1986
|
+
}
|
|
1987
|
+
} else {
|
|
1988
|
+
console.log(chalk.gray('\n Astuce : l app Desktop BYAN offre une vraie fenetre native. Une fois installee,'));
|
|
1989
|
+
console.log(chalk.gray(' cette commande l ouvrira automatiquement. Pour l instant, j ouvre le navigateur.'));
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
launchWeb(options);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// The browser wizard. Starts the local HTTP + WebSocket server (bound to
|
|
1996
|
+
// loopback) and opens the browser — the server owns the browser launch. Used
|
|
1997
|
+
// as the fallback of launchDesktopOrWeb and as the explicit `web` subcommand.
|
|
1998
|
+
function launchWeb(options = {}) {
|
|
1999
|
+
const ByanWebUI = require('../src/webui/server');
|
|
2000
|
+
const port = parseInt(options.port, 10) || 3000;
|
|
2001
|
+
const projectRoot = path.resolve(options.dir || process.cwd());
|
|
2002
|
+
|
|
2003
|
+
console.log(chalk.cyan.bold(`\n BYAN ${BYAN_VERSION} — assistant d installation (navigateur)\n`));
|
|
2004
|
+
const server = new ByanWebUI({ port, projectRoot });
|
|
2005
|
+
server.start();
|
|
2006
|
+
console.log(chalk.green(` Interface ouverte sur http://localhost:${port}`));
|
|
2007
|
+
console.log(chalk.gray(` Repertoire projet : ${projectRoot}`));
|
|
2008
|
+
console.log(chalk.gray(' Installation en terminal : create-byan-agent --cli | Ctrl+C pour arreter\n'));
|
|
2009
|
+
}
|
|
2010
|
+
|
|
1890
2011
|
// CLI Program
|
|
1891
2012
|
program
|
|
1892
2013
|
.name('create-byan-agent')
|
|
1893
|
-
.description('Install BYAN
|
|
2014
|
+
.description('Install BYAN - Builder of YAN. Default: graphical web wizard. --cli for the terminal install.')
|
|
1894
2015
|
.version(BYAN_VERSION)
|
|
1895
2016
|
.option('--skip-version-check', 'Bypass the npm freshness guard (not recommended)')
|
|
1896
|
-
.
|
|
2017
|
+
.option('--cli', 'Installation automatique en terminal (au lieu de l assistant web par defaut)')
|
|
2018
|
+
.option('-p, --port <port>', 'Port de l assistant web (defaut : 3000)', '3000')
|
|
2019
|
+
.option('--name <name>', 'Nom du projet (defaut : nom du dossier)')
|
|
2020
|
+
.option('--dir <dir>', 'Repertoire d installation (defaut : dossier courant)')
|
|
2021
|
+
.option('--no-launch', 'Ne pas lancer Claude Code en fin d installation (mode --cli)')
|
|
2022
|
+
.option('--no-rtk', 'Ne pas installer rtk automatiquement (mode --cli)')
|
|
2023
|
+
.option('--sync-skills', 'Mode --cli : synchroniser sans demander les copies globales ~/.claude/skills divergentes')
|
|
2024
|
+
.option('--legacy', 'Interview complete d origine (l ancien parcours a questions)')
|
|
2025
|
+
.action(async (options) => {
|
|
2026
|
+
const mode = chooseInstallMode(options);
|
|
2027
|
+
if (mode === 'legacy') return install(options);
|
|
2028
|
+
if (mode === 'cli') return installAuto(options);
|
|
2029
|
+
return launchDesktopOrWeb(options);
|
|
2030
|
+
});
|
|
1897
2031
|
|
|
1898
2032
|
// Update Command (Yanstaller v3)
|
|
1899
2033
|
program
|
|
@@ -1957,6 +2091,20 @@ program
|
|
|
1957
2091
|
if (result.filesSkipped > 0) {
|
|
1958
2092
|
console.log(chalk.yellow(` Files skipped: ${result.filesSkipped} (user-modified)`));
|
|
1959
2093
|
}
|
|
2094
|
+
if (result.claudeRefreshed) {
|
|
2095
|
+
console.log(chalk.green(' Claude Code: .claude/ refreshed (skills, workflows, hooks) + native setup (.mcp.json, MCP deps)'));
|
|
2096
|
+
if (result.claudeBackupPath) {
|
|
2097
|
+
console.log(chalk.gray(` .claude backup: ${path.basename(result.claudeBackupPath)}`));
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
if (result.globalSkillsDiverged && result.globalSkillsDiverged.length > 0) {
|
|
2101
|
+
const { syncCommand } = require('../lib/global-skills-sync');
|
|
2102
|
+
console.log(chalk.yellow(` [!] Copies globales ~/.claude/skills divergentes : ${result.globalSkillsDiverged.join(', ')}`));
|
|
2103
|
+
console.log(chalk.yellow(' Elles peuvent masquer les skills fraichement mis a jour. Pour les synchroniser :'));
|
|
2104
|
+
for (const line of syncCommand(result.globalSkillsDiverged).split('\n')) {
|
|
2105
|
+
console.log(chalk.gray(` ${line}`));
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
1960
2108
|
if (result.backupPath) {
|
|
1961
2109
|
console.log(chalk.gray(` Backup: ${path.basename(result.backupPath)}`));
|
|
1962
2110
|
}
|
|
@@ -2056,20 +2204,13 @@ program
|
|
|
2056
2204
|
}
|
|
2057
2205
|
});
|
|
2058
2206
|
|
|
2207
|
+
// Explicit alias for the default action — same graphical wizard, kept for
|
|
2208
|
+
// discoverability and backward compatibility with `create-byan-agent web`.
|
|
2059
2209
|
program
|
|
2060
2210
|
.command('web')
|
|
2061
|
-
.description('Launch BYAN WebUI installer in the browser')
|
|
2211
|
+
.description('Launch BYAN WebUI installer in the browser (same as the default action)')
|
|
2062
2212
|
.option('-p, --port <port>', 'Port number', '3000')
|
|
2063
|
-
.
|
|
2064
|
-
|
|
2065
|
-
const port = parseInt(options.port, 10);
|
|
2066
|
-
const projectRoot = process.cwd();
|
|
2067
|
-
|
|
2068
|
-
console.log(chalk.cyan.bold('\n BYAN WebUI\n'));
|
|
2069
|
-
const server = new ByanWebUI({ port, projectRoot });
|
|
2070
|
-
server.start();
|
|
2071
|
-
console.log(chalk.green(` Server running at http://localhost:${port}`));
|
|
2072
|
-
console.log(chalk.gray(' Press Ctrl+C to stop\n'));
|
|
2073
|
-
});
|
|
2213
|
+
.option('--dir <dir>', 'Repertoire d installation (defaut : dossier courant)')
|
|
2214
|
+
.action(async (options) => launchWeb(options));
|
|
2074
2215
|
|
|
2075
2216
|
program.parse(process.argv);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
// Detection of an installed BYAN Desktop app (Electron, packaged by
|
|
6
|
+
// electron-builder). productName is "BYAN", appId fr.acadenice.byan. We match
|
|
7
|
+
// CONCRETE executables at the install locations electron-builder produces per
|
|
8
|
+
// OS — NOT a bare `byan` on PATH, which would false-positive on any unrelated
|
|
9
|
+
// binary of that name. The AppImage build has no fixed install path, so it is
|
|
10
|
+
// covered by the BYAN_DESKTOP_BIN override (the user points at their AppImage).
|
|
11
|
+
//
|
|
12
|
+
// Pure and injectable: platform / env / homedir / existsSync are all overridable
|
|
13
|
+
// so the detection is unit-testable without touching the real filesystem.
|
|
14
|
+
|
|
15
|
+
function candidatePaths(platform, env, homedir) {
|
|
16
|
+
const out = [];
|
|
17
|
+
|
|
18
|
+
// Build the TARGET platform's paths, not the host's — so detection is correct
|
|
19
|
+
// and unit-testable regardless of the OS the check runs on.
|
|
20
|
+
const p = platform === 'win32' ? path.win32 : path.posix;
|
|
21
|
+
|
|
22
|
+
// Explicit override always wins (AppImage users, custom install prefixes).
|
|
23
|
+
if (env.BYAN_DESKTOP_BIN) out.push(env.BYAN_DESKTOP_BIN);
|
|
24
|
+
|
|
25
|
+
if (platform === 'darwin') {
|
|
26
|
+
out.push('/Applications/BYAN.app/Contents/MacOS/BYAN');
|
|
27
|
+
out.push(p.join(homedir, 'Applications', 'BYAN.app', 'Contents', 'MacOS', 'BYAN'));
|
|
28
|
+
} else if (platform === 'win32') {
|
|
29
|
+
if (env.LOCALAPPDATA) out.push(p.join(env.LOCALAPPDATA, 'Programs', 'BYAN', 'BYAN.exe'));
|
|
30
|
+
if (env.PROGRAMFILES) out.push(p.join(env.PROGRAMFILES, 'BYAN', 'BYAN.exe'));
|
|
31
|
+
} else {
|
|
32
|
+
// Linux deb/rpm: electron-builder installs to /opt/<productName> and drops
|
|
33
|
+
// a symlink /usr/bin/<executableName> (productName lowercased -> byan).
|
|
34
|
+
out.push('/opt/BYAN/byan');
|
|
35
|
+
out.push('/usr/bin/byan');
|
|
36
|
+
out.push('/usr/local/bin/byan');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function detectDesktopApp(opts = {}) {
|
|
43
|
+
const platform = opts.platform || process.platform;
|
|
44
|
+
const env = opts.env || process.env;
|
|
45
|
+
const homedir = opts.homedir || require('os').homedir();
|
|
46
|
+
const existsSync = opts.existsSync || require('fs').existsSync;
|
|
47
|
+
|
|
48
|
+
for (const candidate of candidatePaths(platform, env, homedir)) {
|
|
49
|
+
if (candidate && existsSync(candidate)) {
|
|
50
|
+
const source = env.BYAN_DESKTOP_BIN === candidate ? 'env' : 'path';
|
|
51
|
+
return { found: true, bin: candidate, source };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { found: false, bin: null, source: null };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { detectDesktopApp, candidatePaths };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GLOBAL SKILLS SYNC — closes the "stale global copy masks the fresh install"
|
|
5
|
+
* trap at INSTALL TIME.
|
|
6
|
+
*
|
|
7
|
+
* Field failure (2026-07-21): the installer laid a fully up-to-date
|
|
8
|
+
* .claude/skills/ in the project, but the user's manual copies under
|
|
9
|
+
* ~/.claude/skills/ (same names, months old) kept being the ones the slash
|
|
10
|
+
* command loaded — the freshly installed features stayed invisible. The
|
|
11
|
+
* session-start freshness hook flags this at the NEXT session; the installer
|
|
12
|
+
* can flag it at the very moment it creates the situation, and it has a TTY to
|
|
13
|
+
* ask. Red line kept from the hook: no silent write into the user's home —
|
|
14
|
+
* consent is asked, refusal or a non-interactive terminal degrades to a clear
|
|
15
|
+
* notice with the exact command.
|
|
16
|
+
*
|
|
17
|
+
* The comparison core is NOT duplicated: we require compareSkills from the
|
|
18
|
+
* .claude payload this very package ships (templates/.claude/hooks/lib/
|
|
19
|
+
* skill-freshness.js), the same tested module the session hook uses.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs-extra');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
function loadComparator(templateDir) {
|
|
27
|
+
return require(path.join(templateDir, '.claude', 'hooks', 'lib', 'skill-freshness.js'));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Compare the project's freshly installed skills with same-named global copies.
|
|
32
|
+
* Silent-safe: any failure (no global dir, unreadable comparator) yields an
|
|
33
|
+
* empty result — this check must never break an install.
|
|
34
|
+
*
|
|
35
|
+
* @returns {Promise<{diverged: string[], checked: number}>}
|
|
36
|
+
*/
|
|
37
|
+
async function checkGlobalSkills(projectRoot, templateDir, { homeDir = os.homedir() } = {}) {
|
|
38
|
+
try {
|
|
39
|
+
const globalSkillsDir = path.join(homeDir, '.claude', 'skills');
|
|
40
|
+
if (!await fs.pathExists(globalSkillsDir)) return { diverged: [], checked: 0 };
|
|
41
|
+
const { compareSkills } = loadComparator(templateDir);
|
|
42
|
+
return compareSkills({
|
|
43
|
+
projectSkillsDir: path.join(projectRoot, '.claude', 'skills'),
|
|
44
|
+
globalSkillsDir,
|
|
45
|
+
});
|
|
46
|
+
} catch {
|
|
47
|
+
return { diverged: [], checked: 0 };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Copy the project SKILL.md over the global copy, per name. Only called after
|
|
53
|
+
* explicit consent. Returns the names actually synced (a per-name failure is
|
|
54
|
+
* skipped, never thrown).
|
|
55
|
+
*/
|
|
56
|
+
async function syncGlobalSkills(projectRoot, names, { homeDir = os.homedir() } = {}) {
|
|
57
|
+
const synced = [];
|
|
58
|
+
for (const name of names) {
|
|
59
|
+
try {
|
|
60
|
+
const src = path.join(projectRoot, '.claude', 'skills', name, 'SKILL.md');
|
|
61
|
+
const dest = path.join(homeDir, '.claude', 'skills', name, 'SKILL.md');
|
|
62
|
+
await fs.copy(src, dest, { overwrite: true });
|
|
63
|
+
synced.push(name);
|
|
64
|
+
} catch {
|
|
65
|
+
// one bad copy must not abort the others
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return synced;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The exact copy-paste command shown when we do not write ourselves. */
|
|
72
|
+
function syncCommand(names, { homeDir = os.homedir() } = {}) {
|
|
73
|
+
const globalDir = path.join(homeDir, '.claude', 'skills');
|
|
74
|
+
return names
|
|
75
|
+
.map((n) => `cp .claude/skills/${n}/SKILL.md ${path.join(globalDir, n, 'SKILL.md')}`)
|
|
76
|
+
.join('\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* End-of-install offer.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} projectRoot
|
|
83
|
+
* @param {string} templateDir
|
|
84
|
+
* @param {Object} opts
|
|
85
|
+
* @param {(question: string) => Promise<boolean>} [opts.ask] - Consent
|
|
86
|
+
* collector (inquirer in the CLI). Absent (non-interactive) -> never write,
|
|
87
|
+
* notice only.
|
|
88
|
+
* @param {(line: string) => void} [opts.log]
|
|
89
|
+
* @returns {Promise<{diverged: string[], synced: string[]}>}
|
|
90
|
+
*/
|
|
91
|
+
async function offerGlobalSkillsSync(projectRoot, templateDir, { ask = null, homeDir = os.homedir(), log = console.log } = {}) {
|
|
92
|
+
const { diverged } = await checkGlobalSkills(projectRoot, templateDir, { homeDir });
|
|
93
|
+
if (diverged.length === 0) return { diverged: [], synced: [] };
|
|
94
|
+
|
|
95
|
+
log(` [!] ${diverged.length} skill(s) globaux divergents dans ~/.claude/skills : ${diverged.join(', ')}`);
|
|
96
|
+
log(' Ces copies datent d\'avant cette installation et peuvent etre celles que la');
|
|
97
|
+
log(' commande /<skill> charge — les nouveautes resteraient invisibles.');
|
|
98
|
+
|
|
99
|
+
let synced = [];
|
|
100
|
+
if (typeof ask === 'function') {
|
|
101
|
+
const yes = await ask('Synchroniser ces copies globales depuis le projet (sauvegarde du contenu : aucune — elles seront ecrasees) ?');
|
|
102
|
+
if (yes) {
|
|
103
|
+
synced = await syncGlobalSkills(projectRoot, diverged, { homeDir });
|
|
104
|
+
log(` [OK] ${synced.length} copie(s) globale(s) synchronisee(s) depuis le projet`);
|
|
105
|
+
return { diverged, synced };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
log(' Rien n\'a ete modifie dans ~/.claude. Pour synchroniser toi-meme :');
|
|
109
|
+
for (const line of syncCommand(diverged, { homeDir }).split('\n')) log(` ${line}`);
|
|
110
|
+
return { diverged, synced };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
checkGlobalSkills,
|
|
115
|
+
syncGlobalSkills,
|
|
116
|
+
syncCommand,
|
|
117
|
+
offerGlobalSkillsSync,
|
|
118
|
+
};
|