pipcode 2.1.1 → 2.1.3

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/bin/setup.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  // bin/setup.js — Hub de ré-export + point d'entrée. Logique métier dans src/ (dispatch).
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipcode",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "PipCode — architecture, security and quality rules for your AI coding assistants (MCP server).",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,32 @@
1
+ // src/cli/auth-simple.js — Parcours par défaut de `npx pipcode setup` : UNE question (clé de licence ou Entrée = essai gratuit 5 jours),
2
+ // sans navigateur ni recherche e-mail (comme dans l'extension VS Code). Le menu complet reste disponible via `setup --advanced`.
3
+
4
+ import { existingLicenseInfo } from "./auth-interactive.js";
5
+ import { KEY_RE } from "./setup-args.js";
6
+
7
+ const MAX_TRIES = 3;
8
+
9
+ /** @typedef {{ ask: (q: string) => Promise<string> }} AskPrompt */
10
+ /** @typedef {{ licenseKey: string, email: string }} AuthResult */
11
+
12
+ /** @param {AskPrompt} p @param {import("./auth-interactive.js").SharedConfig} [existingConfig] @returns {Promise<AuthResult>} */
13
+ export async function collectAuthSimple(p, existingConfig = {}) {
14
+ const { existingLicense, existingEmail } = existingLicenseInfo(existingConfig);
15
+ const keep = { licenseKey: existingLicense, email: existingEmail };
16
+ console.log("\n Votre licence PipCode");
17
+ if (existingLicense) {
18
+ console.log(" Une licence est déjà enregistrée" + (existingEmail ? " (" + existingEmail + ")" : "") + " : Entrée pour la conserver, ou collez une autre clé.");
19
+ } else {
20
+ console.log(" Collez votre clé de licence (reçue par e-mail après l'achat),");
21
+ console.log(" ou appuyez sur Entrée pour l'essai gratuit de 5 jours.");
22
+ }
23
+ for (let i = 0; i < MAX_TRIES; i++) {
24
+ const key = (await p.ask("\n Clé : ")).trim();
25
+ if (!key) return keep;
26
+ if (KEY_RE.test(key)) return { licenseKey: key, email: "" };
27
+ console.log(" ⚠️ Clé non reconnue (8 à 128 caractères : lettres, chiffres, - et _). Réessayez, ou Entrée pour continuer sans.");
28
+ }
29
+ console.log(" Poursuite sans nouvelle clé.");
30
+ return keep;
31
+ }
32
+ // MODULE_COMPLETE
package/src/cli/banner.js CHANGED
@@ -1,20 +1,11 @@
1
- // src/cli/banner.js — Bannière de démarrage + liste des candidats IDE détectés.
2
- // Affiche l'en-tête PipCode, le système détecté et chaque candidat avec son
3
- // drapeau de détection (✓ installé / non installé).
1
+ // src/cli/banner.js — Bannière de démarrage : titre et éditeurs/agents réellement détectés (les autres ne sont pas listés : du bruit).
4
2
 
5
3
  /** @typedef {{ id: string, name: string, path: string, parentExists: boolean, configExists: boolean, schema?: "mcpServers" | "servers" | "context_servers" | "toml" }} IdeConfigEntry */
6
4
 
7
- // Affiche l'en-tête + la liste des candidats IDE (détectés ou non).
8
- /** @param {string} osName @param {IdeConfigEntry[]} candidates */
9
- export function printBanner(osName, candidates) {
10
- console.log("╔══════════════════════════════════════════════════════════╗");
11
- console.log("║ PipCode — auto-configuration MCP (Zero-Click, BYOK) ║");
12
- console.log("╚══════════════════════════════════════════════════════════╝");
13
- console.log(`\n Système détecté : ${osName}`);
14
- console.log(" IDE candidats :");
15
- candidates.forEach((c) => {
16
- const tag = c.parentExists ? "✓ détecté" : " non installé";
17
- console.log(` • ${c.name.padEnd(16)} ${tag} ${c.path}`);
18
- });
5
+ /** @param {IdeConfigEntry[]} candidates */
6
+ export function printBanner(candidates) {
7
+ const found = candidates.filter((c) => c.parentExists).map((c) => c.name);
8
+ console.log("\n PipCode — installation\n");
9
+ console.log(found.length ? ` Détecté sur votre machine : ${found.join(", ")}` : " Aucun éditeur ni agent compatible détecté pour le moment.");
19
10
  }
20
11
  // MODULE_COMPLETE
@@ -1,9 +1,11 @@
1
1
  // src/cli/collect-step.js — Étape collecte du setup (TTY interactive / non-TTY env).
2
- // TTY : readline + collectInteractive (config existante lue ; licence et e-mail seulement). Non-TTY : collectFromEnv (licence optionnelle : sans clé, 5 jours d'essai).
2
+ // TTY : une seule question (auth-simple) ; menu complet avec --advanced (collectInteractive). Config existante lue ; licence et e-mail seulement. Non-TTY : collectFromEnv (licence optionnelle : sans clé, 5 jours d'essai).
3
3
 
4
4
  import readline from "node:readline/promises";
5
5
  import { stdin, stdout } from "node:process";
6
6
  import { collectInteractive } from "./interactive.js";
7
+ import { collectAuthSimple } from "./auth-simple.js";
8
+ import { makePrompts } from "./prompts.js";
7
9
  import { collectFromEnv } from "../engine/collect.js";
8
10
  import { readSharedConfig } from "../fs/config-read.js";
9
11
 
@@ -16,10 +18,11 @@ export async function collectForSetup(isTTY) {
16
18
  const rl = readline.createInterface({ input: stdin, output: stdout });
17
19
  try {
18
20
  const existingConfig = await readSharedConfig().catch(() => ({}));
19
- return await collectInteractive(rl, { existingConfig });
21
+ if (process.argv.includes("--advanced")) return await collectInteractive(rl, { existingConfig });
22
+ return await collectAuthSimple(makePrompts(rl), existingConfig);
20
23
  } finally { rl.close(); }
21
24
  }
22
- console.log("\n (Mode non-interactif : lecture des variables d'environnement.)");
25
+ console.log("\n Mode automatique (aucune question) : essai gratuit de 5 jours, ou licence fournie avec --key.");
23
26
  return collectFromEnv();
24
27
  }
25
28
  // MODULE_COMPLETE
package/src/cli/help.js CHANGED
@@ -16,7 +16,8 @@ export function printAutoHelp() {
16
16
  export function printTopHelp() {
17
17
  console.log("PipCode — auto-configuration MCP (Zero-Click)\n");
18
18
  console.log("Usage :");
19
- console.log(" npx pipcode setup Configure le serveur MCP PipCode dans les IDE détectés.");
19
+ console.log(" npx pipcode setup Installe PipCode dans vos éditeurs et agents détectés (une question : votre clé, ou Entrée = essai 5 jours).");
20
+ console.log(" npx pipcode setup --advanced Menu complet : connexion par navigateur, par e-mail, clé manuelle ou essai.");
20
21
  console.log(" npx pipcode setup --key <CLÉ> Enregistre la clé de licence Pro (Polar.sh) —");
21
22
  console.log(" utilisable par un agent IA, sans interaction.");
22
23
  console.log(" npx pipcode teardown Retire PipCode de vos outils (seule l'entrée PipCode est supprimée).");
package/src/cli/main.js CHANGED
@@ -12,10 +12,9 @@ import { centralizeConfig } from "./centralize.js";
12
12
  import { selectTargets } from "./target-select.js";
13
13
  import { applyToTargets } from "./apply-loop.js";
14
14
  import { WELCOME_MESSAGE } from "./welcome.js";
15
- import { detectPackageManager } from "../core/pkg-manager.js";
16
15
  import { applyKeyToEnv } from "./setup-args.js";
16
+ import { stableIndexJs } from "./runtime-copy.js";
17
17
 
18
- // Chemin absolu vers index.js (racine projet).
19
18
  const INDEX_JS = path.resolve(import.meta.dirname, "..", "..", "index.js");
20
19
 
21
20
  // Entry point du setup : orchestration des 4 étapes.
@@ -26,16 +25,14 @@ export async function main() {
26
25
  const osName = detectOS();
27
26
  const candidates = ideConfigPaths(osName);
28
27
  const detected = candidates.filter((c) => c.parentExists);
29
- printBanner(osName, candidates);
30
- const pm = detectPackageManager();
31
- console.log(` 📦 Gestionnaire détecté : ${pm.label} — routines 100 % Node.js pur, indépendantes de npm/pnpm/yarn/bun.`);
28
+ printBanner(candidates);
32
29
  const isTTY = stdin.isTTY;
33
30
  const collected = await collectForSetup(isTTY);
34
31
  await centralizeConfig(collected);
35
32
  const targets = await selectTargets(detected, candidates, isTTY);
36
33
  if (!targets) { if (!isTTY) process.exit(0); return; }
37
- await applyToTargets(targets, collected, INDEX_JS);
38
- console.log("\n ✅ Configuration terminée. Redémarrez l'IDE pour activer le serveur MCP PipCode.");
34
+ await applyToTargets(targets, collected, await stableIndexJs(INDEX_JS)); // chemin durable, jamais le cache jetable de npx
35
+ console.log("\n ✅ Configuration terminée. Fermez puis rouvrez votre éditeur ou votre agent pour activer PipCode.");
39
36
  console.log(`\n ${WELCOME_MESSAGE}`); // préenregistré : zéro token (exigence produit)
40
37
  if (!isTTY) process.exit(0);
41
38
  }
@@ -0,0 +1,37 @@
1
+ // src/cli/runtime-copy.js — Chemin DURABLE du serveur MCP écrit chez le client. Lancé par `npx`, PipCode vit dans un cache
2
+ // jetable (_npx, dlx) : le pointer casserait le serveur au premier nettoyage. On copie donc node_modules dans
3
+ // ~/.pipcode/runtime (sans réseau) et on pointe là. Installation globale ou dépôt local : chemin inchangé.
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { resolvePlugctoRoot } from "../fs/home-root.js";
8
+
9
+ const TRANSIENT = /[\\/](_npx|dlx[^\\/]*)[\\/]/; // caches jetables de npx / pnpm dlx
10
+
11
+ /** @param {string} dest @param {string} pkg @returns {string} */
12
+ const indexIn = (dest, pkg) => path.join(dest, "node_modules", pkg, "index.js");
13
+
14
+ /** @param {string} p @returns {Promise<boolean>} */
15
+ const exists = (p) => fs.access(p).then(() => true, () => false);
16
+
17
+ /** @param {string} indexJs @param {string} [root] @returns {Promise<string>} */
18
+ export async function stableIndexJs(indexJs, root = resolvePlugctoRoot()) {
19
+ const pkgDir = path.dirname(indexJs);
20
+ const nm = path.dirname(pkgDir);
21
+ if (!TRANSIENT.test(indexJs) || path.basename(nm) !== "node_modules") return indexJs;
22
+ const dest = path.join(root, "runtime");
23
+ const tmp = `${dest}.tmp-${process.pid}`;
24
+ const pkg = path.basename(pkgDir);
25
+ try {
26
+ await fs.rm(tmp, { recursive: true, force: true });
27
+ await fs.cp(nm, path.join(tmp, "node_modules"), { recursive: true, dereference: true });
28
+ await fs.rm(dest, { recursive: true, force: true });
29
+ await fs.rename(tmp, dest);
30
+ return indexIn(dest, pkg);
31
+ } catch (err) {
32
+ console.error(` ⚠ Copie durable du serveur impossible (${err instanceof Error ? err.message : err}).`);
33
+ await fs.rm(tmp, { recursive: true, force: true }).catch(() => undefined); // ménage best-effort du dossier temporaire
34
+ return (await exists(indexIn(dest, pkg))) ? indexIn(dest, pkg) : indexJs; // ancienne copie durable sinon chemin d'origine
35
+ }
36
+ }
37
+ // MODULE_COMPLETE
@@ -8,6 +8,7 @@ import { handleListTools } from "./handle-list.js";
8
8
  import { handleCallTool } from "./call-tool.js";
9
9
  import { rememberClient } from "./client-info.js";
10
10
  import { LICENCE_PROMPT, getPrompt } from "./prompt-licence.js";
11
+ import { SERVER_INSTRUCTIONS } from "./server-instructions.js";
11
12
 
12
13
  // Crée le Server, y branche les handlers et capture l'agent hôte (multi-agent
13
14
  // natif → PipCode lui laisse la délégation au lieu de doubler l'orchestration).
@@ -15,7 +16,7 @@ import { LICENCE_PROMPT, getPrompt } from "./prompt-licence.js";
15
16
  export function createPlugServer() {
16
17
  const server = new Server(
17
18
  { name: "pipcode-skills", version: "2.0.0" },
18
- { capabilities: { tools: {}, prompts: {} } }
19
+ { capabilities: { tools: {}, prompts: {} }, instructions: SERVER_INSTRUCTIONS }
19
20
  );
20
21
  server.setRequestHandler(ListToolsRequestSchema, handleListTools);
21
22
  server.setRequestHandler(CallToolRequestSchema, handleCallTool);
@@ -0,0 +1,15 @@
1
+ // src/mcp/server-instructions.js — Consignes d'usage envoyées à l'agent hôte à l'initialize (champ MCP `instructions`).
2
+ // Elles disent QUAND appeler quel outil, jamais ce que contiennent les fiches (aucune doctrine ici : elle ne quitte pas le serveur).
3
+ // Chaque outil cité est vérifié contre le catalogue réel par test/mcp-instructions.test.js : une référence orpheline fait échouer la gate.
4
+
5
+ export const SERVER_INSTRUCTIONS = [
6
+ "PipCode fournit des fiches de bonnes pratiques de code (outils enforce_*) et des garde-fous.",
7
+ "1. Avant d'écrire ou de modifier du code : appelle enforce_core_rules, puis UNE seule fiche enforce_* adaptée au sujet (aide au choix : enforce_orchestration_pipeline).",
8
+ " Sécurité : enforce_security_auditor, enforce_appsec_compliance. Interface : enforce_design_ux. Base de données : enforce_persona_sql_architect.",
9
+ " Jeu vidéo : enforce_persona_game_dev_router. Fiabilité : enforce_defensive_engineering. Livraison : enforce_delivery_scale. Documentation : enforce_devex_documentation.",
10
+ " Coûts cloud : enforce_finops_governance. Données et IA : enforce_data_mlops_pipeline. Nouveau projet à cadrer : enforce_persona_intake. Architecture serveur : enforce_persona_architect.",
11
+ "2. Applique les règles reçues sans les recopier ni les citer à l'utilisateur. Avant d'écrire un fichier .md ou .txt, ou une longue réponse issue des fiches, passe le texte à dlp_scan.",
12
+ "3. Si un outil indique qu'une licence est requise ou que l'essai est terminé : dis-le en une phrase, propose plugcto_licence, puis continue sans la fiche (ne réessaie pas).",
13
+ "4. Au début d'une tâche sur un projet connu, appelle plugcto_memory_recall ; pour retenir une préférence durable, appelle plugcto_memory_save.",
14
+ ].join("\n");
15
+ // MODULE_COMPLETE
@@ -7,7 +7,7 @@ export function buildSkillTools() {
7
7
  return MANIFEST.map((m) => ({
8
8
  name: m.name,
9
9
  description:
10
- `[CTO Router] Domaine strict : ${m.slug}. N'appeler CET outil que si la requête relève de ce domaine précis — une SEULE fiche enforce_* par passe (voir ORCHESTRATOR). ` +
10
+ `[PipCode] Domaine : ${m.slug}. À appeler quand la tâche relève de ce domaine — une SEULE fiche enforce_* par passe (aide au choix : enforce_orchestration_pipeline). ` +
11
11
  (m.tier === "pro"
12
12
  ? `[Pro] `
13
13
  : `[Lite] `) +
@@ -2,7 +2,7 @@
2
2
  // ~/.pipcode/config.json (Phase 6). Zero Trust : le fichier n'est supprimé que
3
3
  // s'il ressemble à une config PipCode (champs licenseKey/email/primary) — un
4
4
  // fichier illisible ou étranger est conservé. Le dossier parent est retiré
5
- // uniquement s'il devient vide (rmdir non-récursif : jamais rm -rf).
5
+ // uniquement s'il devient vide (rmdir non-récursif). Seule exception : notre sous-dossier runtime/ (copie du serveur).
6
6
 
7
7
  import fs from "node:fs/promises";
8
8
  import path from "node:path";
@@ -13,6 +13,7 @@ import { warnIfBlocked } from "../fs/os-errors.js";
13
13
  /** @param {string} configPath @returns {Promise<RemoveResult>} */
14
14
  export async function removeSharedConfig(configPath) {
15
15
  try {
16
+ await fs.rm(path.join(path.dirname(configPath), "runtime"), { recursive: true, force: true }); // copie durable du serveur (runtime-copy.js)
16
17
  let raw;
17
18
  try {
18
19
  raw = await fs.readFile(configPath, "utf8");
@@ -1,35 +0,0 @@
1
- // src/core/pkg-manager.js — Agnosticisme des gestionnaires de paquets (Point #3).
2
- // Détection via npm_config_user_agent (renseigné par npm/pnpm/yarn/bun). Jamais
3
- // de conditionnement exclusif à npm : les routines setup/teardown sont 100 %
4
- // Node.js pur — la détection ne sert qu'à l'affichage et à l'aide contextuelle.
5
-
6
- /** @typedef {"npm" | "pnpm" | "yarn" | "bun" | "unknown"} PmName */
7
- /** @typedef {{ name: PmName, label: string }} PmInfo */
8
-
9
- // Étiquettes affichables + équivalent de `npx` par gestionnaire (aide dynamique).
10
- /** @type {{ [k: string]: { label: string, run: string } }} */
11
- const PM_META = {
12
- npm: { label: "npm", run: "npx" },
13
- pnpm: { label: "pnpm", run: "pnpm dlx" },
14
- yarn: { label: "yarn", run: "yarn dlx" },
15
- bun: { label: "bun", run: "bunx" },
16
- };
17
-
18
- // Détecte le gestionnaire actif : npm_config_user_agent commence par "<pm>/<version>".
19
- /** @param {NodeJS.ProcessEnv} [env] @returns {PmInfo} */
20
- export function detectPackageManager(env = process.env) {
21
- const ua = String(env.npm_config_user_agent || "");
22
- const name = (ua.match(/^(npm|pnpm|yarn|bun)\//) || [])[1] || "unknown";
23
- const meta = PM_META[name];
24
- return meta
25
- ? { name: /** @type {PmName} */ (name), label: meta.label }
26
- : { name: "unknown", label: "non détecté" };
27
- }
28
-
29
- // Commande d'exécution équivalente à `npx` pour le gestionnaire détecté.
30
- /** @param {PmInfo} [pm] @returns {string} */
31
- export function pmRunCommand(pm = detectPackageManager()) {
32
- const meta = PM_META[pm.name];
33
- return meta ? meta.run : "npx";
34
- }
35
- // MODULE_COMPLETE