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.
- package/CHANGELOG.md +78 -0
- package/README.md +24 -0
- package/install/GUIDE-INSTALLATION-BYAN-SIMPLE.md +40 -0
- package/install/bin/create-byan-agent-v2.js +9 -0
- package/install/lib/claude-native-setup.js +37 -0
- package/install/lib/mcp-extensions/gdrive.js +256 -0
- package/install/lib/mcp-extensions/index.js +147 -0
- package/install/package.json +1 -1
- package/install/packages/platform-config/lib/mcp-config.js +107 -8
- package/install/packages/platform-config/lib/validate.js +0 -14
- package/install/src/webui/api.js +6 -0
- package/install/src/webui/server.js +8 -1
- package/install/templates/.claude/CLAUDE.md +18 -0
- package/install/templates/.claude/hooks/lib/strict-config.json +46 -0
- package/install/templates/.claude/hooks/lib/strict-runtime.js +82 -0
- package/install/templates/.claude/hooks/strict-context-inject.js +86 -0
- package/install/templates/.claude/hooks/strict-scope-guard.js +101 -0
- package/install/templates/.claude/hooks/strict-stop-guard.js +100 -0
- package/install/templates/.claude/rules/strict-mode.md +166 -0
- package/install/templates/.claude/settings.json +12 -0
- package/install/templates/.claude/skills/byan-strict/SKILL.md +54 -0
- package/install/templates/.githooks/pre-commit +15 -0
- package/install/templates/_byan/_config/strict-mode.yaml +258 -0
- package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-sync-rules.js +24 -0
- package/install/templates/_byan/mcp/byan-mcp-server/bin/strict-precommit-gate.js +21 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/fd-state.js +2 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/precommit-gate.js +120 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-activation.js +76 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-mode.js +391 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-sync.js +140 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/sync-rules.js +261 -0
- package/install/templates/_byan/mcp/byan-mcp-server/server.js +207 -1
- package/package.json +6 -2
- package/src/byan-v2/data/strict-mantras.json +188 -0
- package/src/byan-v2/generation/mantra-validator.js +39 -4
- package/update-byan-agent/__tests__/migrate-mcp-config.test.js +74 -24
- 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
|
+
};
|
package/install/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-byan-agent",
|
|
3
|
-
"version": "2.
|
|
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"
|