import { loadSync } from "jsr:@std/dotenv"; // Sandbox provisioning loads its OWN .env.sandbox; prod config stays in .env (main.ts). loadSync({ envPath: ".env.sandbox", export: true }); import { chromium } from "playwright"; import path from "node:path"; import login from "./scripts/login.ts"; import moduleSetup from "./scripts/admin/moduleSetup.ts"; import userSetup from "./scripts/admin/userSetup.ts"; /* provisionSandbox.ts — separate entrypoint (does NOT touch main.ts). Provisions the erp-sandbox Dolibarr so the AI agent can write to it: 1. enable the REST API / Web services module, 2. create a write-scoped `ai_agent_sandbox` user (non-admin), 3. grant it the write rights it needs, 4. have Dolibarr generate its API key and emit it safely to test/.ai_agent_sandbox.key (gitignored). Run: cd test && deno run --allow-all provisionSandbox.ts CANNOT be run end-to-end yet: the sandbox Dolibarr is not installed/provisioned (empty DB, fresh install wizard). Selector correctness in moduleSetup.ts / userSetup.ts is therefore best-effort and must be verified on the first real run, AFTER the install wizard has been completed against the sandbox. NEXT STEP (not implemented here): load the generated key into the dolibarr skill's sandbox config / Vault at kvv2/erp-sandbox/ai_agent. We intentionally do NOT write to Vault from this POC. */ /* Write rights to grant `ai_agent_sandbox` (stable Dolibarr rights ids, verified against prod Dolibarr 22.0.4). Each module's read (lire) + create (creer): facture: lire=11, creer=12 societe: lire=121, creer=122, client voir (see-all)=262 societe contact: lire=281, creer=282 fournisseur: lire=1181, facture lire=1231, facture creer=1232 produit: lire=31, creer=32 */ const WRITE_IDS = [ 11, // facture lire 12, // facture creer 121, // societe lire 122, // societe creer 262, // societe client "voir" (see-all) — REQUIRED for the /thirdparties LIST // endpoint; without it the list 404s (the voir_tous ACL trap, cf. the // dolibarr skill SKILL.md). Other modules' lists work with plain `lire`. 281, // societe contact lire 282, // societe contact creer 1181, // fournisseur lire 1231, // fournisseur facture lire 1232, // fournisseur facture creer 31, // produit lire 32, // produit creer ]; const KEY_FILE = ".ai_agent_sandbox.key"; /* Initialisation — mirrors main.ts but targeted at the sandbox. The default address points at the sandbox; admin creds come from env (the same DOLI_ADMIN_LOGIN / DOLI_ADMIN_PASSWORD vars main.ts uses, populated from the erp-sandbox namespace secrets — see test/README.md "Provision the sandbox"). */ const dolibarrAddress = Deno.env.get("DOLIBARR_ADDRESS") || "https://erp-sandbox.arcodange.lab"; const debug = true; const DBpassword = Deno.env.get("DOLI_DB_PASSWORD") || "undefined"; const adminCredentials = { username: Deno.env.get("DOLI_ADMIN_LOGIN") || "admin", password: Deno.env.get("DOLI_ADMIN_PASSWORD") || "undefined", }; // The new user's login is fixed; its password comes from env or is generated. const AI_AGENT_LOGIN = "ai_agent_sandbox"; const aiAgentPassword = Deno.env.get("AI_AGENT_SANDBOX_PASSWORD") || generatePassword(); const rootFolderPath = Deno.env.get("ROOT_FOLDER") || path.join(Deno.cwd(), ".."); const imgFolderPath = Deno.env.get("IMG_FOLDER") || path.join(rootFolderPath, "static/img"); const configFolderPath = Deno.env.get("CONFIG_FOLDER") || path.join(rootFolderPath, "static/config"); /* Generate a reasonably strong random password (used only if none provided). */ function generatePassword(): string { const bytes = new Uint8Array(18); crypto.getRandomValues(bytes); // URL-safe base64 → no shell-hostile characters. return btoa(String.fromCharCode(...bytes)) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); } const browser = await chromium.launch({ headless: false, logger: { isEnabled: (_name, _severity) => debug, log: (name, severity, message, args) => console.warn(`${severity}| ${name} :: ${message} __ ${args}`), }, }); const context = await browser.newContext({ locale: "fr-FR" }); const page = await context.newPage(); const globalCtx = { dolibarrAddress, DBpassword, adminCredentials, debug, rootFolderPath, imgFolderPath, configFolderPath, browser, page, context, }; try { await login.doAdminLogin(globalCtx); console.log(`connected as ${await login.whoAmI(globalCtx)}`); await moduleSetup.enableApiModule(globalCtx); console.log("REST API module enabled"); const userId = await userSetup.createUser(globalCtx, { login: AI_AGENT_LOGIN, password: aiAgentPassword, lastname: "AI Agent (sandbox)", admin: false, }); console.log(`created user '${AI_AGENT_LOGIN}' (id=${userId})`); await userSetup.assignRights(globalCtx, userId, WRITE_IDS); console.log(`granted ${WRITE_IDS.length} write rights to id=${userId}`); const apiKey = await userSetup.generateApiKey(globalCtx, userId); // Emit the key safely: write it to a gitignored file rather than printing it. await Deno.writeTextFile(KEY_FILE, apiKey + "\n"); console.log(`AI_AGENT_SANDBOX API KEY WRITTEN TO test/${KEY_FILE}`); } catch (error) { console.error(error); } finally { await browser.close(); }