feat: consolidated Gitea API client and pi extension
- 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.
This commit is contained in:
187
cli.ts
Normal file
187
cli.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in New Issue
Block a user