#!/usr/bin/env node /** * GODCALL — bulk-import every Gitea repo as a CodeGraph. * * Enumerates repos via the Gitea API and registers each with the knowledge * service. Clone auth is handled by the git credential store mounted into the * memory-hub container (see start-memory-hub.sh GIT_CREDENTIALS_FILE) — tokens * never go into repo URLs. * * Env: * GITEA_URL e.g. http://100.71.119.27:3000 * GITEA_TOKEN_FILE file containing a Gitea access token (read:repository) * KNOWLEDGE_URL e.g. http://100.91.239.7:8424 * TEAM_ID godcall team id (team-...) * USER_ID godcall user id (usr-...) * GITEA_CLONE_BASE optional override when Gitea ROOT_URL differs from the * address reachable from the hub container * ONLY optional comma-separated repo names to import * SKIP optional comma-separated repo names to skip */ import { readFileSync } from "node:fs"; const need = (k) => { const v = process.env[k]; if (!v) { console.error(`missing env ${k}`); process.exit(1); } return v; }; const GITEA_URL = need("GITEA_URL").replace(/\/$/, ""); const TOKEN = readFileSync(need("GITEA_TOKEN_FILE"), "utf8").trim(); const KNOWLEDGE_URL = need("KNOWLEDGE_URL").replace(/\/$/, ""); const TEAM_ID = need("TEAM_ID"); const USER_ID = need("USER_ID"); const CLONE_BASE = (process.env.GITEA_CLONE_BASE || GITEA_URL).replace(/\/$/, ""); const ONLY = (process.env.ONLY || "").split(",").map(s => s.trim()).filter(Boolean); const SKIP = new Set((process.env.SKIP || "").split(",").map(s => s.trim()).filter(Boolean)); async function giteaRepos() { const repos = []; for (let page = 1; ; page++) { const r = await fetch(`${GITEA_URL}/api/v1/repos/search?limit=50&page=${page}`, { headers: { Authorization: `token ${TOKEN}` }, }); if (!r.ok) throw new Error(`gitea search ${r.status}: ${await r.text()}`); const { data } = await r.json(); if (!data?.length) break; repos.push(...data); if (data.length < 50) break; } return repos; } async function createCodeGraph(repo) { const repoUrl = `${CLONE_BASE}/${repo.full_name}.git`; const body = { team_id: TEAM_ID, user_id: USER_ID, repo_url: repoUrl, branch: repo.default_branch || "main", repo_name: repo.name, }; const r = await fetch(`${KNOWLEDGE_URL}/v3/code-graph/create`, { method: "POST", headers: { "Content-Type": "application/json", "x-tdai-service-id": "default" }, body: JSON.stringify(body), }); const json = await r.json().catch(() => ({})); return { status: r.status, id: json?.data?.code_graph_id, msg: json?.message }; } const repos = await giteaRepos(); console.log(`gitea reports ${repos.length} repos`); let ok = 0, fail = 0; for (const repo of repos) { if (ONLY.length && !ONLY.includes(repo.name)) continue; if (SKIP.has(repo.name)) { console.log(`skip ${repo.full_name}`); continue; } try { const res = await createCodeGraph(repo); const good = res.status === 200 || res.status === 201; good ? ok++ : fail++; console.log(`${good ? "ok " : "FAIL"} ${repo.full_name} [${repo.default_branch}] -> ${res.id ?? res.msg ?? res.status}`); } catch (e) { fail++; console.log(`FAIL ${repo.full_name}: ${e.message}`); } } console.log(`done: ${ok} registered, ${fail} failed`);