Connectez votre agent d’automatisation des opérations à votre code
Copiez-collez du code pour relier votre agent d’automatisation des opérations à votre dépôt GitHub ou à un dossier sur votre ordinateur. Avant tout envoi au modèle d’IA, un filtre de confidentialité remplace les mots de passe, clés, adresses e-mail, numéros de téléphone et autres données privées par des marqueurs, puis rétablit les valeurs réelles dans la réponse, de votre côté.
1 Ajoutez les fichiers
Créez un dossier agent/ dans votre dépôt et enregistrez-y privacy-filter.ts et agent-github.ts.
2 Ajoutez votre clé IA
Dans GitHub : Settings → Secrets and variables → Actions → ajoutez AI_API_KEY. Définissez AI_BASE_URL et AI_MODEL pour utiliser un fournisseur compatible avec OpenAI.
3 Activer la GitHub Action
Enregistrez le workflow dans .github/workflows/ops-automation-agent.yml. S’exécute chaque lundi (ou manuellement) et enregistre un rapport pour vérification humaine.
agent/agent-github.tsLit votre dépôt GitHub, masque les données privées, interroge le modèle et enregistre ou publie la réponse.
/**
* Ops automation agent: reads your GitHub repository, scrubs secrets and personal data, then asks the AI model.
*
* npm install @octokit/rest tsx
* GITHUB_TOKEN=… GITHUB_REPOSITORY=owner/repo AI_API_KEY=… npx tsx agent/agent-github.ts
*
* Optional: AI_BASE_URL and AI_MODEL (any OpenAI-compatible API), PR_NUMBER or ISSUE_NUMBER
* (the GitHub Action sets these for you).
*/
import { writeFileSync } from "node:fs";
import { Octokit } from "@octokit/rest";
import { isSensitivePath, restore, scrub, type Vault } from "./privacy-filter";
const AGENT = {
"label": "Ops automation agent",
"system": "You are a ops automation agent. Your job: find repetitive operational steps and propose automations. Text like [EMAIL_1] or [API_KEY_2] is a placeholder for private data that was removed before you saw it. Keep placeholders exactly as written and never guess the real values.",
"task": "List the manual steps in our runbooks that could be automated, with the time each would save.",
"include": [
"runbooks/",
"scripts/",
"docs/"
],
"baseUrl": "https://api.openai.com/v1",
"model": "gpt-5-mini",
"keyFallback": "",
"autoPost": false,
"audit": false
};
const MAX_CHARS = 60_000;
const BINARY = /\.(png|jpe?g|gif|webp|ico|pdf|zip|gz|mp4|mp3|woff2?|lock)$/i;
function env(key: string, fallback?: string): string {
const value = process.env[key] || fallback;
if (!value) throw new Error(`Missing environment variable ${key}`);
return value;
}
const [owner, repo] = env("GITHUB_REPOSITORY").split("/") as [string, string];
const gh = new Octokit({ auth: env("GITHUB_TOKEN") });
const vault: Vault = new Map(); // placeholder → real value; never leaves this machine
const wanted = (path: string) =>
!isSensitivePath(path) && !BINARY.test(path) && AGENT.include.some((p) => path === p || path.startsWith(p));
async function readFile(path: string): Promise<string> {
const { data } = await gh.repos.getContent({ owner, repo, path });
if (Array.isArray(data) || data.type !== "file" || !("content" in data)) return "";
return Buffer.from(data.content, "base64").toString("utf8");
}
async function collectContext(): Promise<string> {
const parts: string[] = [];
const size = () => parts.reduce((n, p) => n + p.length, 0);
if (process.env.PR_NUMBER) {
const files = await gh.paginate(gh.pulls.listFiles, { owner, repo, pull_number: Number(process.env.PR_NUMBER), per_page: 100 });
for (const f of files) if (!isSensitivePath(f.filename) && f.patch) parts.push(`### Changed: ${f.filename}\n${f.patch}`);
}
if (process.env.ISSUE_NUMBER) {
const { data } = await gh.issues.get({ owner, repo, issue_number: Number(process.env.ISSUE_NUMBER) });
parts.push(`### Issue #${data.number}: ${data.title}\n${data.body ?? ""}`);
}
const { data: info } = await gh.repos.get({ owner, repo });
const { data: tree } = await gh.git.getTree({ owner, repo, tree_sha: info.default_branch, recursive: "true" });
for (const item of tree.tree) {
if (size() > MAX_CHARS) break;
if (item.type !== "blob" || !item.path || (item.size ?? 0) > 200_000 || !wanted(item.path)) continue;
parts.push(`### ${item.path}\n${await readFile(item.path)}`);
}
return parts.join("\n\n").slice(0, MAX_CHARS);
}
async function askModel(context: string) {
const safe = scrub(context, { vault }); // the model only ever sees safe.text
const res = await fetch(`${env("AI_BASE_URL", AGENT.baseUrl)}/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${env("AI_API_KEY", AGENT.keyFallback)}` },
body: JSON.stringify({
model: env("AI_MODEL", AGENT.model),
messages: [
{ role: "system", content: AGENT.system },
{ role: "user", content: `${AGENT.task}\n\n${safe.text}` },
],
}),
});
if (!res.ok) throw new Error(`AI model error ${res.status}: ${await res.text()}`);
const json = (await res.json()) as { choices: { message: { content: string } }[] };
return { reply: json.choices[0]?.message.content ?? "", findings: safe.findings };
}
async function main() {
const { reply, findings } = await askModel(await collectContext());
console.log("Privacy filter hid:", findings.map((f) => `${f.count} × ${f.label}`).join(", ") || "nothing");
writeFileSync("agent-report.md", restore(reply, vault)); // real values, for you only
writeFileSync("agent-report.safe.md", reply); // placeholders, safe to share
if (AGENT.audit) writeFileSync("privacy-audit.json", JSON.stringify({ at: new Date().toISOString(), findings }, null, 2));
const target = Number(process.env.PR_NUMBER || process.env.ISSUE_NUMBER || 0);
if (target && AGENT.autoPost) {
// Post the scrubbed version so private values never end up in a public comment.
await gh.issues.createComment({ owner, repo, issue_number: target, body: reply });
console.log(`Commented on #${target}`);
} else {
console.log("Saved agent-report.md. A person should review it before anything is sent.");
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
Voir ce que verrait votre agent d’automatisation des opérations
Collez du code, un fichier de configuration ou un message client. À gauche, votre contenu ; à droite, uniquement ce que le modèle d’IA verrait.
// config.ts export const db = "[CONNECTION_STRING_1]"; const apiKey = "[API_KEY_1]"; STRIPE_SECRET_KEY=[API_KEY_2] // Ticket from [EMAIL_1], phone [PHONE_1]