create-byan-agent 2.15.0 → 2.17.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +24 -0
  3. package/install/GUIDE-INSTALLATION-BYAN-SIMPLE.md +40 -0
  4. package/install/bin/create-byan-agent-v2.js +9 -0
  5. package/install/lib/claude-native-setup.js +37 -0
  6. package/install/lib/mcp-extensions/gdrive.js +256 -0
  7. package/install/lib/mcp-extensions/index.js +147 -0
  8. package/install/package.json +1 -1
  9. package/install/packages/platform-config/lib/mcp-config.js +107 -8
  10. package/install/packages/platform-config/lib/validate.js +0 -14
  11. package/install/src/webui/api.js +6 -0
  12. package/install/src/webui/server.js +8 -1
  13. package/install/templates/.claude/CLAUDE.md +18 -0
  14. package/install/templates/.claude/hooks/lib/strict-config.json +46 -0
  15. package/install/templates/.claude/hooks/lib/strict-runtime.js +82 -0
  16. package/install/templates/.claude/hooks/strict-context-inject.js +86 -0
  17. package/install/templates/.claude/hooks/strict-scope-guard.js +101 -0
  18. package/install/templates/.claude/hooks/strict-stop-guard.js +100 -0
  19. package/install/templates/.claude/rules/strict-mode.md +166 -0
  20. package/install/templates/.claude/settings.json +12 -0
  21. package/install/templates/.claude/skills/byan-strict/SKILL.md +54 -0
  22. package/install/templates/.githooks/pre-commit +15 -0
  23. package/install/templates/_byan/_config/strict-mode.yaml +258 -0
  24. package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-sync-rules.js +24 -0
  25. package/install/templates/_byan/mcp/byan-mcp-server/bin/strict-precommit-gate.js +21 -0
  26. package/install/templates/_byan/mcp/byan-mcp-server/lib/fd-state.js +2 -1
  27. package/install/templates/_byan/mcp/byan-mcp-server/lib/precommit-gate.js +120 -0
  28. package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-activation.js +76 -0
  29. package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-mode.js +391 -0
  30. package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-sync.js +140 -0
  31. package/install/templates/_byan/mcp/byan-mcp-server/lib/sync-rules.js +261 -0
  32. package/install/templates/_byan/mcp/byan-mcp-server/server.js +207 -1
  33. package/package.json +6 -2
  34. package/src/byan-v2/data/strict-mantras.json +188 -0
  35. package/src/byan-v2/generation/mantra-validator.js +39 -4
  36. package/update-byan-agent/__tests__/migrate-mcp-config.test.js +74 -24
  37. package/update-byan-agent/lib/migrate-mcp-config.js +33 -27
package/CHANGELOG.md CHANGED
@@ -7,6 +7,84 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [2.17.0] - 2026-05-27
11
+
12
+ ### Added - BYAN Strict Mode shipped to npm + byan_web persistence
13
+
14
+ Anti-downgrade enforcement now packaged for `npx create-byan-agent` and backed by the byan_web API.
15
+
16
+ #### Strict Mode distribution
17
+
18
+ - Mirrored the full strict feature into `install/templates/` so a fresh install ships it: MCP tools (`byan_strict_*`), Claude Code hooks (Stop / PreToolUse / UserPromptSubmit), the `byan-strict` skill, `strict-mode.yaml`, and the generated runtime config.
19
+ - `settings.json` template now registers the three strict hooks.
20
+ - Installer wires the cross-platform pre-commit gate: copies `.githooks/` and sets `core.hooksPath` when the target is a git repo (`claude-native-setup.js`).
21
+
22
+ #### Server-side persistence (API authority)
23
+
24
+ - byan_web migration `033-strict-sessions.sql` + `routes/strict-sessions.js` (POST lock/upsert, PATCH verify/complete/abort, GET list + by id), scoped to the API key user with optional project attachment.
25
+ - New `lib/strict-sync.js` isolates network I/O: each local mutation pushes best-effort to the API; `byan_strict_status` and the pre-commit gate consult the API first and fall back to the local mirror when it is unreachable.
26
+ - `.mcp.json` carries `BYAN_API_TOKEN` via env (no secret committed).
27
+
28
+ ---
29
+
30
+ ## [2.16.2] - 2026-05-02
31
+
32
+ ### Added - Electron desktop app v1.0 (Linux + Windows)
33
+
34
+ Commits ea7abf3 + e905bee. App lives in `app/` as a standalone package (not a workspace of the root); builds and publishes independently of `create-byan-agent`.
35
+
36
+ #### Core shell and security (F1, F2, F12)
37
+
38
+ - **F1 app shell** — Electron main process in TypeScript (`app/main/`), compiled to `dist/main/`. Window lifecycle, splash, tray (Linux/Win only).
39
+ - **F2 IPC contract** — Preload bridge via `contextBridge` (`app/preload/`). All renderer-to-Node calls go through typed IPC channels; `nodeIntegration: false`, `contextIsolation: true`, `sandbox: true`.
40
+ - **F12 strict CSP** — Content Security Policy header injected by main process; default-src self, no inline scripts, no eval.
41
+
42
+ #### Local server lifecycle and renderer (F3, F13)
43
+
44
+ - **F3 lifecycle** — Main process spawns and supervises the existing `install/src/webui/server.js` local server on a free port; emits `server-ready` IPC event to renderer.
45
+ - **F13 dev hot reload** — `npm run dev` runs renderer (Vite dev server, port 5173), main watcher (`tsc -w`), and preload watcher concurrently via `concurrently`; `BYAN_DEV=1` env flag switches main to load the Vite URL instead of `dist/renderer/index.html`.
46
+
47
+ #### Authentication (F5)
48
+
49
+ - **F5 hybrid login** — Three login modes selectable at runtime: cloud (`byan.acadenice.fr`), local (auto-detected server), custom URL. Mode persisted in app config; switchable from the native menu.
50
+
51
+ #### Onboarding (F4)
52
+
53
+ - **F4 5-step onboarding** — Welcome -> Platform detection -> Config preview -> Apply -> Done. Covers Linux and Windows; macOS branch present but gated (F21 deferred). Onboarding state persisted via Electron store; skipped on subsequent launches.
54
+
55
+ #### Secure storage (F6)
56
+
57
+ - **F6 keytar** — API tokens stored via `keytar` (libsecret on Linux, Credential Manager on Windows). Token stored in the OS keychain, not in plaintext config files; IPC `get-token` / `set-token` channels exposed through preload only.
58
+
59
+ #### Native integration (F19)
60
+
61
+ - **F19 native menu** — Application menu built with `Menu.buildFromTemplate`; entries: File (quit), Edit (cut/copy/paste/select-all), View (reload, devtools in dev mode), Help (about). Consistent on Linux and Windows.
62
+
63
+ #### Cross-platform build (F10)
64
+
65
+ - **F10 build** — `npm run build` compiles main + preload (TypeScript) and bundles renderer (Vite). `npm run build:linux` produces AppImage + deb via electron-builder. `npm run build:win` produces NSIS installer via cross-compilation (Wine on Linux CI or native Windows runner).
66
+
67
+ #### CI matrix (F11, P1)
68
+
69
+ - **F11 + P1 GitHub Actions matrix** — Workflow `.github/workflows/electron-ci.yml` runs on `ubuntu-latest` (Linux build + unit tests) and `windows-latest` (Windows build + unit tests) in parallel. Draft GitHub Release created automatically when a `v*` tag is pushed; AppImage, deb, and NSIS installer attached as artifacts.
70
+
71
+ #### Test suite (F18)
72
+
73
+ - **F18 E2E Playwright** — Playwright suite in `app/__tests__/` using `playwright-electron`; covers: app launch, onboarding flow, login modal, token store round-trip, native menu visibility. `npm run test:e2e` runs the full suite headlessly.
74
+
75
+ ### Changed
76
+
77
+ - `install/src/webui/server.js` and `install/src/webui/api.js` — minor edits to support port-injection from the Electron main process (F3: server accepts `BYAN_PORT` env var, binds to `127.0.0.1` only, emits a ready signal to stdout that main process parses).
78
+
79
+ ### Notes
80
+
81
+ - **F21 macOS** — deferred; code branch exists, not tested, no CI runner. Target: v1.1.
82
+ - **F9 auto-update** — electron-updater integration deferred to P2 (v1.1). Update check menu item is present but inert.
83
+ - **F14 MCP control panel** — deferred to P2 (v1.1).
84
+ - **First CI run** — push tag `v0.1.0-rc` to trigger the first draft release and validate artifact upload end-to-end before promoting to `v1.0.0`.
85
+
86
+ ---
87
+
10
88
  ## [2.9.10] - 2026-04-21
11
89
 
12
90
  ### Fixed - MCP auth scheme wrong for byan_web API keys
package/README.md CHANGED
@@ -428,6 +428,29 @@ Domaines stricts : `security` / `performance` / `compliance` → LEVEL-2 minimum
428
428
 
429
429
  ---
430
430
 
431
+ ## BYAN Strict Mode — Anti-Downgrade
432
+
433
+ Mode d'enforcement qui empêche l'agent de livrer moins que demandé (un MVP au
434
+ lieu de l'app prod, un stub au lieu de la feature, un template baclé). Actif sur
435
+ les **3 plateformes** : Claude Code, Codex, GitHub Copilot.
436
+
437
+ ```
438
+ 1. Lock du scope byan_strict_lock_scope (scope verbatim + critères testables)
439
+ 2. Build complet pas de MVP, pas de stub, tout gap est signalé
440
+ 3. Self-verify >= 3x byan_strict_self_verify (relit la demande initiale)
441
+ 4. Complete byan_strict_complete (jeton d'audit)
442
+ ```
443
+
444
+ Le commit est **bloqué** par un filet pre-commit tant que la session strict
445
+ engagée n'est pas complétée correctement — y compris pour Codex et Copilot qui
446
+ n'ont pas de hook in-session. Source de vérité unique :
447
+ `_byan/_config/strict-mode.yaml`, régénérée via `byan-sync-rules`.
448
+
449
+ Activation : `byan_fd_start strict:true`, skill `byan-strict`, ou auto-détection
450
+ sur mots-clés (`prod`, `client`, `livrable`, `contrat`, `release`...).
451
+
452
+ ---
453
+
431
454
  ## Workflows Principaux
432
455
 
433
456
  | Workflow | Description | Agent principal |
@@ -443,6 +466,7 @@ Domaines stricts : `security` / `performance` / `compliance` → LEVEL-2 minimum
443
466
  | `testarch-atdd` | Générer des tests ATDD avant implémentation | tea |
444
467
  | `fact-check` | Analyser une assertion ou un document | fact-checker |
445
468
  | `elo-workflow` | Consulter et gérer le score de confiance ELO | byan |
469
+ | `byan-sync-rules` | Régénérer les artefacts du mode strict (3 plateformes) | byan |
446
470
 
447
471
  ---
448
472
 
@@ -416,6 +416,46 @@ Claude reconnaît l'agent BYAN et vous pouvez interagir avec lui.
416
416
 
417
417
  ---
418
418
 
419
+ ### 🔌 Extensions MCP optionnelles (depuis 2.16.0)
420
+
421
+ Pendant l'installation, BYAN propose d'activer des MCP servers tiers en plus du serveur byan natif.
422
+
423
+ **Disponibles aujourd'hui :**
424
+
425
+ | Extension | Description | Setup |
426
+ |-----------|-------------|-------|
427
+ | `gdrive` | Google Workspace : Docs, Sheets, Slides, Drive, Gmail, Calendar (95+ tools via `google-workspace-mcp`) | Interactif — guide Google Cloud + OAuth flow |
428
+
429
+ **Sécurité — où vivent les credentials :**
430
+
431
+ - Aucune credential n'est écrite dans le repo BYAN ni dans `.mcp.json` (source : [CLAIM L1] code de `install/packages/platform-config/lib/mcp-config.js`, fonction `assertNoSecretInEntry`)
432
+ - `~/.google-mcp/credentials.json` (perm 600) — Client OAuth Google
433
+ - `~/.google-mcp/tokens/<account>.json` — Tokens d'accès persistés par compte
434
+ - `BYAN_API_TOKEN` — vit uniquement dans `.env` (gitignored) + `.claude/settings.local.json` (gitignored)
435
+
436
+ **Si tu skip l'extension à l'install et veux l'activer plus tard**, relance `npx create-byan-agent` ou ajoute manuellement l'entry suivante à `.mcp.json` :
437
+
438
+ ```json
439
+ {
440
+ "mcpServers": {
441
+ "gdrive": {
442
+ "command": "npx",
443
+ "args": ["-y", "google-workspace-mcp", "serve"]
444
+ }
445
+ }
446
+ }
447
+ ```
448
+
449
+ Puis exécute le setup interactif du package :
450
+
451
+ ```bash
452
+ npx -y google-workspace-mcp setup
453
+ npx -y google-workspace-mcp accounts add default
454
+ npx -y google-workspace-mcp status # vérifier que tout est OK
455
+ ```
456
+
457
+ ---
458
+
419
459
  ## 5. Cas d'Usage Typiques
420
460
 
421
461
  ### 🎯 Cas 1 : Créer un Nouvel Agent
@@ -17,6 +17,7 @@ const { launchPhase2Chat, generateDefaultConfig } = require('../lib/phase2-chat'
17
17
  const { setupByanWebIntegration, validateByanWebReachability } = require('../lib/byan-web-integration');
18
18
  const { setupClaudeNative } = require('../lib/claude-native-setup');
19
19
  const { setupCodexNative } = require('../lib/codex-native-setup');
20
+ const { setupMcpExtensions } = require('../lib/mcp-extensions');
20
21
  const { setupStagingConsent } = require('../lib/staging-consent');
21
22
  const { getLatestVersion, compareVersions } = require('../lib/utils/version-compare');
22
23
 
@@ -1491,6 +1492,14 @@ async function install(options = {}) {
1491
1492
  }
1492
1493
  }
1493
1494
 
1495
+ if (needsClaude) {
1496
+ try {
1497
+ await setupMcpExtensions(projectRoot, {});
1498
+ } catch (error) {
1499
+ console.log(chalk.yellow(` ⚠ MCP extensions setup skipped: ${error.message}`));
1500
+ }
1501
+ }
1502
+
1494
1503
  // Step 8: Create config.yaml
1495
1504
  const configSpinner = ora('Generating configuration...').start();
1496
1505
 
@@ -131,6 +131,35 @@ async function installMcpDependencies(mcpServerPath) {
131
131
  }
132
132
  }
133
133
 
134
+ async function copyGitHooks(projectRoot) {
135
+ // The BYAN Strict Mode pre-commit gate is the cross-platform final net
136
+ // (Codex/Copilot have no in-session hook). Install it whenever the project
137
+ // is a git repo: copy .githooks/ and point core.hooksPath at it.
138
+ const src = path.join(TEMPLATE_ROOT, '.githooks');
139
+ const dst = path.join(projectRoot, '.githooks');
140
+ if (!(await fs.pathExists(src))) return { copied: false, reason: 'no_template' };
141
+ await fs.copy(src, dst, { overwrite: true });
142
+
143
+ const preCommit = path.join(dst, 'pre-commit');
144
+ if (await fs.pathExists(preCommit)) {
145
+ try { await fs.chmod(preCommit, 0o755); } catch { /* non-fatal */ }
146
+ }
147
+
148
+ // Only wire core.hooksPath when this is actually a git repo.
149
+ if (!(await fs.pathExists(path.join(projectRoot, '.git')))) {
150
+ return { copied: true, hooksPath: false, reason: 'not_a_git_repo' };
151
+ }
152
+ try {
153
+ execSync('git config core.hooksPath .githooks', {
154
+ cwd: projectRoot,
155
+ stdio: ['ignore', 'ignore', 'pipe'],
156
+ });
157
+ return { copied: true, hooksPath: true };
158
+ } catch (err) {
159
+ return { copied: true, hooksPath: false, error: err.message || String(err) };
160
+ }
161
+ }
162
+
134
163
  async function setupClaudeNative(projectRoot, options = {}) {
135
164
  const log = options.quiet ? () => {} : (...a) => console.log(...a);
136
165
  const results = {};
@@ -158,6 +187,13 @@ async function setupClaudeNative(projectRoot, options = {}) {
158
187
  results.mcpConfig = await generateMcpConfig(projectRoot, options);
159
188
  log(chalk.green(` ✓ .mcp.json generated (absolute path)`));
160
189
 
190
+ results.gitHooks = await copyGitHooks(projectRoot);
191
+ if (results.gitHooks.copied && results.gitHooks.hooksPath) {
192
+ log(chalk.green(` ✓ Strict pre-commit gate wired (.githooks + core.hooksPath)`));
193
+ } else if (results.gitHooks.copied) {
194
+ log(chalk.yellow(` ⚠ .githooks copied but not wired (${results.gitHooks.reason || 'no git repo'}); run: git config core.hooksPath .githooks`));
195
+ }
196
+
161
197
  if (results.mcp.copied && options.installDeps !== false) {
162
198
  results.mcpDeps = await installMcpDependencies(results.mcp.path);
163
199
  if (results.mcpDeps.installed) {
@@ -187,6 +223,7 @@ module.exports = {
187
223
  copyClaudeSkills,
188
224
  copyClaudeSettings,
189
225
  copyMcpServer,
226
+ copyGitHooks,
190
227
  makeNodeModulesFilter,
191
228
  generateMcpConfig,
192
229
  installMcpDependencies,
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Google Workspace MCP extension (gdrive)
3
+ *
4
+ * Wraps the npm package `google-workspace-mcp` (Docs, Sheets, Slides, Drive,
5
+ * Gmail, Calendar, Forms — 95+ tools). The package ships its own CLI for
6
+ * interactive credential setup and persists everything under
7
+ * ~/.google-mcp/ (gitignored by virtue of being in $HOME).
8
+ *
9
+ * Our role here is:
10
+ * 1. Detect if the package is reachable (npx).
11
+ * 2. Detect if credentials are already on disk.
12
+ * 3. If not, walk the user through the Google Cloud setup steps with
13
+ * direct console links, and delegate the actual OAuth flow to the
14
+ * package's `setup` / `accounts add` CLI subcommands.
15
+ * 4. Provide the .mcp.json entry to register the server.
16
+ *
17
+ * No credential ever touches the project tree. The .mcp.json entry only
18
+ * declares command/args — every secret stays in ~/.google-mcp/.
19
+ */
20
+
21
+ 'use strict';
22
+
23
+ const path = require('path');
24
+ const os = require('os');
25
+ const fs = require('fs-extra');
26
+ const chalk = require('chalk');
27
+ const inquirer = require('inquirer');
28
+ const { execSync, spawnSync } = require('child_process');
29
+
30
+ const PACKAGE_NAME = 'google-workspace-mcp';
31
+ const CONFIG_DIR = path.join(os.homedir(), '.google-mcp');
32
+ const CREDENTIALS_PATH = path.join(CONFIG_DIR, 'credentials.json');
33
+
34
+ const SETUP_LINKS = [
35
+ {
36
+ step: 'Créer un projet Google Cloud',
37
+ url: 'https://console.cloud.google.com/projectcreate',
38
+ },
39
+ {
40
+ step: 'Activer les APIs (Drive, Docs, Sheets, Slides, Gmail, Calendar, Forms)',
41
+ url: 'https://console.cloud.google.com/apis/library',
42
+ },
43
+ {
44
+ step: 'Configurer l\'écran de consentement OAuth (External, mode Test)',
45
+ url: 'https://console.cloud.google.com/apis/credentials/consent',
46
+ },
47
+ {
48
+ step: 'Créer un OAuth Client ID type "Desktop App"',
49
+ url: 'https://console.cloud.google.com/apis/credentials/oauthclient',
50
+ },
51
+ ];
52
+
53
+ async function isPackageInstallable() {
54
+ try {
55
+ execSync(`npm view ${PACKAGE_NAME} version --silent`, {
56
+ stdio: ['ignore', 'ignore', 'pipe'],
57
+ timeout: 30_000,
58
+ });
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ async function hasCredentials() {
66
+ return fs.pathExists(CREDENTIALS_PATH);
67
+ }
68
+
69
+ async function isConfigured() {
70
+ return hasCredentials();
71
+ }
72
+
73
+ function buildEntry() {
74
+ return {
75
+ command: 'npx',
76
+ args: ['-y', PACKAGE_NAME, 'serve'],
77
+ };
78
+ }
79
+
80
+ async function buildMcpEntry() {
81
+ return buildEntry();
82
+ }
83
+
84
+ function printSetupGuide(log) {
85
+ log();
86
+ log(chalk.cyan('Étapes Google Cloud (faire dans le navigateur, dans cet ordre) :'));
87
+ SETUP_LINKS.forEach((s, i) => {
88
+ log(chalk.gray(` ${i + 1}. ${s.step}`));
89
+ log(chalk.gray(` → ${s.url}`));
90
+ });
91
+ log();
92
+ log(chalk.gray(` 5. Télécharger le JSON OAuth Client (bouton "Download JSON")`));
93
+ log(chalk.gray(` 6. Renommer ce fichier en : credentials.json`));
94
+ log(chalk.gray(` 7. Le placer dans : ${CREDENTIALS_PATH}`));
95
+ log();
96
+ }
97
+
98
+ async function importCredentialsFromPath(srcPath, log) {
99
+ const abs = path.resolve(srcPath);
100
+ if (!(await fs.pathExists(abs))) {
101
+ throw new Error(`Fichier introuvable : ${abs}`);
102
+ }
103
+ let parsed;
104
+ try {
105
+ parsed = await fs.readJson(abs);
106
+ } catch (e) {
107
+ throw new Error(`JSON invalide : ${e.message}`);
108
+ }
109
+ // Sanity check — Google OAuth client JSON has either "installed" or "web"
110
+ if (!parsed.installed && !parsed.web) {
111
+ throw new Error(
112
+ `Format inattendu : ce fichier ne ressemble pas à un OAuth Client Google (clé "installed" ou "web" absente)`
113
+ );
114
+ }
115
+ await fs.ensureDir(CONFIG_DIR);
116
+ // Tighten dir perms : 700 (owner only). Best-effort on non-POSIX.
117
+ try {
118
+ await fs.chmod(CONFIG_DIR, 0o700);
119
+ } catch {
120
+ // ignore on platforms where this fails
121
+ }
122
+ await fs.writeFile(CREDENTIALS_PATH, JSON.stringify(parsed, null, 2), {
123
+ mode: 0o600,
124
+ });
125
+ log(chalk.green(` ✓ credentials.json copié vers ${CREDENTIALS_PATH} (perm 600)`));
126
+ }
127
+
128
+ async function runOAuthFlow(log) {
129
+ log();
130
+ log(chalk.cyan('Lancement du flow OAuth Google (le navigateur va s\'ouvrir)'));
131
+ log(chalk.gray(' Suis les instructions à l\'écran. Ferme la fenêtre quand le flow est terminé.'));
132
+ log();
133
+
134
+ const { accountName } = await inquirer.prompt([
135
+ {
136
+ type: 'input',
137
+ name: 'accountName',
138
+ message: 'Nom du compte Google (un slug — ex : "perso", "work") :',
139
+ default: 'default',
140
+ validate: (v) => /^[a-z0-9_-]+$/i.test(v) || 'Caractères autorisés : a-z, 0-9, _, -',
141
+ },
142
+ ]);
143
+
144
+ const result = spawnSync('npx', ['-y', PACKAGE_NAME, 'accounts', 'add', accountName], {
145
+ stdio: 'inherit',
146
+ timeout: 600_000, // 10 minutes
147
+ });
148
+
149
+ if (result.status !== 0) {
150
+ throw new Error(`google-workspace-mcp accounts add a échoué (exit ${result.status})`);
151
+ }
152
+ log(chalk.green(` ✓ Compte "${accountName}" ajouté`));
153
+ return accountName;
154
+ }
155
+
156
+ async function setup({ quiet } = {}) {
157
+ const log = quiet ? () => {} : (...a) => console.log(...a);
158
+
159
+ if (!(await isPackageInstallable())) {
160
+ return {
161
+ configured: false,
162
+ skipReason: `Le package npm "${PACKAGE_NAME}" n'est pas accessible (réseau / registre indisponible). Réessaie plus tard.`,
163
+ };
164
+ }
165
+
166
+ if (await hasCredentials()) {
167
+ log(chalk.gray(` · credentials.json déjà présent à ${CREDENTIALS_PATH}`));
168
+ const { reuse } = await inquirer.prompt([
169
+ {
170
+ type: 'confirm',
171
+ name: 'reuse',
172
+ message: 'Réutiliser la config existante (sans relancer OAuth) ?',
173
+ default: true,
174
+ },
175
+ ]);
176
+ if (reuse) {
177
+ return { configured: true, message: 'reused existing credentials' };
178
+ }
179
+ } else {
180
+ printSetupGuide(log);
181
+
182
+ const { hasJson } = await inquirer.prompt([
183
+ {
184
+ type: 'confirm',
185
+ name: 'hasJson',
186
+ message: 'Tu as téléchargé le credentials.json (étapes 1-5) ?',
187
+ default: false,
188
+ },
189
+ ]);
190
+
191
+ if (!hasJson) {
192
+ return {
193
+ configured: false,
194
+ skipReason:
195
+ 'Setup interrompu — relance l\'installer une fois le credentials.json téléchargé.',
196
+ };
197
+ }
198
+
199
+ const { jsonPath } = await inquirer.prompt([
200
+ {
201
+ type: 'input',
202
+ name: 'jsonPath',
203
+ message: 'Chemin local vers le credentials.json téléchargé :',
204
+ validate: (v) => v && v.trim().length > 0 || 'Chemin requis',
205
+ },
206
+ ]);
207
+
208
+ try {
209
+ await importCredentialsFromPath(jsonPath.trim(), log);
210
+ } catch (err) {
211
+ return { configured: false, skipReason: `Import des credentials échoué : ${err.message}` };
212
+ }
213
+ }
214
+
215
+ const { runAuth } = await inquirer.prompt([
216
+ {
217
+ type: 'confirm',
218
+ name: 'runAuth',
219
+ message: 'Lancer le flow OAuth maintenant (ouvre le navigateur) ?',
220
+ default: true,
221
+ },
222
+ ]);
223
+
224
+ if (!runAuth) {
225
+ return {
226
+ configured: true,
227
+ message:
228
+ 'credentials importés ; lance le flow OAuth plus tard via : npx -y google-workspace-mcp accounts add <name>',
229
+ };
230
+ }
231
+
232
+ try {
233
+ const account = await runOAuthFlow(log);
234
+ return { configured: true, message: `compte "${account}" authentifié` };
235
+ } catch (err) {
236
+ return {
237
+ configured: false,
238
+ skipReason:
239
+ `OAuth a échoué : ${err.message}. Relance manuellement : npx -y ${PACKAGE_NAME} accounts add <name>`,
240
+ };
241
+ }
242
+ }
243
+
244
+ module.exports = {
245
+ id: 'gdrive',
246
+ name: 'Google Workspace (Docs / Sheets / Slides / Drive / Gmail / Calendar)',
247
+ description: '95+ tools via google-workspace-mcp, OAuth2, creds in ~/.google-mcp/',
248
+ isConfigured,
249
+ setup,
250
+ buildMcpEntry,
251
+ // exposed for tests
252
+ buildEntry,
253
+ CONFIG_DIR,
254
+ CREDENTIALS_PATH,
255
+ PACKAGE_NAME,
256
+ };
@@ -0,0 +1,147 @@
1
+ /**
2
+ * MCP Extensions Registry — discovers and orchestrates third-party MCP
3
+ * server integrations (Google Workspace, etc.) during yanstaller install.
4
+ *
5
+ * Each extension is a module under this directory exposing the contract:
6
+ *
7
+ * {
8
+ * id : string // unique slug (becomes mcpServers key)
9
+ * name : string // human-readable name
10
+ * description : string // shown to user during prompt
11
+ * async isConfigured(): Promise<boolean> // already set up on this machine?
12
+ * async setup(options): Promise<{
13
+ * configured: boolean,
14
+ * message: string,
15
+ * skipReason?: string,
16
+ * }> // interactive: walks the user
17
+ * // through credential setup
18
+ * async buildMcpEntry(): Promise<object> // returns the entry to write
19
+ * // into mcpServers.<id>
20
+ * }
21
+ *
22
+ * The registry walks the list, prompts the user for each, runs setup if
23
+ * accepted, and writes the resulting MCP entry into .mcp.json via
24
+ * addMcpEntry (which refuses any entry containing a value that looks like
25
+ * a secret — see mcp-config.js).
26
+ *
27
+ * No secret value is ever stored by this module. Per-extension credential
28
+ * persistence is the extension's responsibility (typically under ~/.config/
29
+ * or ~/.<package>/, never inside the project).
30
+ */
31
+
32
+ 'use strict';
33
+
34
+ const path = require('path');
35
+ const fs = require('fs-extra');
36
+ const chalk = require('chalk');
37
+ const inquirer = require('inquirer');
38
+
39
+ const {
40
+ mcpConfig: { addMcpEntry },
41
+ } = require('byan-platform-config');
42
+
43
+ const gdrive = require('./gdrive');
44
+
45
+ const EXTENSIONS = [gdrive];
46
+
47
+ function listExtensions() {
48
+ return EXTENSIONS.map((ext) => ({
49
+ id: ext.id,
50
+ name: ext.name,
51
+ description: ext.description,
52
+ }));
53
+ }
54
+
55
+ function getExtension(id) {
56
+ return EXTENSIONS.find((ext) => ext.id === id) || null;
57
+ }
58
+
59
+ /**
60
+ * Walk every registered extension and offer it to the user. For each
61
+ * extension the user accepts, run its setup flow, then register the
62
+ * resulting MCP entry in .mcp.json.
63
+ *
64
+ * @param {string} projectRoot
65
+ * @param {{
66
+ * skipPrompts?: boolean,
67
+ * presetSelections?: Record<string, boolean>, // { gdrive: true }
68
+ * quiet?: boolean,
69
+ * }} options
70
+ * @returns {Promise<Array<{ id: string, configured: boolean, message: string }>>}
71
+ */
72
+ async function setupMcpExtensions(projectRoot, options = {}) {
73
+ const log = options.quiet ? () => {} : (...a) => console.log(...a);
74
+ const results = [];
75
+
76
+ if (EXTENSIONS.length === 0) return results;
77
+
78
+ log();
79
+ log(chalk.cyan('MCP extensions (optional third-party integrations)'));
80
+
81
+ for (const ext of EXTENSIONS) {
82
+ let want;
83
+ if (options.skipPrompts) {
84
+ want = options.presetSelections && options.presetSelections[ext.id] === true;
85
+ } else {
86
+ const answers = await inquirer.prompt([
87
+ {
88
+ type: 'confirm',
89
+ name: 'enable',
90
+ message: `${ext.name} — ${ext.description}\n Activer ?`,
91
+ default: false,
92
+ },
93
+ ]);
94
+ want = answers.enable === true;
95
+ }
96
+
97
+ if (!want) {
98
+ log(chalk.gray(` · ${ext.id}: skipped`));
99
+ results.push({ id: ext.id, configured: false, message: 'skipped by user' });
100
+ continue;
101
+ }
102
+
103
+ let setupResult;
104
+ try {
105
+ setupResult = await ext.setup({ projectRoot, quiet: options.quiet });
106
+ } catch (err) {
107
+ log(chalk.red(` ✘ ${ext.id} setup failed: ${err.message}`));
108
+ results.push({ id: ext.id, configured: false, message: `setup error: ${err.message}` });
109
+ continue;
110
+ }
111
+
112
+ if (!setupResult || setupResult.configured !== true) {
113
+ const reason = (setupResult && (setupResult.skipReason || setupResult.message)) || 'setup not completed';
114
+ log(chalk.yellow(` ⚠ ${ext.id}: ${reason}`));
115
+ results.push({ id: ext.id, configured: false, message: reason });
116
+ continue;
117
+ }
118
+
119
+ let entry;
120
+ try {
121
+ entry = await ext.buildMcpEntry({ projectRoot });
122
+ } catch (err) {
123
+ log(chalk.red(` ✘ ${ext.id} buildMcpEntry failed: ${err.message}`));
124
+ results.push({ id: ext.id, configured: false, message: `buildMcpEntry error: ${err.message}` });
125
+ continue;
126
+ }
127
+
128
+ try {
129
+ await addMcpEntry(projectRoot, ext.id, entry);
130
+ log(chalk.green(` ✓ ${ext.id} registered in .mcp.json`));
131
+ results.push({ id: ext.id, configured: true, message: 'registered' });
132
+ } catch (err) {
133
+ log(chalk.red(` ✘ ${ext.id} addMcpEntry failed: ${err.message}`));
134
+ results.push({ id: ext.id, configured: false, message: `addMcpEntry error: ${err.message}` });
135
+ }
136
+ }
137
+
138
+ return results;
139
+ }
140
+
141
+ module.exports = {
142
+ listExtensions,
143
+ getExtension,
144
+ setupMcpExtensions,
145
+ // re-exported for tests / programmatic callers
146
+ EXTENSIONS,
147
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "2.15.0",
3
+ "version": "2.17.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"