Add AgentOS daemon control plane and maintenance tooling

This commit is contained in:
-Puter
2026-07-19 06:29:51 +05:30
parent a37e0cc3c9
commit ec84c52155
621 changed files with 97578 additions and 15 deletions

282
scripts/update-docs.ts Normal file
View File

@@ -0,0 +1,282 @@
import { parseArgs } from "node:util";
const excludedPrefixes = [
".agents/",
".claude/skills/",
"node_modules/",
"repos/",
"dist/",
"build/",
"apps/web/build/",
"apps/web/.react-router/",
"apps/native/.expo/",
"apps/native/dist/",
"apps/native/web-build/",
"packages/backend/convex/_generated/",
];
const documentationNames = new Set([
"AGENTS.md",
"README.md",
"README.mdx",
"LLMS.md",
]);
const supportedAgents = ["codex", "opencode", "claude"] as const;
type AgentName = (typeof supportedAgents)[number];
interface CommandResult {
readonly exitCode: number;
readonly stdout: string;
readonly stderr: string;
}
const run = async (
command: readonly string[],
options: { readonly stdin?: string; readonly inherit?: boolean } = {}
): Promise<CommandResult> => {
const process = Bun.spawn([...command], {
cwd: processCwd,
stdin: options.stdin === undefined ? "ignore" : new Blob([options.stdin]),
stdout: options.inherit ? "inherit" : "pipe",
stderr: options.inherit ? "inherit" : "pipe",
});
const [exitCode, stdout, stderr] = await Promise.all([
process.exited,
options.inherit ? Promise.resolve("") : new Response(process.stdout).text(),
options.inherit ? Promise.resolve("") : new Response(process.stderr).text(),
]);
return { exitCode, stdout, stderr };
};
const shellQuote = (value: string) => `'${value.replaceAll("'", `'\\''`)}'`;
const formatCommand = (command: readonly string[]) =>
command.map(shellQuote).join(" ");
const isExcluded = (path: string) =>
excludedPrefixes.some((prefix) => {
const directory = prefix.slice(0, -1);
return (
path === directory ||
path.startsWith(prefix) ||
path.includes(`/${directory}/`) ||
path.endsWith(`/${directory}`)
);
});
const isDocumentation = (path: string) => {
const name = path.split("/").at(-1) ?? path;
return (
documentationNames.has(name) ||
name.endsWith(".md") ||
name.endsWith(".mdx")
);
};
const resolveRange = async (revision: string | undefined) => {
const requested = revision ?? "HEAD";
if (requested.includes("..")) {
const result = await run(["git", "rev-list", "--max-count=1", requested]);
if (result.exitCode !== 0 || result.stdout.trim() === "") {
throw new Error(
`Invalid revision range ${shellQuote(requested)}: ${result.stderr.trim()}`
);
}
return requested;
}
const verify = await run([
"git",
"rev-parse",
"--verify",
`${requested}^{commit}`,
]);
if (verify.exitCode !== 0) {
throw new Error(
`Invalid commit ${shellQuote(requested)}: ${verify.stderr.trim()}`
);
}
return `${requested}^!`;
};
const listChangedFiles = async (range: string) => {
const result = await run([
"git",
"diff",
"--name-only",
"--diff-filter=ACDMRTUXB",
range,
"--",
]);
if (result.exitCode !== 0) {
throw new Error(
`Unable to inspect ${shellQuote(range)}: ${result.stderr.trim()}`
);
}
return result.stdout
.split("\n")
.map((path) => path.trim())
.filter((path) => path !== "" && !isExcluded(path));
};
const listDocumentationFiles = async () => {
const result = await run(["git", "ls-files", "*.md", "*.mdx"]);
if (result.exitCode !== 0) {
throw new Error(
`Unable to list documentation files: ${result.stderr.trim()}`
);
}
return result.stdout
.split("\n")
.map((path) => path.trim())
.filter(
(path) => path !== "" && !isExcluded(path) && isDocumentation(path)
);
};
const resolveAgent = (requested: string | undefined): AgentName => {
const configured = requested ?? process.env.DOCS_AGENT;
if (configured !== undefined) {
if (!supportedAgents.includes(configured as AgentName)) {
throw new Error(
`Unsupported agent ${shellQuote(configured)}. Use codex, opencode, or claude.`
);
}
if (Bun.which(configured) === null) {
throw new Error(
`Agent executable ${shellQuote(configured)} was not found on PATH.`
);
}
return configured as AgentName;
}
const detected = supportedAgents.find((agent) => Bun.which(agent) !== null);
if (detected === undefined) {
throw new Error(
"No supported coding agent found. Install codex, opencode, or claude, or set DOCS_AGENT."
);
}
return detected;
};
const agentCommand = (agent: AgentName) => {
switch (agent) {
case "codex":
return [
"codex",
"exec",
"-",
"--sandbox",
"workspace-write",
"--cd",
processCwd,
"--ephemeral",
];
case "opencode":
return ["opencode", "run", "--auto"];
case "claude":
return ["claude", "--print", "--permission-mode", "acceptEdits"];
}
};
const buildPrompt = (input: {
readonly range: string;
readonly changedFiles: readonly string[];
readonly documentationFiles: readonly string[];
readonly diff: string;
}) => `Update repository documentation for revision ${input.range} in the current checkout.
Requirements:
- Read and follow the root AGENTS.md and any more specific AGENTS.md governing files you edit.
- Inspect the supplied diff and the current code before editing; do not document behavior that is not implemented.
- Update affected README.md, AGENTS.md, LLMS.md, or other first-party Markdown/MDX files when the revision made them stale.
- Keep documentation concise and practical. Preserve generated markers and package-specific instructions.
- Never edit repos/**, .agents/**, .claude/skills/**, generated files, or dependency/build output.
- Do not modify source code, run broad formatting, or commit changes.
- If documentation is already accurate, make no edits and say so.
Changed first-party files:
${input.changedFiles.map((path) => `- ${path}`).join("\n") || "- None"}
First-party documentation candidates:
${input.documentationFiles.map((path) => `- ${path}`).join("\n") || "- None"}
Revision diff:
\`\`\`diff
${input.diff}
\`\`\`
`;
const processCwd = process.cwd();
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
options: {
agent: { type: "string" },
"dry-run": { type: "boolean", default: false },
help: { type: "boolean", short: "h", default: false },
},
allowPositionals: true,
strict: true,
});
if (values.help || positionals.length > 1) {
console.log(`Usage: bun run docs:update [<commit-or-range>] [--agent codex|opencode|claude] [--dry-run]
A single commit defaults to HEAD and is converted to <commit>^!. Revision ranges such as main..HEAD and main...HEAD are accepted directly. Set DOCS_AGENT to choose the default coding-agent CLI.`);
process.exit(positionals.length > 1 ? 1 : 0);
}
try {
const range = await resolveRange(positionals[0]);
const [changedFiles, documentationFiles, diffResult] = await Promise.all([
listChangedFiles(range),
listDocumentationFiles(),
run([
"git",
"diff",
"--binary",
"--find-renames",
range,
"--",
".",
":(exclude)repos/**",
]),
]);
if (diffResult.exitCode !== 0) {
throw new Error(
`Unable to read revision diff: ${diffResult.stderr.trim()}`
);
}
const agent = resolveAgent(values.agent);
const command = agentCommand(agent);
const prompt = buildPrompt({
range,
changedFiles,
documentationFiles,
diff: diffResult.stdout,
});
console.log(`Revision: ${range}`);
console.log(`Agent: ${agent}`);
console.log(`Changed first-party files: ${changedFiles.length}`);
console.log(`Command: ${formatCommand(command)} < generated-prompt`);
if (values["dry-run"]) {
console.log("\n--- Generated prompt ---\n");
console.log(prompt);
process.exit(0);
}
const result = await run(command, { stdin: prompt, inherit: true });
if (result.exitCode !== 0) {
process.exit(result.exitCode);
}
const status = await run(["git", "status", "--short", "--", "*.md", "*.mdx"]);
if (status.stdout.trim() !== "") {
console.log("\nDocumentation changes:");
console.log(status.stdout.trimEnd());
} else {
console.log("\nNo documentation files changed.");
}
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}