feat: add npm distribution support (@gitlink-ai/cli)

- Add npm package structure (package.json, install script, bin wrapper)
- Add cross-platform build script (darwin/linux × amd64/arm64)
- Add local pack script for embedding binary in npm package
- Update README with larksuite/cli-style installation docs
- Published as @gitlink-ai/cli@0.1.0 on npmjs.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Your GitHub Username 2026-04-02 22:57:37 +08:00
parent bd2d8b52d6
commit 381a32a49f
7 changed files with 509 additions and 25 deletions

6
.gitignore vendored
View File

@ -13,3 +13,9 @@ Thumbs.db
# Go
vendor/
# npm packaging
npm/bin/gitlink-cli
npm/*.tgz
npm/README.md
dist/

111
README.md
View File

@ -1,50 +1,111 @@
# gitlink-cli
[![GitLink](https://img.shields.io/badge/GitLink-wbtiger%2Fgitlink--cli-green)](https://www.gitlink.org.cn/wbtiger/gitlink-cli)
[![GitLink](https://img.shields.io/badge/GitLink-Gitlink%2Fgitlink--cli-green)](https://www.gitlink.org.cn/Gitlink/gitlink-cli)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![Go Version](https://img.shields.io/badge/Go-1.21%2B-blue.svg)](https://golang.org)
[![npm version](https://img.shields.io/npm/v/@gitlink-ai/cli.svg)](https://www.npmjs.com/package/@gitlink-ai/cli)
**gitlink-cli** 是 [GitLink确实开源](https://www.gitlink.org.cn) 平台的官方命令行工具,提供高效的代码托管、协作开发和自动化能力。
The official [GitLink确实开源](https://www.gitlink.org.cn) CLI tool — built for humans and AI Agents. Covers repository management, issue tracking, pull requests, CI/CD, and AI-powered workflows, with 40+ commands and 11 AI Agent [Skills](./skills/).
## 核心特性
[Install](#installation--quick-start) · [AI Agent Skills](#ai-agent-skills) · [Auth](#首次使用) · [Commands](#使用示例) · [Contributing](#相关项目)
- 🚀 **三层命令体系**Shortcuts快捷命令→ Raw API全量接口→ Config配置管理
- 🔐 **安全认证**OS Keychain 存储,支持 Token 和用户名密码登录
- 📦 **智能上下文**:自动从 git remote 解析 owner/repo无需重复指定
- 📊 **多格式输出**JSON / Table / YAML标准 Envelope 结构
- 🤖 **AI 自动化**11 个 Claude Code Agent Skills支持 Issue 分类、PR Review、Release Notes 等工作流
- 🌐 **双向同步**:支持 GitHub ↔ GitLink 代码同步(通过 gitlink-bisync
## Why gitlink-cli?
## 快速开始
- **Agent-Native Design** — 11 structured [Skills](./skills/) out of the box, compatible with Claude Code — Agents can operate GitLink with zero extra setup
- **Wide Coverage** — Repository, Issue, PR, Branch, Release, CI, Org, Search, User — all core domains covered
- **AI-Friendly & Optimized** — Every command is tested with real Agents, featuring concise parameters, smart defaults, and structured output
- **Open Source, Zero Barriers** — Apache 2.0 license, ready to use, just `npm install`
- **Up and Running in 3 Minutes** — Interactive login, from install to first API call in just 3 steps
- **Secure & Controllable** — OS-native keychain credential storage, auto git remote context resolution
- **Three-Layer Architecture** — Shortcuts (human & AI friendly) → Raw API (full coverage) → Config (configuration management)
### 安装
## Features
| Category | Capabilities |
|----------|-------------|
| 📦 Repo | List, create, fork, delete repositories, view repo info |
| 🐛 Issue | Create, update, close, comment on issues |
| 🔀 PR | Create, merge, review pull requests, view changed files |
| 🌿 Branch | Create, delete, protect branches |
| 🏷️ Release | Create, view, delete releases |
| 🏢 Org | Manage organizations, members, teams |
| 🔧 CI | View builds, logs, CI/CD operations |
| 🔍 Search | Search repositories, users |
| 👤 User | View user profiles and info |
| 🤖 Workflow | AI-powered issue triage, PR review, release notes |
## Installation & Quick Start
### Requirements
Before you start, make sure you have:
- Node.js 14+ (`npm`/`npx`) — for npm installation
- Go 1.21+ — only required for building from source
### Quick Start (Human Users)
> **Note for AI assistants:** If you are an AI Agent helping the user with installation, jump directly to [Quick Start (AI Agent)](#quick-start-ai-agent), which contains all the steps you need to complete.
#### Install
Choose **one** of the following methods:
**Option 1 — From npm (recommended):**
```bash
# 从源码构建
git clone https://www.gitlink.org.cn/wbtiger/gitlink-cli.git
cd gitlink-cli
make build
npm install -g @gitlink-ai/cli
```
# 或安装到 PATH
**Option 2 — From source:**
Requires Go 1.21+.
```bash
git clone https://www.gitlink.org.cn/Gitlink/gitlink-cli.git
cd gitlink-cli
make install
```
**要求**Go 1.21+
### 首次使用
#### Configure & Use
```bash
# 1. 初始化配置
# 1. Configure (one-time, interactive guided setup)
gitlink-cli config init
# 2. 登录
# 2. Log in
gitlink-cli auth login
# 3. 验证登录
gitlink-cli user +me
# 3. Start using
gitlink-cli repo +list
```
# 4. 查看仓库
gitlink-cli repo +info --owner Gitlink --repo forgeplus
### Quick Start (AI Agent)
> The following steps are for AI Agents. Some steps require the user to complete actions in a browser.
**Step 1 — Install**
```bash
npm install -g @gitlink-ai/cli
```
**Step 2 — Configure**
```bash
gitlink-cli config init
```
**Step 3 — Login**
```bash
gitlink-cli auth login
```
**Step 4 — Verify**
```bash
gitlink-cli user +me
```
## 使用示例

21
npm/bin/cli.js Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env node
"use strict";
const path = require("path");
const { execFileSync } = require("child_process");
const binaryPath = path.join(__dirname, "gitlink-cli");
try {
execFileSync(binaryPath, process.argv.slice(2), { stdio: "inherit" });
} catch (err) {
if (err.status !== undefined) {
process.exit(err.status);
}
console.error(`Failed to run gitlink-cli: ${err.message}`);
console.error(
"Binary may not be installed. Try reinstalling: npm install -g @gitlink-ai/cli"
);
process.exit(1);
}

41
npm/package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "@gitlink-ai/cli",
"version": "0.1.0",
"description": "GitLink 平台官方命令行工具 — 代码托管、协作开发和自动化",
"bin": {
"gitlink-cli": "bin/cli.js"
},
"scripts": {
"postinstall": "node scripts/install.js"
},
"keywords": [
"gitlink",
"cli",
"git",
"devops",
"code-hosting"
],
"author": "GitLink <support@gitlink.org.cn>",
"license": "Apache-2.0",
"homepage": "https://www.gitlink.org.cn/Gitlink/gitlink-cli",
"repository": {
"type": "git",
"url": "https://www.gitlink.org.cn/Gitlink/gitlink-cli.git"
},
"os": [
"darwin",
"linux"
],
"cpu": [
"x64",
"arm64"
],
"files": [
"bin/",
"scripts/",
"README.md"
],
"engines": {
"node": ">=14"
}
}

242
npm/scripts/install.js Normal file
View File

@ -0,0 +1,242 @@
#!/usr/bin/env node
"use strict";
const os = require("os");
const path = require("path");
const fs = require("fs");
const https = require("https");
const http = require("http");
const { execSync } = require("child_process");
const PACKAGE = require("../package.json");
const VERSION = PACKAGE.version;
const BINARY_NAME = "gitlink-cli";
// GitLink release download base URL
// Format: https://www.gitlink.org.cn/Gitlink/gitlink-cli/releases
// Attachment download: https://www.gitlink.org.cn/api/attachments/{attachment_id}
const RELEASE_BASE = "https://www.gitlink.org.cn";
const REPO_OWNER = "Gitlink";
const REPO_NAME = "gitlink-cli";
function getPlatformInfo() {
const platform = os.platform();
const arch = os.arch();
const platformMap = {
darwin: "darwin",
linux: "linux",
};
const archMap = {
x64: "amd64",
arm64: "arm64",
};
const goPlatform = platformMap[platform];
const goArch = archMap[arch];
if (!goPlatform || !goArch) {
throw new Error(
`Unsupported platform: ${platform}-${arch}. ` +
`Supported: darwin-x64, darwin-arm64, linux-x64, linux-arm64`
);
}
return { platform: goPlatform, arch: goArch };
}
function getBinaryName(platform) {
return BINARY_NAME;
}
function getArchiveName(platform, arch) {
return `${BINARY_NAME}_${VERSION}_${platform}_${arch}.tar.gz`;
}
function fetch(url, options = {}) {
return new Promise((resolve, reject) => {
const maxRedirects = options.maxRedirects || 5;
let redirectCount = 0;
function doRequest(currentUrl) {
const mod = currentUrl.startsWith("https") ? https : http;
const req = mod.get(currentUrl, (res) => {
// Follow redirects
if (
(res.statusCode === 301 ||
res.statusCode === 302 ||
res.statusCode === 307 ||
res.statusCode === 308) &&
res.headers.location
) {
redirectCount++;
if (redirectCount > maxRedirects) {
reject(new Error(`Too many redirects (max ${maxRedirects})`));
return;
}
let redirectUrl = res.headers.location;
if (redirectUrl.startsWith("/")) {
const parsed = new URL(currentUrl);
redirectUrl = `${parsed.protocol}//${parsed.host}${redirectUrl}`;
}
doRequest(redirectUrl);
return;
}
if (res.statusCode !== 200) {
reject(
new Error(`HTTP ${res.statusCode} when downloading ${currentUrl}`)
);
return;
}
if (options.json) {
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => {
try {
resolve(JSON.parse(body));
} catch (e) {
reject(new Error(`Failed to parse JSON: ${e.message}`));
}
});
} else {
const chunks = [];
res.on("data", (chunk) => chunks.push(chunk));
res.on("end", () => resolve(Buffer.concat(chunks)));
}
});
req.on("error", reject);
req.setTimeout(60000, () => {
req.destroy();
reject(new Error("Request timed out"));
});
}
doRequest(url);
});
}
async function findReleaseAsset(platform, arch) {
const archiveName = getArchiveName(platform, arch);
// Try fetching release info from GitLink API
const apiUrl = `${RELEASE_BASE}/api/${REPO_OWNER}/${REPO_NAME}/releases.json`;
console.log(`Fetching release info from ${apiUrl}`);
try {
const releases = await fetch(apiUrl, { json: true });
// Find the release matching our version
let release = null;
const tagName = `v${VERSION}`;
if (Array.isArray(releases)) {
release = releases.find(
(r) => r.tag_name === tagName || r.tag_name === VERSION
);
if (!release && releases.length > 0) {
// Fall back to latest release
release = releases[0];
}
} else if (releases && releases.releases) {
const list = releases.releases;
release = list.find(
(r) => r.tag_name === tagName || r.tag_name === VERSION
);
if (!release && list.length > 0) {
release = list[0];
}
}
if (release && release.attachments) {
const asset = release.attachments.find(
(a) => a.title === archiveName || a.filename === archiveName
);
if (asset) {
// Return the download URL for this attachment
const downloadUrl = asset.url || `${RELEASE_BASE}/api/attachments/${asset.id}`;
return downloadUrl;
}
}
} catch (e) {
console.log(`Warning: Could not fetch release info: ${e.message}`);
}
// Fallback: try direct download URL pattern
return `${RELEASE_BASE}/api/${REPO_OWNER}/${REPO_NAME}/releases/${tagName}/assets/${archiveName}`;
}
async function downloadAndExtract(url, destDir) {
console.log(`Downloading ${BINARY_NAME} from ${url}...`);
const data = await fetch(url);
const tarballPath = path.join(destDir, "download.tar.gz");
fs.writeFileSync(tarballPath, data);
console.log(`Downloaded ${(data.length / 1024 / 1024).toFixed(1)} MB`);
// Extract using tar
execSync(`tar -xzf "${tarballPath}" -C "${destDir}"`, { stdio: "pipe" });
fs.unlinkSync(tarballPath);
// Find the binary in extracted files
const binaryName = getBinaryName();
const binaryPath = path.join(destDir, binaryName);
if (!fs.existsSync(binaryPath)) {
// It might be in a subdirectory
const files = fs.readdirSync(destDir);
for (const file of files) {
const subPath = path.join(destDir, file, binaryName);
if (fs.existsSync(subPath)) {
fs.renameSync(subPath, binaryPath);
break;
}
}
}
if (!fs.existsSync(binaryPath)) {
throw new Error(`Binary "${binaryName}" not found after extraction`);
}
// Make executable
fs.chmodSync(binaryPath, 0o755);
console.log(`Installed ${BINARY_NAME} to ${binaryPath}`);
}
async function main() {
try {
const { platform, arch } = getPlatformInfo();
console.log(`Platform: ${platform}-${arch}`);
const binDir = path.join(__dirname, "..", "bin");
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, { recursive: true });
}
const binaryPath = path.join(binDir, getBinaryName());
// If binary already exists (pre-packed), skip download
if (fs.existsSync(binaryPath)) {
console.log(`${BINARY_NAME} binary already exists, skipping download.`);
return;
}
const downloadUrl = await findReleaseAsset(platform, arch);
await downloadAndExtract(downloadUrl, binDir);
} catch (err) {
console.error(`\nFailed to install ${BINARY_NAME}: ${err.message}`);
console.error(
`\nYou can install manually:\n` +
` 1. Download from https://www.gitlink.org.cn/${REPO_OWNER}/${REPO_NAME}/releases\n` +
` 2. Extract and place the binary in your PATH\n` +
` 3. Or build from source: git clone && make build\n`
);
process.exit(1);
}
}
main();

78
scripts/build-npm.sh Executable file
View File

@ -0,0 +1,78 @@
#!/bin/bash
# Build multi-platform binaries and package for npm
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
NPM_DIR="$PROJECT_DIR/npm"
DIST_DIR="$PROJECT_DIR/dist"
# Read version from npm/package.json
VERSION=$(node -p "require('$NPM_DIR/package.json').version")
MODULE="github.com/gitlink-org/gitlink-cli"
LDFLAGS="-s -w -X '${MODULE}/cmd.Version=${VERSION}'"
BINARY="gitlink-cli"
echo "=== Building gitlink-cli v${VERSION} ==="
# Clean
rm -rf "$DIST_DIR"
mkdir -p "$DIST_DIR"
# Platforms to build: GOOS/GOARCH
PLATFORMS=(
"darwin/amd64"
"darwin/arm64"
"linux/amd64"
"linux/arm64"
)
cd "$PROJECT_DIR"
for PLATFORM in "${PLATFORMS[@]}"; do
GOOS="${PLATFORM%/*}"
GOARCH="${PLATFORM#*/}"
OUTPUT_NAME="${BINARY}"
echo "Building ${GOOS}/${GOARCH}..."
ARCHIVE_DIR="${DIST_DIR}/${BINARY}_${VERSION}_${GOOS}_${GOARCH}"
mkdir -p "$ARCHIVE_DIR"
CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" \
go build -ldflags "$LDFLAGS" -o "${ARCHIVE_DIR}/${OUTPUT_NAME}" .
# Create tar.gz archive
ARCHIVE_NAME="${BINARY}_${VERSION}_${GOOS}_${GOARCH}.tar.gz"
(cd "$DIST_DIR" && tar -czf "$ARCHIVE_NAME" -C "${ARCHIVE_DIR}" "$OUTPUT_NAME")
echo " -> dist/${ARCHIVE_NAME}"
rm -rf "$ARCHIVE_DIR"
done
echo ""
echo "=== Build complete ==="
echo "Archives in dist/:"
ls -lh "$DIST_DIR"/*.tar.gz
echo ""
echo "=== Packaging npm ==="
# Copy README to npm dir
cp "$PROJECT_DIR/README.md" "$NPM_DIR/README.md"
# Ensure bin dir exists and wrapper is executable
chmod +x "$NPM_DIR/bin/gitlink-cli"
echo ""
echo "=== Done ==="
echo ""
echo "Next steps:"
echo " 1. Upload dist/*.tar.gz to GitLink Release v${VERSION}"
echo " URL: https://www.gitlink.org.cn/Gitlink/gitlink-cli/releases"
echo ""
echo " 2. Publish npm package:"
echo " cd npm && npm publish --access public"
echo ""
echo " Or for local-packed npm (includes binary for current platform):"
echo " ./scripts/pack-local.sh"

35
scripts/pack-local.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
# Pack npm package with the binary for the current platform pre-included.
# This way npm install does NOT need to download anything.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
NPM_DIR="$PROJECT_DIR/npm"
MODULE="github.com/gitlink-org/gitlink-cli"
VERSION=$(node -p "require('$NPM_DIR/package.json').version")
LDFLAGS="-s -w -X '${MODULE}/cmd.Version=${VERSION}'"
BINARY="gitlink-cli"
echo "=== Building gitlink-cli for current platform ==="
cd "$PROJECT_DIR"
# Build for current platform
CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o "$NPM_DIR/bin/${BINARY}" .
chmod +x "$NPM_DIR/bin/${BINARY}"
cp "$PROJECT_DIR/README.md" "$NPM_DIR/README.md"
echo "Binary size: $(du -h "$NPM_DIR/bin/${BINARY}" | cut -f1)"
echo ""
echo "=== Creating npm tarball ==="
cd "$NPM_DIR"
npm pack
echo ""
echo "=== Done ==="
echo "To install locally: npm install -g gitlink-ai-cli-${VERSION}.tgz"
echo "To publish: cd npm && npm publish --access public"