forked from Gitlink/gitlink-cli
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:
parent
bd2d8b52d6
commit
381a32a49f
|
|
@ -13,3 +13,9 @@ Thumbs.db
|
|||
|
||||
# Go
|
||||
vendor/
|
||||
|
||||
# npm packaging
|
||||
npm/bin/gitlink-cli
|
||||
npm/*.tgz
|
||||
npm/README.md
|
||||
dist/
|
||||
|
|
|
|||
111
README.md
111
README.md
|
|
@ -1,50 +1,111 @@
|
|||
# gitlink-cli
|
||||
|
||||
[](https://www.gitlink.org.cn/wbtiger/gitlink-cli)
|
||||
[](https://www.gitlink.org.cn/Gitlink/gitlink-cli)
|
||||
[](LICENSE)
|
||||
[](https://golang.org)
|
||||
[](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
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
Loading…
Reference in New Issue