feat: add Windows (x64/arm64) support

- Add windows/amd64 and windows/arm64 to build targets with .zip packaging
- Update npm install.js: win32 platform detection, .exe binary, PowerShell zip extraction
- Update npm cli.js wrapper to append .exe on Windows
- Fix credential path to use filepath.Join for cross-platform compatibility
- Update README with Windows support docs and FAQ

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
wbtiger 2026-04-07 10:52:56 +08:00
parent 0aed7ad2d7
commit 342badbdd2
6 changed files with 61 additions and 27 deletions

View File

@ -5,7 +5,7 @@
[![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)
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/).
The official [GitLink确实开源](https://www.gitlink.org.cn) CLI tool — built for humans and AI Agents. Supports **macOS, Linux, and Windows**. 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](#相关项目)
@ -14,6 +14,7 @@ The official [GitLink确实开源](https://www.gitlink.org.cn) CLI tool
- **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
- **Cross-Platform** — Runs on macOS, Linux, and Windows (x64/arm64), install via `npm` in one command
- **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
@ -41,6 +42,7 @@ The official [GitLink确实开源](https://www.gitlink.org.cn) CLI tool
Before you start, make sure you have:
- Node.js 14+ (`npm`/`npx`) — for npm installation
- Supported platforms: macOS, Linux, Windows (x64/arm64)
- Go 1.21+ — only required for building from source
### Quick Start (Human Users)
@ -74,6 +76,8 @@ make install
npx skills add ./skills -y -g
```
> **Windows 用户注意:** 请在 PowerShell 或 CMD 中运行 `npm install -g @gitlink-ai/cli`。从源码构建请使用 `go install .` 代替 `make install`
#### Configure & Use
```bash
@ -343,6 +347,10 @@ A: 重新登录:
gitlink-cli auth login
```
### Q: Windows 上凭证存储在哪里?
A: gitlink-cli 使用 Windows Credential Manager 安全存储 Token。如果 Credential Manager 不可用,会自动降级到文件存储 (`~/.config/gitlink-cli/credentials`)。
### Q: 如何查看完整的 API 参考?
A: 查看 [skills/gitlink-shared/REFERENCE.md](skills/gitlink-shared/REFERENCE.md)

View File

@ -2,6 +2,7 @@ package auth
import (
"os"
"path/filepath"
"github.com/zalando/go-keyring"
)
@ -41,15 +42,15 @@ func DeleteToken() error {
func credentialPath() string {
home, _ := os.UserHomeDir()
return home + "/.config/gitlink-cli/credentials"
return filepath.Join(home, ".config", "gitlink-cli", "credentials")
}
func storeTokenFile(token string) error {
dir := credentialPath()
if err := os.MkdirAll(dir[:len(dir)-len("/credentials")], 0700); err != nil {
p := credentialPath()
if err := os.MkdirAll(filepath.Dir(p), 0700); err != nil {
return err
}
return os.WriteFile(dir, []byte(token), 0600)
return os.WriteFile(p, []byte(token), 0600)
}
func loadTokenFile() (string, error) {

View File

@ -5,7 +5,8 @@
const path = require("path");
const { execFileSync } = require("child_process");
const binaryPath = path.join(__dirname, "gitlink-cli");
const ext = process.platform === "win32" ? ".exe" : "";
const binaryPath = path.join(__dirname, "gitlink-cli" + ext);
try {
execFileSync(binaryPath, process.argv.slice(2), { stdio: "inherit" });

View File

@ -1,6 +1,6 @@
{
"name": "@gitlink-ai/cli",
"version": "0.1.1",
"version": "0.1.2",
"description": "GitLink 平台官方命令行工具 — 代码托管、协作开发和自动化",
"bin": {
"gitlink-cli": "bin/cli.js"
@ -24,7 +24,8 @@
},
"os": [
"darwin",
"linux"
"linux",
"win32"
],
"cpu": [
"x64",

View File

@ -27,6 +27,7 @@ function getPlatformInfo() {
const platformMap = {
darwin: "darwin",
linux: "linux",
win32: "windows",
};
const archMap = {
@ -40,19 +41,20 @@ function getPlatformInfo() {
if (!goPlatform || !goArch) {
throw new Error(
`Unsupported platform: ${platform}-${arch}. ` +
`Supported: darwin-x64, darwin-arm64, linux-x64, linux-arm64`
`Supported: darwin-x64, darwin-arm64, linux-x64, linux-arm64, win32-x64, win32-arm64`
);
}
return { platform: goPlatform, arch: goArch };
return { platform: goPlatform, arch: goArch, isWindows: platform === "win32" };
}
function getBinaryName(platform) {
return BINARY_NAME;
return platform === "windows" ? BINARY_NAME + ".exe" : BINARY_NAME;
}
function getArchiveName(platform, arch) {
return `${BINARY_NAME}_${VERSION}_${platform}_${arch}.tar.gz`;
const ext = platform === "windows" ? ".zip" : ".tar.gz";
return `${BINARY_NAME}_${VERSION}_${platform}_${arch}${ext}`;
}
function fetch(url, options = {}) {
@ -169,21 +171,30 @@ async function findReleaseAsset(platform, arch) {
return `${RELEASE_BASE}/api/${REPO_OWNER}/${REPO_NAME}/releases/${tagName}/assets/${archiveName}`;
}
async function downloadAndExtract(url, destDir) {
async function downloadAndExtract(url, destDir, platform) {
console.log(`Downloading ${BINARY_NAME} from ${url}...`);
const data = await fetch(url);
const tarballPath = path.join(destDir, "download.tar.gz");
const isWindows = platform === "windows";
const archiveExt = isWindows ? "download.zip" : "download.tar.gz";
const archivePath = path.join(destDir, archiveExt);
fs.writeFileSync(tarballPath, data);
fs.writeFileSync(archivePath, 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);
// Extract
if (isWindows) {
execSync(
`powershell -NoProfile -Command "Expand-Archive -Force -Path '${archivePath}' -DestinationPath '${destDir}'"`,
{ stdio: "pipe" }
);
} else {
execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, { stdio: "pipe" });
}
fs.unlinkSync(archivePath);
// Find the binary in extracted files
const binaryName = getBinaryName();
const binaryName = getBinaryName(platform);
const binaryPath = path.join(destDir, binaryName);
if (!fs.existsSync(binaryPath)) {
@ -202,8 +213,10 @@ async function downloadAndExtract(url, destDir) {
throw new Error(`Binary "${binaryName}" not found after extraction`);
}
// Make executable
fs.chmodSync(binaryPath, 0o755);
// Make executable (not needed on Windows)
if (!isWindows) {
fs.chmodSync(binaryPath, 0o755);
}
console.log(`Installed ${BINARY_NAME} to ${binaryPath}`);
}
@ -217,7 +230,7 @@ async function main() {
fs.mkdirSync(binDir, { recursive: true });
}
const binaryPath = path.join(binDir, getBinaryName());
const binaryPath = path.join(binDir, getBinaryName(platform));
// If binary already exists (pre-packed), skip download
if (fs.existsSync(binaryPath)) {
@ -226,7 +239,7 @@ async function main() {
}
const downloadUrl = await findReleaseAsset(platform, arch);
await downloadAndExtract(downloadUrl, binDir);
await downloadAndExtract(downloadUrl, binDir, platform);
} catch (err) {
console.error(`\nFailed to install ${BINARY_NAME}: ${err.message}`);
console.error(

View File

@ -25,6 +25,8 @@ PLATFORMS=(
"darwin/arm64"
"linux/amd64"
"linux/arm64"
"windows/amd64"
"windows/arm64"
)
cd "$PROJECT_DIR"
@ -33,6 +35,9 @@ for PLATFORM in "${PLATFORMS[@]}"; do
GOOS="${PLATFORM%/*}"
GOARCH="${PLATFORM#*/}"
OUTPUT_NAME="${BINARY}"
if [ "$GOOS" = "windows" ]; then
OUTPUT_NAME="${BINARY}.exe"
fi
echo "Building ${GOOS}/${GOARCH}..."
@ -42,9 +47,14 @@ for PLATFORM in "${PLATFORMS[@]}"; do
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")
# Create archive (zip for Windows, tar.gz for others)
if [ "$GOOS" = "windows" ]; then
ARCHIVE_NAME="${BINARY}_${VERSION}_${GOOS}_${GOARCH}.zip"
(cd "${ARCHIVE_DIR}" && zip -q "../${ARCHIVE_NAME}" "$OUTPUT_NAME")
else
ARCHIVE_NAME="${BINARY}_${VERSION}_${GOOS}_${GOARCH}.tar.gz"
(cd "$DIST_DIR" && tar -czf "$ARCHIVE_NAME" -C "${ARCHIVE_DIR}" "$OUTPUT_NAME")
fi
echo " -> dist/${ARCHIVE_NAME}"
rm -rf "$ARCHIVE_DIR"
@ -53,7 +63,7 @@ done
echo ""
echo "=== Build complete ==="
echo "Archives in dist/:"
ls -lh "$DIST_DIR"/*.tar.gz
ls -lh "$DIST_DIR"/*.tar.gz "$DIST_DIR"/*.zip 2>/dev/null
echo ""
echo "=== Packaging npm ==="