- Translate panel UI (~500 strings) and all extraction/wiki prompts to English; rebrand to GODCALL (title, logo, login, header) - git-fetcher: allow http:// clone URLs behind KNOWLEDGE_ALLOW_HTTP=1 (tailnet Gitea) - MemoryCore/Dockerfile: drop-in image (upstream ships none) with TDAI_GATEWAY_CONFIG env + healthcheck; combined-hub Dockerfile: stock Debian mirrors; stub MemoryKnowledge openapi.yaml (absent upstream but COPYed) - start-memory-hub.sh: optional GIT_CREDENTIALS_FILE mount (keeps tokens out of repo URLs); tools/import-gitea.mjs: bulk CodeGraph import of all Gitea repos Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
88 lines
3.3 KiB
JavaScript
88 lines
3.3 KiB
JavaScript
#!/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`);
|