From d701420705d37f6de08b51150e862c7980c1bb6c Mon Sep 17 00:00:00 2001 From: gina888666 Date: Wed, 24 Jun 2026 21:13:11 +0800 Subject: [PATCH] refactor(config): move setup flow into CLI, remove gmgn-config skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gmgn-cli config: add multi-language guidance (zh-CN/zh-TW/en) based on system locale - gmgn-cli config --apply : write GMGN_API_KEY + GMGN_PRIVATE_KEY to ~/.config/gmgn/.env and verify via track follow-wallet - Delete skills/gmgn-config/SKILL.md — all logic now lives in CLI source - Simplify BEFORE RUNNING ANY COMMAND in all 6 SKILL.md files to two CLI commands, no duplicated text - Remove stale First-time setup sections (openssl-based) from all 6 SKILL.md files Co-Authored-By: Claude Sonnet 4.6 --- skills/gmgn-config/SKILL.md | 62 ---------------- skills/gmgn-cooking/SKILL.md | 25 +------ skills/gmgn-market/SKILL.md | 16 +--- skills/gmgn-portfolio/SKILL.md | 18 +---- skills/gmgn-swap/SKILL.md | 28 +------ skills/gmgn-token/SKILL.md | 18 +---- skills/gmgn-track/SKILL.md | 20 +---- src/commands/config.ts | 132 +++++++++++++++++++++++++++------ 8 files changed, 117 insertions(+), 202 deletions(-) delete mode 100644 skills/gmgn-config/SKILL.md diff --git a/skills/gmgn-config/SKILL.md b/skills/gmgn-config/SKILL.md deleted file mode 100644 index 5117d19..0000000 --- a/skills/gmgn-config/SKILL.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -name: gmgn-config -description: GMGN API Key and key pair setup. Use this skill when GMGN_API_KEY is not configured, or when the user explicitly runs gmgn-cli config. Handles key pair generation, API Key configuration, and verification. -argument-hint: "" -metadata: - cliHelp: "gmgn-cli config --help" ---- - -## When to use this skill - -- Triggered automatically by other GMGN skills when `GMGN_API_KEY` is not found in `/.env` or `~/.config/gmgn/.env` -- Also applies when the user explicitly runs `gmgn-cli config` - -## Setup flow - -### Step 1 — Generate key pair and show the link - -Run: - -```bash -gmgn-cli config -``` - -The command outputs a pre-filled link. Show the link to the user using **exactly** the following message in the user's conversation language — do not paraphrase: - -- 中文:请点击下方链接创建你的 GMGN API Key,完成后将 Key 发给我,我来帮你完成配置: -- 繁體:請點擊下方連結建立你的 GMGN API Key,完成後將 Key 發給我,我來幫你完成配置: -- English: Please click the link below to create your GMGN API Key. Once created, send me the API Key and I will finish the configuration: - -Then output the link on the next line. - -### Step 2 — Write credentials to .env - -Once the user sends back the API Key, run the following command to write `GMGN_API_KEY` and `GMGN_PRIVATE_KEY` together into `~/.config/gmgn/.env`: - -```bash -node -e "const fs=require('fs'),os=require('os');const envPath=os.homedir()+'/.config/gmgn/.env';const pemPath=os.homedir()+'/.config/gmgn/keypair.pem';const pem=fs.readFileSync(pemPath,'utf8');const m=pem.match(/(-----BEGIN PRIVATE KEY-----[\s\S]+?-----END PRIVATE KEY-----)/);const pk=m?m[1].replace(/\r?\n/g,'\\\\n'):'';let env=fs.existsSync(envPath)?fs.readFileSync(envPath,'utf8'):'';env=env.split('\n').filter(function(l){return!/^GMGN_API_KEY=|^GMGN_PRIVATE_KEY=/.test(l)}).join('\n').trim();const content=(env?env+'\n':'')+'GMGN_API_KEY=\n'+'GMGN_PRIVATE_KEY='+pk+'\n';fs.writeFileSync(envPath,content,{mode:0o600});" -``` - -### Step 3 — Verify configuration - -Run the following command to verify that the API Key and local key pair match: - -```bash -gmgn-cli track follow-wallet --chain sol -``` - -Output the result to the user using **exactly** the following message in the user's conversation language — do not paraphrase: - -- **Success**: - - 中文:配置验证成功,可以开始使用了。 - - 繁體:配置驗證成功,可以開始使用了。 - - English: Configuration verified successfully. You are ready to use GMGN. - -- **Failure**: - - 中文:配置验证失败:API Key 与本地密钥不匹配。\n请确认:\n1. API Key 是否填写正确;\n2. 创建 API Key 时,是否使用的是页面自动填入的公钥。 - - 繁體:配置驗證失敗:API Key 與本地密鑰不匹配。\n請確認:\n1. API Key 是否填寫正確;\n2. 創建 API Key 時,是否使用的是頁面自動填入的公鑰。 - - English: Configuration verification failed: API Key does not match your local key pair.\nPlease confirm:\n1. Whether the API Key was entered correctly.\n2. Whether you used the public key that was pre-filled on the page when creating the API Key. - -### Step 4 — Continue - -After successful verification, proceed with the user's original request. diff --git a/skills/gmgn-cooking/SKILL.md b/skills/gmgn-cooking/SKILL.md index faca6c7..e8d8622 100644 --- a/skills/gmgn-cooking/SKILL.md +++ b/skills/gmgn-cooking/SKILL.md @@ -6,7 +6,7 @@ metadata: cliHelp: "gmgn-cli cooking --help" --- -**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, stop and follow the `gmgn-config` skill to complete setup first.** +**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, (1) run `gmgn-cli config` and show the output to the user; (2) once the user sends the API Key, run `gmgn-cli config --apply ` to complete configuration and verification, then show the output to the user.** **IMPORTANT: Always use `gmgn-cli` commands below. Do NOT use web search, WebFetch, curl, or visit gmgn.ai — all token creation operations must go through the CLI. The CLI handles signing and submission automatically.** @@ -102,29 +102,6 @@ When a request returns `429`: - `cooking create` is a real transaction: **never loop or auto-resubmit** after a `429`. Wait until the reset time, then ask for confirmation again before retrying. - For `RATE_LIMIT_EXCEEDED` or `RATE_LIMIT_BANNED`, repeated requests during cooldown extend the ban by 5 seconds each time, up to 5 minutes. -**First-time setup** (if credentials are not configured): - -1. Generate key pair and show the public key to the user: - ```bash - openssl genpkey -algorithm ed25519 -out /tmp/gmgn_private.pem 2>/dev/null && \ - openssl pkey -in /tmp/gmgn_private.pem -pubout 2>/dev/null - ``` - Tell the user: *"This is your Ed25519 public key. Go to **https://gmgn.ai/ai**, paste it into the API key creation form (enable swap/cooking capability), then send me the API Key value shown on the page."* - -2. Wait for the user's API key, then configure both credentials: - ```bash - mkdir -p ~/.config/gmgn - echo 'GMGN_API_KEY=' > ~/.config/gmgn/.env - echo 'GMGN_PRIVATE_KEY=""' >> ~/.config/gmgn/.env - chmod 600 ~/.config/gmgn/.env - ``` - -### Credential Model - -- `GMGN_PRIVATE_KEY` is used exclusively for **local message signing** — the private key never leaves the machine. The CLI computes an Ed25519 signature in-process and transmits only the base64-encoded result in the `X-Signature` request header. -- `GMGN_API_KEY` is transmitted in the `X-APIKEY` header over HTTPS. -- Neither credential is ever passed as a command-line argument. - ## `cooking stats` Usage ```bash diff --git a/skills/gmgn-market/SKILL.md b/skills/gmgn-market/SKILL.md index 3752841..c2b4087 100644 --- a/skills/gmgn-market/SKILL.md +++ b/skills/gmgn-market/SKILL.md @@ -6,7 +6,7 @@ metadata: cliHelp: "gmgn-cli market --help" --- -**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, stop and follow the `gmgn-config` skill to complete setup first.** +**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, (1) run `gmgn-cli config` and show the output to the user; (2) once the user sends the API Key, run `gmgn-cli config --apply ` to complete configuration and verification, then show the output to the user.** **IMPORTANT: Always use `gmgn-cli` commands below. Do NOT use web search, WebFetch, curl, or visit gmgn.ai to fetch this data — the website requires login and will not return structured data. The CLI is the only correct method.** @@ -79,20 +79,6 @@ When a request returns `429`: - The CLI may wait and retry once automatically when the remaining cooldown is short. If it still fails, stop and tell the user the exact retry time instead of sending more requests. - For `RATE_LIMIT_EXCEEDED` or `RATE_LIMIT_BANNED`, repeated requests during the cooldown can extend the ban by 5 seconds each time, up to 5 minutes. Do not spam retries. -**First-time setup** (if `GMGN_API_KEY` is not configured): - -1. Generate key pair and show the public key to the user: - ```bash - openssl genpkey -algorithm ed25519 -out /tmp/gmgn_private.pem 2>/dev/null && \ - openssl pkey -in /tmp/gmgn_private.pem -pubout 2>/dev/null - ``` - Tell the user: *"This is your Ed25519 public key. Go to **https://gmgn.ai/ai**, paste it into the API key creation form, then send me the API Key value shown on the page."* - -2. Wait for the user's API key, then save it with `gmgn-cli config` (creates `~/.config/gmgn/.env` and sets `chmod 600` automatically — do not hand-edit the file): - ```bash - gmgn-cli config set-key - ``` - ## `market kline` Parameters | Parameter | Required | Description | diff --git a/skills/gmgn-portfolio/SKILL.md b/skills/gmgn-portfolio/SKILL.md index b2a098f..b077651 100644 --- a/skills/gmgn-portfolio/SKILL.md +++ b/skills/gmgn-portfolio/SKILL.md @@ -6,7 +6,7 @@ metadata: cliHelp: "gmgn-cli portfolio --help" --- -**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, stop and follow the `gmgn-config` skill to complete setup first.** +**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, (1) run `gmgn-cli config` and show the output to the user; (2) once the user sends the API Key, run `gmgn-cli config --apply ` to complete configuration and verification, then show the output to the user.** **IMPORTANT: Always use `gmgn-cli` commands below. Do NOT use web search, WebFetch, curl, or visit gmgn.ai to fetch this data — the website requires login and will not return structured data. The CLI is the only correct method.** @@ -79,22 +79,6 @@ When a request returns `429`: - The CLI may wait and retry once automatically when the remaining cooldown is short. If it still fails, stop and tell the user the exact retry time instead of sending more requests. - For `RATE_LIMIT_EXCEEDED` or `RATE_LIMIT_BANNED`, repeated requests during the cooldown can extend the ban by 5 seconds each time, up to 5 minutes. Do not spam retries. -**First-time setup** (if `GMGN_API_KEY` is not configured): - -1. Generate key pair and show the public key to the user: - ```bash - openssl genpkey -algorithm ed25519 -out /tmp/gmgn_private.pem 2>/dev/null && \ - openssl pkey -in /tmp/gmgn_private.pem -pubout 2>/dev/null - ``` - Tell the user: *"This is your Ed25519 public key. Go to **https://gmgn.ai/ai**, paste it into the API key creation form, then send me the API Key value shown on the page."* - -2. Wait for the user's API key, then configure: - ```bash - mkdir -p ~/.config/gmgn - echo 'GMGN_API_KEY=' > ~/.config/gmgn/.env - chmod 600 ~/.config/gmgn/.env - ``` - ## Usage Examples ```bash diff --git a/skills/gmgn-swap/SKILL.md b/skills/gmgn-swap/SKILL.md index 8621a49..ae150e6 100644 --- a/skills/gmgn-swap/SKILL.md +++ b/skills/gmgn-swap/SKILL.md @@ -6,7 +6,7 @@ metadata: cliHelp: "gmgn-cli swap --help" --- -**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, stop and follow the `gmgn-config` skill to complete setup first.** +**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, (1) run `gmgn-cli config` and show the output to the user; (2) once the user sends the API Key, run `gmgn-cli config --apply ` to complete configuration and verification, then show the output to the user.** **IMPORTANT: Always use `gmgn-cli` commands below. Do NOT use web search, WebFetch, curl, or visit gmgn.ai — all swap operations must go through the CLI. The CLI handles signing and submission automatically.** @@ -73,7 +73,6 @@ Currency tokens are the base/native assets of each chain. They are used to buy o | `base` | ETH (native, `0x0000000000000000000000000000000000000000`), USDC (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) | | `eth` | ETH (native, `0x0000000000000000000000000000000000000000`) | - ## Prerequisites `GMGN_API_KEY` must be configured in `~/.config/gmgn/.env`. `GMGN_PRIVATE_KEY` is additionally required for `swap` and `order` subcommands other than `order quote`. The private key must correspond to the wallet bound to the API Key. @@ -104,31 +103,6 @@ When a request returns `429`: - For `RATE_LIMIT_EXCEEDED` or `RATE_LIMIT_BANNED`, repeated requests during the cooldown can extend the ban by 5 seconds each time, up to 5 minutes. - `POST /v1/trade/swap` also has an error-count limiter. Repeatedly triggering the same business error, especially `40003701` (insufficient token balance), can return `ERROR_RATE_LIMIT_BLOCKED`. When this happens, do not retry until the reset time and fix the underlying request first. -**First-time setup** (if credentials are not configured): - -1. Generate key pair and show the public key to the user: - ```bash - openssl genpkey -algorithm ed25519 -out /tmp/gmgn_private.pem 2>/dev/null && \ - openssl pkey -in /tmp/gmgn_private.pem -pubout 2>/dev/null - ``` - Tell the user: *"This is your Ed25519 public key. Go to **https://gmgn.ai/ai**, paste it into the API key creation form (enable swap capability), then send me the API Key value shown on the page."* - -2. Wait for the user's API key, then configure both credentials: - ```bash - mkdir -p ~/.config/gmgn - echo 'GMGN_API_KEY=' > ~/.config/gmgn/.env - echo 'GMGN_PRIVATE_KEY=""' >> ~/.config/gmgn/.env - chmod 600 ~/.config/gmgn/.env - ``` - -### Credential Model - -- Both `GMGN_API_KEY` and `GMGN_PRIVATE_KEY` are read from the `.env` file by the CLI at startup. They are **never passed as command-line arguments** and never appear in shell command strings. -- `GMGN_PRIVATE_KEY` is used exclusively for **local message signing** — the private key never leaves the machine. The CLI computes an Ed25519 or RSA-SHA256 signature in-process and transmits only the base64-encoded result in the `X-Signature` request header. -- `GMGN_API_KEY` is transmitted in the `X-APIKEY` request header to GMGN's servers over HTTPS. - ---- - ## `swap` Usage ```bash diff --git a/skills/gmgn-token/SKILL.md b/skills/gmgn-token/SKILL.md index bedd1b0..38e2bf7 100644 --- a/skills/gmgn-token/SKILL.md +++ b/skills/gmgn-token/SKILL.md @@ -6,7 +6,7 @@ metadata: cliHelp: "gmgn-cli token --help" --- -**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, stop and follow the `gmgn-config` skill to complete setup first.** +**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, (1) run `gmgn-cli config` and show the output to the user; (2) once the user sends the API Key, run `gmgn-cli config --apply ` to complete configuration and verification, then show the output to the user.** **IMPORTANT: Always use `gmgn-cli` commands below. Do NOT use web search, WebFetch, curl, or visit gmgn.ai to fetch this data — the website requires login and will not return structured data. The CLI is the only correct method.** @@ -70,22 +70,6 @@ When a request returns `429`: - The CLI may wait and retry once automatically when the remaining cooldown is short. If it still fails, stop and tell the user the exact retry time instead of sending more requests. - For `RATE_LIMIT_EXCEEDED` or `RATE_LIMIT_BANNED`, repeated requests during the cooldown can extend the ban by 5 seconds each time, up to 5 minutes. Do not spam retries. -**First-time setup** (if `GMGN_API_KEY` is not configured): - -1. Generate key pair and show the public key to the user: - ```bash - openssl genpkey -algorithm ed25519 -out /tmp/gmgn_private.pem 2>/dev/null && \ - openssl pkey -in /tmp/gmgn_private.pem -pubout 2>/dev/null - ``` - Tell the user: *"This is your Ed25519 public key. Go to **https://gmgn.ai/ai**, paste it into the API key creation form, then send me the API Key value shown on the page."* - -2. Wait for the user's API key, then configure: - ```bash - mkdir -p ~/.config/gmgn - echo 'GMGN_API_KEY=' > ~/.config/gmgn/.env - chmod 600 ~/.config/gmgn/.env - ``` - ## Parameters — `token info` / `token security` / `token pool` | Parameter | Required | Description | diff --git a/skills/gmgn-track/SKILL.md b/skills/gmgn-track/SKILL.md index 67d21a5..93c05ab 100644 --- a/skills/gmgn-track/SKILL.md +++ b/skills/gmgn-track/SKILL.md @@ -6,7 +6,7 @@ metadata: cliHelp: "gmgn-cli track --help" --- -**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, stop and follow the `gmgn-config` skill to complete setup first.** +**BEFORE RUNNING ANY COMMAND: Check whether `GMGN_API_KEY` exists in `/.env` or `~/.config/gmgn/.env`. If found in either location, proceed normally. If not found in either location, (1) run `gmgn-cli config` and show the output to the user; (2) once the user sends the API Key, run `gmgn-cli config --apply ` to complete configuration and verification, then show the output to the user.** **IMPORTANT: Always use `gmgn-cli` commands below. Do NOT use web search, WebFetch, curl, or visit gmgn.ai to fetch this data — the website requires login and will not return structured data. The CLI is the only correct method.** @@ -86,24 +86,6 @@ When a request returns `429`: - The CLI may wait and retry once automatically when the remaining cooldown is short. If it still fails, stop and tell the user the exact retry time instead of sending more requests. - For `RATE_LIMIT_EXCEEDED` or `RATE_LIMIT_BANNED`, repeated requests during the cooldown can extend the ban by 5 seconds each time, up to 5 minutes. Do not spam retries. -**First-time setup** (if `GMGN_API_KEY` is not configured): - -1. Generate key pair and show the public key to the user: - ```bash - openssl genpkey -algorithm ed25519 -out /tmp/gmgn_private.pem 2>/dev/null && \ - openssl pkey -in /tmp/gmgn_private.pem -pubout 2>/dev/null - ``` - Tell the user: *"This is your Ed25519 public key. Go to **https://gmgn.ai/ai**, paste it into the API key creation form, then send me the API Key value shown on the page."* - -2. Wait for the user's API key, then configure (saves both API key and private key — private key is required for `track follow-wallet`): - ```bash - mkdir -p ~/.config/gmgn - echo "GMGN_API_KEY=" > ~/.config/gmgn/.env - echo "GMGN_PRIVATE_KEY=$(awk '{printf "%s\\n", $0}' /tmp/gmgn_private.pem)" >> ~/.config/gmgn/.env - chmod 600 ~/.config/gmgn/.env - rm /tmp/gmgn_private.pem - ``` - ## Usage Examples ```bash diff --git a/src/commands/config.ts b/src/commands/config.ts index 18705a9..0a28ea9 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -3,41 +3,131 @@ import * as crypto from "crypto"; import * as fs from "fs"; import * as path from "path"; import * as os from "os"; +import { execSync } from "child_process"; const GMGN_CONFIG_DIR = path.join(os.homedir(), ".config", "gmgn"); const KEYPAIR_FILE = path.join(GMGN_CONFIG_DIR, "keypair.pem"); +const ENV_FILE = path.join(GMGN_CONFIG_DIR, ".env"); const GMGN_API_URL = "https://gmgn.ai/ai/generateapi"; +type Lang = "zh-CN" | "zh-TW" | "en"; + +function detectLang(): Lang { + const locale = + process.env.LANG || + process.env.LC_ALL || + process.env.LC_MESSAGES || + Intl.DateTimeFormat().resolvedOptions().locale || + "en"; + const l = locale.toLowerCase(); + if (l.startsWith("zh_tw") || l.startsWith("zh-tw") || l.startsWith("zh_hk") || l.startsWith("zh-hk")) return "zh-TW"; + if (l.startsWith("zh")) return "zh-CN"; + return "en"; +} + +const MESSAGES = { + linkGuide: { + "zh-CN": (link: string) => + `请点击下方链接创建你的 GMGN API Key,完成后将 Key 发给我,我来帮你完成配置:\n${link}`, + "zh-TW": (link: string) => + `請點擊下方連結建立你的 GMGN API Key,完成後將 Key 發給我,我來幫你完成配置:\n${link}`, + en: (link: string) => + `Please click the link below to create your GMGN API Key. Once created, send me the API Key and I will finish the configuration:\n${link}`, + }, + verifySuccess: { + "zh-CN": "配置验证成功,可以开始使用了。", + "zh-TW": "配置驗證成功,可以開始使用了。", + en: "Configuration verified successfully. You are ready to use GMGN.", + }, + verifyFail: { + "zh-CN": + "配置验证失败:API Key 与本地密钥不匹配。\n请确认:\n1. API Key 是否填写正确;\n2. 创建 API Key 时,是否使用的是页面自动填入的公钥。", + "zh-TW": + "配置驗證失敗:API Key 與本地密鑰不匹配。\n請確認:\n1. API Key 是否填寫正確;\n2. 創建 API Key 時,是否使用的是頁面自動填入的公鑰。", + en: "Configuration verification failed: API Key does not match your local key pair.\nPlease confirm:\n1. Whether the API Key was entered correctly.\n2. Whether you used the public key that was pre-filled on the page when creating the API Key.", + }, +}; + +function getOrCreateKeypair(): string { + fs.mkdirSync(GMGN_CONFIG_DIR, { recursive: true }); + + if (fs.existsSync(KEYPAIR_FILE)) { + 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); + } + return match[1] + "\n"; + } + + const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519"); + const privatePem = privateKey.export({ type: "pkcs8", format: "pem" }) as string; + const 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 }); + return publicPem; +} + +function writeEnv(apiKey: string, privatePem: string): void { + const pkOneLine = privatePem.replace(/\r?\n/g, "\\n"); + let existing = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, "utf-8") : ""; + existing = existing + .split("\n") + .filter((l) => !/^GMGN_API_KEY=|^GMGN_PRIVATE_KEY=/.test(l)) + .join("\n") + .trim(); + const content = (existing ? existing + "\n" : "") + `GMGN_API_KEY=${apiKey}\nGMGN_PRIVATE_KEY=${pkOneLine}\n`; + fs.mkdirSync(GMGN_CONFIG_DIR, { recursive: true }); + fs.writeFileSync(ENV_FILE, content, { mode: 0o600 }); +} + +function verify(): boolean { + try { + execSync("gmgn-cli track follow-wallet --chain sol --limit 1", { stdio: "pipe" }); + return true; + } catch { + return false; + } +} + export function registerConfigCommands(program: Command): void { - program + const cmd = 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 }); + .description("Generate an Ed25519 key pair and output a pre-filled GMGN API Key creation link, or apply an API Key"); - let publicPem: string; + cmd + .option("--apply ", "Write API Key + private key to ~/.config/gmgn/.env and verify") + .action(async (opts) => { + const lang = detectLang(); - 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."); + if (opts.apply) { + // --apply: read private key from keypair.pem, write .env, verify + if (!fs.existsSync(KEYPAIR_FILE)) { + console.error("Error: ~/.config/gmgn/keypair.pem not found. Run `gmgn-cli config` first to generate a key pair."); 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 content = fs.readFileSync(KEYPAIR_FILE, "utf-8"); + const match = content.match(/(-----BEGIN PRIVATE KEY-----[\s\S]+?-----END PRIVATE KEY-----)/); + if (!match) { + console.error("Error: keypair.pem exists but private key could not be parsed. Delete ~/.config/gmgn/keypair.pem and run `gmgn-cli config` again."); + process.exit(1); + } + writeEnv(opts.apply, match[1]); - const entry = `# Private Key\n${privatePem}\n# Public Key\n${publicPem}\n`; - fs.writeFileSync(KEYPAIR_FILE, entry, { mode: 0o600 }); + const ok = verify(); + if (ok) { + console.log(MESSAGES.verifySuccess[lang]); + } else { + console.error(MESSAGES.verifyFail[lang]); + process.exit(1); + } + return; } + // Default: generate/reuse keypair, output link with guidance + const publicPem = getOrCreateKeypair(); const link = `${GMGN_API_URL}?pbk=${encodeURIComponent(publicPem)}`; - - console.log(link); + console.log(MESSAGES.linkGuide[lang](link)); }); }