@chuhe2640/ahsopus-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/bin/cli.js +31 -0
- package/npm/lib/install.js +212 -0
- package/npm/lib/place-binary.js +61 -0
- package/npm/lib/platforms.js +22 -0
- package/package.json +44 -0
- package/prebuilt/ahsops-darwin-amd64 +0 -0
- package/prebuilt/ahsops-darwin-arm64 +0 -0
- package/prebuilt/ahsops-linux-amd64 +0 -0
- package/prebuilt/ahsops-linux-arm64 +0 -0
- package/prebuilt/ahsops-windows-amd64.exe +0 -0
- package/prebuilt/ahsops-windows-arm64.exe +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# AHSOpsHub CLI
|
|
2
|
+
|
|
3
|
+
AHSOpsHub 的本机 CLI,对标飞书 `lark-cli` 的认证、信封、Skill 和高风险确认机制。服务端按用户实时权限执行,生产开关默认关闭。
|
|
4
|
+
|
|
5
|
+
## 用户安装
|
|
6
|
+
|
|
7
|
+
Windows、macOS 和 Linux 使用同一条命令。安装过程中再选择要把 Skills 写到哪个 AI Agent:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @chuhe2640/ahsopus-cli@latest install --server <当前 AHSOpsHub 地址> --profile <dev|office|prod>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
用户安装只需要 Node.js 18+(npm / npx),与飞书 CLI 一样不需要 Go。`npx` 会下载当前平台的预编译 `ahsops` 并装到 npm 全局命令。Go 1.23+ 只在从源码构建或发布 npm 包时需要。开发和生产共用这一条 CLI,用 profile 指向不同服务地址,不要发第二套包。调试 CLI 源码时不要 `npm publish` 到 `latest`,在 `cli` 目录运行 `npx --yes . install --server <地址> --profile dev`。
|
|
14
|
+
|
|
15
|
+
发布到 npm(包名挂在当前 npm 账号 `chuhe2640` 下):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd cli
|
|
19
|
+
npm publish --access public
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
正式签名发布物仍可使用校验 SHA-256 的仓库安装器,不会静默更新,也不会绕过 macOS 代码签名/Gatekeeper 或 Windows Authenticode:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bash install.sh https://release.example.internal/ahsops 0.1.0
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```powershell
|
|
29
|
+
.\install.ps1 -ReleaseBaseUrl https://release.example.internal/ahsops -Version 0.1.0
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 首次使用
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
ahsops profile add office --server http://192.168.1.20:3000
|
|
36
|
+
ahsops auth login --recommend
|
|
37
|
+
ahsops skills install --target claude-code --scope user
|
|
38
|
+
ahsops doctor
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Agent 登录不要阻塞等用户点浏览器:`ahsops auth login --no-open --no-wait`,把 `verification_uri_complete` 和 `ahsops auth qrcode --url ...` 交给用户,再用 `--device-code` 继续轮询。缺 `--yes` 的写操作退出码是 `10`,不是普通错误。
|
|
42
|
+
|
|
43
|
+
profile可使用HTTP或HTTPS。部门内网部署直接使用专用Mac的`http://局域网IP:3000`,不需要额外开关、参数或交互确认。服务端`PUBLIC_APP_URL`也必须使用同一个其他电脑可访问的地址;`127.0.0.1`只适用于浏览器和服务都在同一台机器的情况。系统已移除本地密码登录,但HTTP仍会明文传输飞书 OAuth 回调 code、Cookie、CLI Token和业务结果,这是当前部署明确接受的取舍。凭据默认存入 macOS Keychain 或 Windows Credential Manager;系统钥匙串不可用时,必须由用户在交互终端显式执行:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
ahsops auth storage file-enable
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
文件存储使用 AES-GCM 和权限为 `0600` 的本机主密钥,但同一系统用户下的其他进程仍可能读取,因此不会自动降级到文件存储。
|
|
50
|
+
|
|
51
|
+
`file-enable`和`keychain-enable`会迁移全部已配置profile的现有凭据,目标存储回读和配置激活成功后才清理旧存储;切换失败时保留原凭据。若旧存储清理失败,CLI通过`warnings`明确列出受影响profile,但已激活的目标凭据仍可用。
|
|
52
|
+
|
|
53
|
+
设备 Token 轮询遇到短暂网络中断时会在设备码有效期内继续;如果服务端已经消费设备码但响应丢失,CLI会返回`invalid_grant`并要求重新登录。`auth logout`在远端撤销成功或服务端明确返回401时删除本机凭据;网络中断和临时服务端错误不会清理凭据,避免留下无法从终端撤销的活跃会话。
|
|
54
|
+
|
|
55
|
+
## 输出和写操作
|
|
56
|
+
|
|
57
|
+
默认成功输出写 stdout,错误写 stderr,格式均为 JSON 信封。可选 `pretty`、`table`、`ndjson` 和 `csv`,并支持本地 `--jq`。复杂请求使用:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ahsops analytics tools search --data @search.json
|
|
61
|
+
ahsops notification preview --data @preview.json --dry-run
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
受控写操作先运行 `--dry-run`;服务端会执行与真实操作相同的权限、对象、状态、Release和配置版本校验,然后回滚数据库事务。向用户说明实际变化后才可使用 `--yes`。规则正式应用、真实通知发送、资产发布/恢复和平台配置不在 v1 CLI 中开放。
|
|
65
|
+
|
|
66
|
+
每个真实写请求的幂等记录和业务变更在同一PostgreSQL事务中提交;中途故障会整体回滚,成功重试则在重新检查当前RBAC后返回原结果。CSV对可能被电子表格执行的字符串加入防护,table输出移除ANSI与终端控制字符。
|
|
67
|
+
|
|
68
|
+
## 本地开发
|
|
69
|
+
|
|
70
|
+
要求 Go 1.23 或更高版本:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
go test ./...
|
|
74
|
+
go test -race ./...
|
|
75
|
+
go build ./cmd/ahsops
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
依赖下载、Go 安装和正式签名需要在受控开发或 CI 环境中执行。本仓库 CI 负责格式、服务端CLI契约、测试、race 和三目标交叉构建;正式 Release 只能从`main`手工触发,要求名为`ahsops-cli-release`的受保护 GitHub Environment、人工审批以及其中配置的macOS与Windows签名Secret。同一SemVer的已发布签名产物不可覆盖。
|
|
79
|
+
|
|
80
|
+
## 验证边界
|
|
81
|
+
|
|
82
|
+
- `go test` 和交叉编译不等于 Windows 真机运行验收。
|
|
83
|
+
- 安装器校验 SHA-256 不等于代码签名、公证或公司软件分发审批。
|
|
84
|
+
- 本地 CLI 测试不等于真实 PostgreSQL 迁移、局域网HTTP多设备访问、Claude Code/Codex 前向任务或生产验收。
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { install } from '../npm/lib/install.js'
|
|
5
|
+
import { nativeBinaryPath, placeBinary } from '../npm/lib/place-binary.js'
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
if (args[0] === 'install') {
|
|
9
|
+
try {
|
|
10
|
+
await install()
|
|
11
|
+
} catch (error) {
|
|
12
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
13
|
+
console.error(message)
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
process.exit(0)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let binary = nativeBinaryPath()
|
|
20
|
+
if (!existsSync(binary)) {
|
|
21
|
+
try {
|
|
22
|
+
binary = placeBinary()
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
25
|
+
console.error(message)
|
|
26
|
+
process.exit(1)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = spawnSync(binary, args, { stdio: 'inherit' })
|
|
31
|
+
process.exit(result.status ?? 1)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// INPUT: 本机 Node、已安装的 Agent、终端是否可交互
|
|
2
|
+
// OUTPUT: 全局安装 ahsops,多选写入 Skills,并按当前站点配置 profile
|
|
3
|
+
// POS: npx @chuhe2640/ahsopus-cli install 的跨平台入口,对齐飞书 CLI 安装向导,不要求 Go,不打印 Token
|
|
4
|
+
|
|
5
|
+
import * as p from '@clack/prompts'
|
|
6
|
+
import { spawnSync } from 'node:child_process'
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
8
|
+
import { homedir, platform } from 'node:os'
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
import { stdin } from 'node:process'
|
|
11
|
+
import { packageRoot } from './place-binary.js'
|
|
12
|
+
|
|
13
|
+
export const AGENTS = [
|
|
14
|
+
{ id: 'claude-code', name: 'Claude Code', hint: '~/.claude' },
|
|
15
|
+
{ id: 'codex', name: 'Codex', hint: '~/.codex' },
|
|
16
|
+
{ id: 'cursor', name: 'Cursor', hint: '~/.cursor' },
|
|
17
|
+
{ id: 'grok', name: 'Grok', hint: '~/.grok' },
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'))
|
|
21
|
+
|
|
22
|
+
export function parseInstallFlags(argv) {
|
|
23
|
+
let server = ''
|
|
24
|
+
let profile = ''
|
|
25
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
26
|
+
const value = argv[index]
|
|
27
|
+
if (value === '--server' && argv[index + 1] && !argv[index + 1].startsWith('-')) {
|
|
28
|
+
server = argv[index + 1]
|
|
29
|
+
index += 1
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
if (value.startsWith('--server=')) {
|
|
33
|
+
server = value.slice('--server='.length)
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
if (value === '--profile' && argv[index + 1] && !argv[index + 1].startsWith('-')) {
|
|
37
|
+
profile = argv[index + 1]
|
|
38
|
+
index += 1
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
if (value.startsWith('--profile=')) {
|
|
42
|
+
profile = value.slice('--profile='.length)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { server, profile }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function execFile(command, args, options) {
|
|
49
|
+
if (platform() === 'win32') {
|
|
50
|
+
return spawnSync('cmd.exe', ['/c', command, ...args], options)
|
|
51
|
+
}
|
|
52
|
+
return spawnSync(command, args, options)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function run(command, args, options = {}) {
|
|
56
|
+
const result = execFile(command, args, {
|
|
57
|
+
encoding: 'utf8',
|
|
58
|
+
stdio: options.stdio ?? 'inherit',
|
|
59
|
+
shell: false,
|
|
60
|
+
...options,
|
|
61
|
+
})
|
|
62
|
+
if (result.error) throw result.error
|
|
63
|
+
if (result.status !== 0) {
|
|
64
|
+
const detail = String(result.stderr || result.stdout || '').trim()
|
|
65
|
+
throw new Error(detail || `${command} ${args.join(' ')} 失败(退出码 ${result.status})`)
|
|
66
|
+
}
|
|
67
|
+
return result
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function runQuiet(command, args, options = {}) {
|
|
71
|
+
return run(command, args, { stdio: ['ignore', 'pipe', 'pipe'], ...options })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function detectedAgents(home = homedir()) {
|
|
75
|
+
return AGENTS.filter((agent) => existsSync(agent.hint.replace('~', home)))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function globalAhsops() {
|
|
79
|
+
const prefix = runQuiet('npm', ['prefix', '-g']).stdout.trim()
|
|
80
|
+
if (platform() === 'win32') {
|
|
81
|
+
return path.join(prefix, 'ahsops.cmd')
|
|
82
|
+
}
|
|
83
|
+
return path.join(prefix, 'bin', 'ahsops')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function globallyInstalledVersion() {
|
|
87
|
+
try {
|
|
88
|
+
const out = runQuiet('npm', ['list', '-g', pkg.name], { timeout: 15000 }).stdout
|
|
89
|
+
const match = String(out).match(/@(\d+\.\d+\.\d+[^\s]*)/)
|
|
90
|
+
return match ? match[1] : null
|
|
91
|
+
} catch {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function installGlobally() {
|
|
97
|
+
const installed = globallyInstalledVersion()
|
|
98
|
+
if (installed === pkg.version) {
|
|
99
|
+
p.log.info(`已安装 (v${installed}),跳过`)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
const spinner = p.spinner()
|
|
103
|
+
spinner.start(installed ? `正在升级 ${pkg.name} (v${installed} → v${pkg.version})...` : `正在安装 ${pkg.name}...`)
|
|
104
|
+
try {
|
|
105
|
+
try {
|
|
106
|
+
runQuiet('npm', ['install', '-g', `${pkg.name}@${pkg.version}`], { timeout: 120000 })
|
|
107
|
+
} catch {
|
|
108
|
+
runQuiet('npm', ['install', '-g', packageRoot()], { timeout: 120000 })
|
|
109
|
+
}
|
|
110
|
+
spinner.stop(installed ? `已升级到 v${pkg.version}` : '已全局安装')
|
|
111
|
+
} catch {
|
|
112
|
+
spinner.stop(`全局安装失败。运行以下命令重试: npm install -g ${pkg.name}`)
|
|
113
|
+
process.exit(1)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function chooseAgents() {
|
|
118
|
+
const found = detectedAgents()
|
|
119
|
+
const selected = await p.multiselect({
|
|
120
|
+
message: found.length
|
|
121
|
+
? `检测到本机已安装:${found.map((agent) => agent.name).join('、')}。空格多选,回车确认`
|
|
122
|
+
: '未检测到已安装的 AI Agent。空格多选要写入 Skills 的工具,回车确认',
|
|
123
|
+
options: AGENTS.map((agent) => ({
|
|
124
|
+
value: agent.id,
|
|
125
|
+
label: agent.name,
|
|
126
|
+
hint: found.some((item) => item.id === agent.id) ? '已检测到' : undefined,
|
|
127
|
+
})),
|
|
128
|
+
initialValues: found.map((agent) => agent.id),
|
|
129
|
+
required: false,
|
|
130
|
+
})
|
|
131
|
+
if (p.isCancel(selected)) {
|
|
132
|
+
p.cancel('安装已取消')
|
|
133
|
+
process.exit(0)
|
|
134
|
+
}
|
|
135
|
+
return selected
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function installSkills(binary, targets) {
|
|
139
|
+
if (!targets.length) {
|
|
140
|
+
p.log.info('已跳过 Skills 安装')
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
const spinner = p.spinner()
|
|
144
|
+
spinner.start('正在安装 Skills...')
|
|
145
|
+
try {
|
|
146
|
+
for (const target of targets) {
|
|
147
|
+
runQuiet(binary, ['skills', 'install', '--target', target, '--scope', 'user', '--yes'])
|
|
148
|
+
}
|
|
149
|
+
spinner.stop(`Skills 已安装(${targets.map((id) => AGENTS.find((agent) => agent.id === id)?.name ?? id).join('、')})`)
|
|
150
|
+
} catch (error) {
|
|
151
|
+
spinner.stop('Skills 安装失败')
|
|
152
|
+
p.log.error(error instanceof Error ? error.message : String(error))
|
|
153
|
+
p.log.info('可稍后执行:ahsops skills install --target claude-code --scope user --yes')
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function writeProfile(binary, server, profile) {
|
|
158
|
+
const profileName = profile || 'office'
|
|
159
|
+
runQuiet(binary, ['profile', 'add', profileName, '--server', server])
|
|
160
|
+
runQuiet(binary, ['profile', 'use', profileName])
|
|
161
|
+
p.log.success(`已写入 profile ${profileName} → ${server}`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function maybeLogin(binary) {
|
|
165
|
+
const yes = await p.confirm({
|
|
166
|
+
message: '是否现在登录 AHSOpsHub?会打开浏览器,由你本人批准,不要打印或转发 Token',
|
|
167
|
+
initialValue: true,
|
|
168
|
+
})
|
|
169
|
+
if (p.isCancel(yes)) {
|
|
170
|
+
p.cancel('安装已取消')
|
|
171
|
+
process.exit(0)
|
|
172
|
+
}
|
|
173
|
+
if (!yes) {
|
|
174
|
+
p.log.info('跳过授权。后续运行 ahsops auth login --recommend 完成登录')
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
p.log.step('授权')
|
|
178
|
+
try {
|
|
179
|
+
run(binary, ['auth', 'login', '--recommend'])
|
|
180
|
+
p.log.success('授权完成')
|
|
181
|
+
} catch {
|
|
182
|
+
p.log.warn('授权失败。后续运行 ahsops auth login --recommend 重试')
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function install() {
|
|
187
|
+
const { server, profile } = parseInstallFlags(process.argv.slice(2))
|
|
188
|
+
const interactive = Boolean(stdin.isTTY)
|
|
189
|
+
|
|
190
|
+
if (!interactive) {
|
|
191
|
+
console.error('正在设置 AHSOpsHub CLI...')
|
|
192
|
+
runQuiet('npm', ['install', '-g', packageRoot()], { timeout: 120000 })
|
|
193
|
+
const binary = globalAhsops()
|
|
194
|
+
if (server) {
|
|
195
|
+
runQuiet(binary, ['profile', 'add', profile || 'office', '--server', server])
|
|
196
|
+
runQuiet(binary, ['profile', 'use', profile || 'office'])
|
|
197
|
+
}
|
|
198
|
+
console.error('当前不是交互终端。请在终端中运行:')
|
|
199
|
+
console.error(' npx @chuhe2640/ahsopus-cli@latest install --server <当前站点> --profile <dev|office|prod>')
|
|
200
|
+
console.error(' ahsops auth login --recommend')
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
p.intro('正在设置 AHSOpsHub CLI...')
|
|
205
|
+
await installGlobally()
|
|
206
|
+
const binary = globalAhsops()
|
|
207
|
+
if (server) writeProfile(binary, server, profile)
|
|
208
|
+
const targets = await chooseAgents()
|
|
209
|
+
await installSkills(binary, targets)
|
|
210
|
+
await maybeLogin(binary)
|
|
211
|
+
p.outro('安装完成!\n可以和你的 AI 工具(如 Claude Code、Codex、Cursor、Grok)说:“AHSOpsHub CLI 能帮我做什么?”')
|
|
212
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// INPUT: 当前平台,以及 npm 包内的 prebuilt/ 或本机 Go
|
|
2
|
+
// OUTPUT: native/ahsops 可执行文件路径
|
|
3
|
+
// POS: postinstall 放置预编译二进制;用户安装不要求 Go
|
|
4
|
+
|
|
5
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
6
|
+
import { spawnSync } from 'node:child_process'
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
import { currentTarget } from './platforms.js'
|
|
10
|
+
|
|
11
|
+
export function packageRoot() {
|
|
12
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function nativeBinaryPath() {
|
|
16
|
+
const ext = process.platform === 'win32' ? '.exe' : ''
|
|
17
|
+
return path.join(packageRoot(), 'native', `ahsops${ext}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolvePrebuiltPath(nodePlatform = process.platform, nodeArch = process.arch) {
|
|
21
|
+
const target = currentTarget(nodePlatform, nodeArch)
|
|
22
|
+
return path.join(packageRoot(), 'prebuilt', target.file)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function commandExists(name) {
|
|
26
|
+
const probe = process.platform === 'win32' ? 'where' : 'which'
|
|
27
|
+
return spawnSync(probe, [name], { encoding: 'utf8', stdio: 'pipe' }).status === 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function placeBinary() {
|
|
31
|
+
const destination = nativeBinaryPath()
|
|
32
|
+
mkdirSync(path.dirname(destination), { recursive: true })
|
|
33
|
+
const prebuilt = resolvePrebuiltPath()
|
|
34
|
+
if (existsSync(prebuilt)) {
|
|
35
|
+
copyFileSync(prebuilt, destination)
|
|
36
|
+
chmodSync(destination, 0o755)
|
|
37
|
+
return destination
|
|
38
|
+
}
|
|
39
|
+
if (commandExists('go')) {
|
|
40
|
+
const result = spawnSync('go', ['build', '-o', destination, './cmd/ahsops'], {
|
|
41
|
+
cwd: packageRoot(),
|
|
42
|
+
stdio: 'inherit',
|
|
43
|
+
})
|
|
44
|
+
if (result.status !== 0) {
|
|
45
|
+
throw new Error('本机 go build 失败')
|
|
46
|
+
}
|
|
47
|
+
return destination
|
|
48
|
+
}
|
|
49
|
+
throw new Error(
|
|
50
|
+
'未找到本平台预编译文件。请执行 npx @chuhe2640/ahsopus-cli@latest install(只需 Node.js)。从源码安装才需要 Go 1.23+。',
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
55
|
+
try {
|
|
56
|
+
placeBinary()
|
|
57
|
+
} catch (error) {
|
|
58
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// INPUT: Node 的 process.platform / process.arch
|
|
2
|
+
// OUTPUT: 与预编译文件名对应的 GOOS/GOARCH 目标
|
|
3
|
+
// POS: npm 安装器与交叉编译共用的平台映射,不含 Token
|
|
4
|
+
|
|
5
|
+
export const TARGETS = [
|
|
6
|
+
{ id: 'darwin-arm64', goos: 'darwin', goarch: 'arm64', file: 'ahsops-darwin-arm64' },
|
|
7
|
+
{ id: 'darwin-amd64', goos: 'darwin', goarch: 'amd64', file: 'ahsops-darwin-amd64' },
|
|
8
|
+
{ id: 'linux-arm64', goos: 'linux', goarch: 'arm64', file: 'ahsops-linux-arm64' },
|
|
9
|
+
{ id: 'linux-amd64', goos: 'linux', goarch: 'amd64', file: 'ahsops-linux-amd64' },
|
|
10
|
+
{ id: 'windows-amd64', goos: 'windows', goarch: 'amd64', file: 'ahsops-windows-amd64.exe' },
|
|
11
|
+
{ id: 'windows-arm64', goos: 'windows', goarch: 'arm64', file: 'ahsops-windows-arm64.exe' },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export function currentTarget(nodePlatform = process.platform, nodeArch = process.arch) {
|
|
15
|
+
const goos = nodePlatform === 'win32' ? 'windows' : nodePlatform
|
|
16
|
+
const goarch = nodeArch === 'x64' ? 'amd64' : nodeArch
|
|
17
|
+
const found = TARGETS.find((target) => target.goos === goos && target.goarch === goarch)
|
|
18
|
+
if (!found) {
|
|
19
|
+
throw new Error(`不支持的平台: ${nodePlatform}/${nodeArch}`)
|
|
20
|
+
}
|
|
21
|
+
return found
|
|
22
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chuhe2640/ahsopus-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install AHSOpsHub CLI (ahsops) and Agent Skills",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ahsops": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"prepublishOnly": "node npm/lib/build-binaries.js",
|
|
11
|
+
"test": "node --test npm/lib/*.test.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@clack/prompts": "^1.7.0"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin/cli.js",
|
|
18
|
+
"npm/lib/install.js",
|
|
19
|
+
"npm/lib/place-binary.js",
|
|
20
|
+
"npm/lib/platforms.js",
|
|
21
|
+
"prebuilt"
|
|
22
|
+
],
|
|
23
|
+
"os": [
|
|
24
|
+
"darwin",
|
|
25
|
+
"linux",
|
|
26
|
+
"win32"
|
|
27
|
+
],
|
|
28
|
+
"cpu": [
|
|
29
|
+
"x64",
|
|
30
|
+
"arm64"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"license": "UNLICENSED",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/chuheridangwua/datalook.git",
|
|
42
|
+
"directory": "cli"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|