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:
parent
0aed7ad2d7
commit
342badbdd2
10
README.md
10
README.md
|
|
@ -5,7 +5,7 @@
|
|||
[](https://golang.org)
|
||||
[](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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 ==="
|
||||
|
|
|
|||
Loading…
Reference in New Issue