- Pure fetch-based API client (src/) with zero external dependencies - Pi extension adapter (pi-extension/) registering 17 tools - Standalone CLI (cli.ts) replacing gitea-scripts/gitea.js - Token auth everywhere (no HMAC secrets) - SKILL.md for agent auto-discovery - TOOL.md with full parameter reference Consolidates pi-bot/extensions/pi-gitea and clawbot/gitea-scripts into a single shared package.
188 lines
5.8 KiB
JavaScript
188 lines
5.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Gitea CLI — standalone command-line interface
|
|
*
|
|
* Usage: gitea <command> [options]
|
|
* Replaces gitea-scripts/gitea.js with the consolidated core.
|
|
*/
|
|
|
|
import { GiteaClient } from "./src/client.js";
|
|
import * as repos from "./src/repos.js";
|
|
import * as issues from "./src/issues.js";
|
|
import * as pulls from "./src/pulls.js";
|
|
import * as actions from "./src/actions.js";
|
|
import * as webhooks from "./src/webhooks.js";
|
|
import * as files from "./src/files.js";
|
|
|
|
const client = new GiteaClient();
|
|
|
|
function parseArgs(argv: string[]): { positional: string[]; flags: Record<string, string | boolean> } {
|
|
const positional: string[] = [];
|
|
const flags: Record<string, string | boolean> = {};
|
|
for (let i = 0; i < argv.length; i++) {
|
|
if (argv[i].startsWith("--")) {
|
|
const key = argv[i].slice(2);
|
|
const next = argv[i + 1];
|
|
if (next && !next.startsWith("--")) {
|
|
flags[key] = next;
|
|
i++;
|
|
} else {
|
|
flags[key] = true;
|
|
}
|
|
} else {
|
|
positional.push(argv[i]);
|
|
}
|
|
}
|
|
return { positional, flags };
|
|
}
|
|
|
|
function splitOwnerRepo(input: string): { owner: string; repo: string } {
|
|
const parts = input.split("/");
|
|
if (parts.length === 2) return { owner: parts[0], repo: parts[1] };
|
|
return { owner: client.defaultOwner, repo: input };
|
|
}
|
|
|
|
const commands: Record<string, (args: string[]) => Promise<void>> = {
|
|
async whoami() {
|
|
const user = await client.get<{ login: string; email: string }>("/user");
|
|
console.log(JSON.stringify(user, null, 2));
|
|
},
|
|
|
|
async repos(args) {
|
|
const { flags } = parseArgs(args);
|
|
const result = await repos.listRepos(client, {
|
|
owner: flags.owner as string,
|
|
limit: flags.limit ? parseInt(flags.limit as string) : undefined,
|
|
});
|
|
for (const r of result) {
|
|
console.log(`${r.full_name} [${r.private ? "private" : "public"}]`);
|
|
}
|
|
},
|
|
|
|
async "create-repo"(args) {
|
|
const { positional, flags } = parseArgs(args);
|
|
const name = positional[0];
|
|
if (!name) { console.error("Usage: create-repo <name> [--private] [--description ...]"); process.exit(1); }
|
|
const repo = await repos.createRepo(client, {
|
|
name,
|
|
private: !!flags.private,
|
|
description: flags.description as string,
|
|
});
|
|
console.log(`✅ Created: ${repo.html_url}`);
|
|
},
|
|
|
|
async "ensure-repo"(args) {
|
|
const { positional, flags } = parseArgs(args);
|
|
const name = positional[0];
|
|
if (!name) { console.error("Usage: ensure-repo <name> [--private]"); process.exit(1); }
|
|
const { repo, created } = await repos.ensureRepo(client, client.defaultOwner, name, {
|
|
private: !!flags.private,
|
|
});
|
|
console.log(`${created ? "created" : "exists"}: ${repo.clone_url}`);
|
|
},
|
|
|
|
async issues(args) {
|
|
const { positional } = parseArgs(args);
|
|
const target = positional[0];
|
|
if (!target) { console.error("Usage: issues <owner/repo>"); process.exit(1); }
|
|
const { owner, repo } = splitOwnerRepo(target);
|
|
const list = await issues.listIssues(client, owner, repo);
|
|
for (const i of list) {
|
|
console.log(`#${i.number} [${i.state}] ${i.title}`);
|
|
}
|
|
},
|
|
|
|
async "create-issue"(args) {
|
|
const { positional, flags } = parseArgs(args);
|
|
const target = positional[0];
|
|
const title = positional[1];
|
|
if (!target || !title) { console.error("Usage: create-issue <owner/repo> <title> [--body ...]"); process.exit(1); }
|
|
const { owner, repo } = splitOwnerRepo(target);
|
|
const issue = await issues.createIssue(client, owner, repo, {
|
|
title,
|
|
body: flags.body as string,
|
|
});
|
|
console.log(`✅ Issue #${issue.number}: ${issue.html_url}`);
|
|
},
|
|
|
|
async prs(args) {
|
|
const { positional } = parseArgs(args);
|
|
const target = positional[0];
|
|
if (!target) { console.error("Usage: prs <owner/repo>"); process.exit(1); }
|
|
const { owner, repo } = splitOwnerRepo(target);
|
|
const list = await pulls.listPullRequests(client, owner, repo);
|
|
for (const p of list) {
|
|
console.log(`#${p.number} [${p.state}] ${p.title} (${p.head?.label} → ${p.base?.label})`);
|
|
}
|
|
},
|
|
|
|
async runs(args) {
|
|
const { positional } = parseArgs(args);
|
|
const target = positional[0];
|
|
if (!target) { console.error("Usage: runs <owner/repo>"); process.exit(1); }
|
|
const { owner, repo } = splitOwnerRepo(target);
|
|
const { runs } = await actions.listRuns(client, owner, repo);
|
|
for (const r of runs) {
|
|
console.log(`#${r.run_number} [${r.id}] ${r.conclusion ?? r.status} — ${r.display_title} (${r.head_branch})`);
|
|
}
|
|
},
|
|
|
|
async "add-webhook"(args) {
|
|
const { positional, flags } = parseArgs(args);
|
|
const target = positional[0];
|
|
const webhookUrl = positional[1];
|
|
if (!target || !webhookUrl) { console.error("Usage: add-webhook <owner/repo> <url> [--token ...]"); process.exit(1); }
|
|
const { owner, repo } = splitOwnerRepo(target);
|
|
const hook = await webhooks.createWebhook(client, owner, repo, {
|
|
url: webhookUrl,
|
|
token: flags.token as string,
|
|
});
|
|
console.log(`✅ Webhook created: ${hook.id}`);
|
|
},
|
|
|
|
async help() {
|
|
console.log(`
|
|
Gitea CLI — ${client.url}
|
|
|
|
Commands:
|
|
whoami Show current user
|
|
repos [--owner ...] [--limit N] List repos
|
|
create-repo <name> [--private] [--description "..."]
|
|
ensure-repo <name> [--private] Create if not exists
|
|
issues <owner/repo> List issues
|
|
create-issue <owner/repo> <title> [--body "..."]
|
|
prs <owner/repo> List pull requests
|
|
runs <owner/repo> List workflow runs
|
|
add-webhook <owner/repo> <url> [--token "..."]
|
|
|
|
Env: GITEA_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO
|
|
`);
|
|
},
|
|
};
|
|
|
|
async function main() {
|
|
if (!client.token) {
|
|
console.error("❌ No GITEA_TOKEN found. Set GITEA_TOKEN in environment.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const cmd = process.argv[2];
|
|
if (!cmd || cmd === "--help" || cmd === "help") {
|
|
await commands.help([]);
|
|
return;
|
|
}
|
|
|
|
const fn = commands[cmd];
|
|
if (!fn) {
|
|
console.error(`Unknown command: ${cmd}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
await fn(process.argv.slice(3));
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("Error:", err.message);
|
|
process.exit(1);
|
|
});
|