feat(config): add gmgn-cli config command for API Key onboarding

Add a new `gmgn-cli config` command that automates Ed25519 key pair
generation for new users. The command reuses an existing keypair.pem if
present, or generates a new one, then outputs a pre-filled API Key
creation link with the public key embedded. Guidance text is output in
the user's system locale (zh-CN, zh-TW, or English).

All 6 SKILL.md files are updated with a BEFORE RUNNING ANY COMMAND
block that detects missing GMGN_API_KEY, triggers gmgn-cli config, and
instructs the Agent to write both GMGN_API_KEY and GMGN_PRIVATE_KEY
into ~/.config/gmgn/.env once the user provides their API Key.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
gina888666
2026-06-26 21:39:57 +08:00
co-authored by Claude Sonnet 4.6
parent 845a8aaa62
commit e8af4b88b5
8 changed files with 124 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
import { Command } from "commander";
import * as crypto from "crypto";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
const GMGN_CONFIG_DIR = path.join(os.homedir(), ".config", "gmgn");
const KEYPAIR_FILE = path.join(GMGN_CONFIG_DIR, "keypair.pem");
const GMGN_API_URL = "https://gmgn.ai/ai/generateapi";
export function registerConfigCommands(program: Command): void {
program
.command("config")
.description("Generate an Ed25519 key pair and output a pre-filled GMGN API Key creation link")
.action(async () => {
fs.mkdirSync(GMGN_CONFIG_DIR, { recursive: true });
let publicPem: string;
if (fs.existsSync(KEYPAIR_FILE)) {
// Reuse existing key pair
const content = fs.readFileSync(KEYPAIR_FILE, "utf-8");
const match = content.match(/(-----BEGIN PUBLIC KEY-----[\s\S]+?-----END PUBLIC KEY-----)/);
if (!match) {
console.error("Error: keypair.pem exists but public key could not be parsed. Delete ~/.config/gmgn/keypair.pem and try again.");
process.exit(1);
}
publicPem = match[1] + "\n";
} else {
// Generate new Ed25519 key pair
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
const privatePem = privateKey.export({ type: "pkcs8", format: "pem" }) as string;
publicPem = publicKey.export({ type: "spki", format: "pem" }) as string;
const entry = `# Private Key\n${privatePem}\n# Public Key\n${publicPem}\n`;
fs.writeFileSync(KEYPAIR_FILE, entry, { mode: 0o600 });
}
const link = `${GMGN_API_URL}?pbk=${encodeURIComponent(publicPem)}`;
const locale = (process.env.LANG ?? process.env.LC_ALL ?? process.env.LC_MESSAGES ?? "").toLowerCase();
let message: string;
if (locale.startsWith("zh_tw") || locale.startsWith("zh_hk")) {
message = "請點擊連結建立你的 GMGN API Key,完成後將 Key 發給我,我來幫你完成配置:";
} else if (locale.startsWith("zh")) {
message = "请点击链接创建你的 GMGN API Key,完成后将 Key 发给我,我来帮你完成配置:";
} else {
message = "Please click the link below to create your GMGN API Key. Once created, send me the API Key and I will finish the configuration:";
}
console.log("");
console.log(message);
console.log(link);
console.log("");
});
}
+2
View File
@@ -11,6 +11,7 @@ import { registerPortfolioCommands } from "./commands/portfolio.js";
import { registerTrackCommands } from "./commands/track.js";
import { registerSwapCommands } from "./commands/swap.js";
import { registerCookingCommands } from "./commands/cooking.js";
import { registerConfigCommands } from "./commands/config.js";
const proxy = process.env.HTTPS_PROXY ?? process.env.https_proxy
?? process.env.HTTP_PROXY ?? process.env.http_proxy;
@@ -60,6 +61,7 @@ registerPortfolioCommands(program);
registerTrackCommands(program);
registerSwapCommands(program);
registerCookingCommands(program);
registerConfigCommands(program);
program.parseAsync().catch((err) => {
console.error(`[gmgn-cli] ${err.message}`);