@chuhe2640/ahsopus-cli 0.1.0 → 0.1.2
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/bin/cli.js +3 -2
- package/internal/skills/agents.json +74 -0
- package/npm/lib/agents.js +59 -0
- package/npm/lib/install.js +83 -72
- package/npm/lib/place-binary.js +88 -10
- package/package.json +4 -2
- 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/bin/cli.js
CHANGED
|
@@ -4,8 +4,9 @@ import { existsSync } from 'node:fs'
|
|
|
4
4
|
import { install } from '../npm/lib/install.js'
|
|
5
5
|
import { nativeBinaryPath, placeBinary } from '../npm/lib/place-binary.js'
|
|
6
6
|
|
|
7
|
-
const args = process.argv.slice(2)
|
|
8
|
-
|
|
7
|
+
const args = process.argv.slice(2).filter((value) => value !== '--')
|
|
8
|
+
const command = args.find((value) => !value.startsWith('-')) ?? 'install'
|
|
9
|
+
if (command === 'install') {
|
|
9
10
|
try {
|
|
10
11
|
await install()
|
|
11
12
|
} catch (error) {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[
|
|
2
|
+
{"id":"aider-desk","name":"AiderDesk","detect":["~/.aider-desk"],"skills":["~/.aider-desk/skills"]},
|
|
3
|
+
{"id":"amp","name":"Amp","detect":["~config/amp"],"skills":["~config/agents/skills"]},
|
|
4
|
+
{"id":"antigravity","name":"Antigravity","detect":["~/.gemini/antigravity"],"skills":["~/.gemini/antigravity/skills"]},
|
|
5
|
+
{"id":"antigravity-cli","name":"Antigravity CLI","detect":["~/.gemini/antigravity-cli"],"skills":["~/.gemini/antigravity-cli/skills"]},
|
|
6
|
+
{"id":"astrbot","name":"AstrBot","detect":["~/.astrbot"],"skills":["~/.astrbot/data/skills"]},
|
|
7
|
+
{"id":"autohand-code","name":"Autohand Code CLI","detect":["$AUTOHAND_HOME","~/.autohand"],"skills":["$AUTOHAND_HOME/skills","~/.autohand/skills"]},
|
|
8
|
+
{"id":"augment","name":"Augment","detect":["~/.augment"],"skills":["~/.augment/skills"]},
|
|
9
|
+
{"id":"bob","name":"IBM Bob","detect":["~/.bob"],"skills":["~/.bob/skills"]},
|
|
10
|
+
{"id":"claude-code","name":"Claude Code","detect":["$CLAUDE_CONFIG_DIR","~/.claude"],"skills":["$CLAUDE_CONFIG_DIR/skills","~/.claude/skills"]},
|
|
11
|
+
{"id":"openclaw","name":"OpenClaw","detect":["~/.openclaw","~/.clawdbot","~/.moltbot"],"skills":["~/.openclaw/skills"]},
|
|
12
|
+
{"id":"cline","name":"Cline","detect":["~/.cline"],"skills":["~/.agents/skills"]},
|
|
13
|
+
{"id":"codearts-agent","name":"CodeArts Agent","detect":["~/.codeartsdoer"],"skills":["~/.codeartsdoer/skills"]},
|
|
14
|
+
{"id":"codebuddy","name":"CodeBuddy","detect":["~/.codebuddy"],"skills":["~/.codebuddy/skills"]},
|
|
15
|
+
{"id":"codemaker","name":"Codemaker","detect":["~/.codemaker"],"skills":["~/.codemaker/skills"]},
|
|
16
|
+
{"id":"codestudio","name":"Code Studio","detect":["~/.codestudio"],"skills":["~/.codestudio/skills"]},
|
|
17
|
+
{"id":"codex","name":"Codex","detect":["$CODEX_HOME","~/.codex"],"skills":["$CODEX_HOME/skills","~/.codex/skills"]},
|
|
18
|
+
{"id":"command-code","name":"Command Code","detect":["~/.commandcode"],"skills":["~/.commandcode/skills"]},
|
|
19
|
+
{"id":"continue","name":"Continue","detect":["~/.continue"],"skills":["~/.continue/skills"]},
|
|
20
|
+
{"id":"cortex","name":"Cortex Code","detect":["~/.snowflake/cortex"],"skills":["~/.snowflake/cortex/skills"]},
|
|
21
|
+
{"id":"crush","name":"Crush","detect":["~config/crush"],"skills":["~config/crush/skills"]},
|
|
22
|
+
{"id":"cursor","name":"Cursor","detect":["~/.cursor"],"skills":["~/.cursor/skills"]},
|
|
23
|
+
{"id":"deepagents","name":"Deep Agents","detect":["~/.deepagents"],"skills":["~/.deepagents/agent/skills"]},
|
|
24
|
+
{"id":"devin","name":"Devin for Terminal","detect":["~config/devin"],"skills":["~config/devin/skills"]},
|
|
25
|
+
{"id":"dexto","name":"Dexto","detect":["~/.dexto"],"skills":["~/.agents/skills"]},
|
|
26
|
+
{"id":"droid","name":"Droid","detect":["~/.factory"],"skills":["~/.factory/skills"]},
|
|
27
|
+
{"id":"firebender","name":"Firebender","detect":["~/.firebender"],"skills":["~/.firebender/skills"]},
|
|
28
|
+
{"id":"forgecode","name":"ForgeCode","detect":["~/.forge"],"skills":["~/.forge/skills"]},
|
|
29
|
+
{"id":"gemini-cli","name":"Gemini CLI","detect":["~/.gemini"],"skills":["~/.gemini/skills"]},
|
|
30
|
+
{"id":"github-copilot","name":"GitHub Copilot","detect":["~/.copilot"],"skills":["~/.copilot/skills"]},
|
|
31
|
+
{"id":"goose","name":"Goose","detect":["~config/goose"],"skills":["~config/goose/skills"]},
|
|
32
|
+
{"id":"grok","name":"Grok","detect":["$GROK_HOME","~/.grok"],"skills":["$GROK_HOME/skills","~/.grok/skills"]},
|
|
33
|
+
{"id":"hermes-agent","name":"Hermes Agent","detect":["$HERMES_HOME","~/.hermes"],"skills":["$HERMES_HOME/skills","~/.hermes/skills"]},
|
|
34
|
+
{"id":"inference-sh","name":"inference.sh","detect":["~/.inferencesh"],"skills":["~/.inferencesh/skills"]},
|
|
35
|
+
{"id":"jazz","name":"Jazz","detect":["~/.jazz"],"skills":["~/.jazz/skills"]},
|
|
36
|
+
{"id":"junie","name":"Junie","detect":["~/.junie"],"skills":["~/.junie/skills"]},
|
|
37
|
+
{"id":"iflow-cli","name":"iFlow CLI","detect":["~/.iflow"],"skills":["~/.iflow/skills"]},
|
|
38
|
+
{"id":"kilo","name":"Kilo Code","detect":["~/.kilocode"],"skills":["~/.kilocode/skills"]},
|
|
39
|
+
{"id":"kimchi","name":"Kimchi","detect":["~config/kimchi"],"skills":["~config/kimchi/harness/skills"]},
|
|
40
|
+
{"id":"kimi-code-cli","name":"Kimi Code CLI","detect":["~/.kimi-code","~/.kimi"],"skills":["~/.agents/skills"]},
|
|
41
|
+
{"id":"kiro-cli","name":"Kiro CLI","detect":["~/.kiro"],"skills":["~/.kiro/skills"]},
|
|
42
|
+
{"id":"kode","name":"Kode","detect":["~/.kode"],"skills":["~/.kode/skills"]},
|
|
43
|
+
{"id":"lingma","name":"Lingma","detect":["~/.lingma"],"skills":["~/.lingma/skills"]},
|
|
44
|
+
{"id":"loaf","name":"Loaf","detect":["~/.loaf"],"skills":["~/.agents/skills"]},
|
|
45
|
+
{"id":"mcpjam","name":"MCPJam","detect":["~/.mcpjam"],"skills":["~/.mcpjam/skills"]},
|
|
46
|
+
{"id":"minimax-code","name":"MiniMax Code","detect":["~/.minimax"],"skills":["~/.minimax/skills"]},
|
|
47
|
+
{"id":"mistral-vibe","name":"Mistral Vibe","detect":["$VIBE_HOME","~/.vibe"],"skills":["$VIBE_HOME/skills","~/.vibe/skills"]},
|
|
48
|
+
{"id":"moxby","name":"Moxby","detect":["~/.moxby"],"skills":["~/.moxby/skills"]},
|
|
49
|
+
{"id":"mux","name":"Mux","detect":["~/.mux"],"skills":["~/.mux/skills"]},
|
|
50
|
+
{"id":"opencode","name":"OpenCode","detect":["~config/opencode"],"skills":["~config/opencode/skills"]},
|
|
51
|
+
{"id":"openhands","name":"OpenHands","detect":["~/.openhands"],"skills":["~/.openhands/skills"]},
|
|
52
|
+
{"id":"ona","name":"Ona","detect":["~/.ona"],"skills":["~/.ona/skills"]},
|
|
53
|
+
{"id":"pi","name":"Pi","detect":["~/.pi/agent"],"skills":["~/.pi/agent/skills"]},
|
|
54
|
+
{"id":"posit-assistant","name":"Posit Assistant","detect":["~/.posit"],"skills":["~/.posit/assistant/skills"]},
|
|
55
|
+
{"id":"qoder","name":"Qoder","detect":["~/.qoder"],"skills":["~/.qoder/skills"]},
|
|
56
|
+
{"id":"qoder-cn","name":"Qoder CN","detect":["~/.qoder-cn"],"skills":["~/.qoder-cn/skills"]},
|
|
57
|
+
{"id":"qwen-code","name":"Qwen Code","detect":["~/.qwen"],"skills":["~/.qwen/skills"]},
|
|
58
|
+
{"id":"reasonix","name":"Reasonix","detect":["~/.reasonix"],"skills":["~/.reasonix/skills"]},
|
|
59
|
+
{"id":"rovodev","name":"Rovo Dev","detect":["~/.rovodev"],"skills":["~/.rovodev/skills"]},
|
|
60
|
+
{"id":"roo","name":"Roo Code","detect":["~/.roo"],"skills":["~/.roo/skills"]},
|
|
61
|
+
{"id":"tabnine-cli","name":"Tabnine CLI","detect":["~/.tabnine"],"skills":["~/.tabnine/agent/skills"]},
|
|
62
|
+
{"id":"terramind","name":"Terramind","detect":["~/.terramind"],"skills":["~/.terramind/skills"]},
|
|
63
|
+
{"id":"tinycloud","name":"Tinycloud","detect":["~/.tinycloud"],"skills":["~/.tinycloud/skills"]},
|
|
64
|
+
{"id":"trae","name":"Trae","detect":["~/.trae"],"skills":["~/.trae/skills"]},
|
|
65
|
+
{"id":"trae-cn","name":"Trae CN","detect":["~/.trae-cn"],"skills":["~/.trae-cn/skills"]},
|
|
66
|
+
{"id":"warp","name":"Warp","detect":["~/.warp"],"skills":["~/.agents/skills"]},
|
|
67
|
+
{"id":"windsurf","name":"Windsurf","detect":["~/.codeium/windsurf"],"skills":["~/.codeium/windsurf/skills"]},
|
|
68
|
+
{"id":"zed","name":"Zed","detect":["~config/zed"],"skills":["~/.agents/skills"]},
|
|
69
|
+
{"id":"zcode","name":"ZCode","detect":["~/.zcode"],"skills":["~/.zcode/skills"]},
|
|
70
|
+
{"id":"zencoder","name":"Zencoder","detect":["~/.zencoder"],"skills":["~/.zencoder/skills"]},
|
|
71
|
+
{"id":"neovate","name":"Neovate","detect":["~/.neovate"],"skills":["~/.neovate/skills"]},
|
|
72
|
+
{"id":"pochi","name":"Pochi","detect":["~/.pochi"],"skills":["~/.pochi/skills"]},
|
|
73
|
+
{"id":"adal","name":"AdaL","detect":["~/.adal"],"skills":["~/.adal/skills"]}
|
|
74
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// INPUT: agents.json(飞书 CLI npx skills add -g 的完整客户端目录)
|
|
2
|
+
// OUTPUT: 可检测的本机 Agent 与展示名
|
|
3
|
+
// POS: 安装向导与飞书 skills CLI 客户端目录对齐
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
6
|
+
import { homedir } from 'node:os'
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
function loadCatalog() {
|
|
11
|
+
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
const candidates = [
|
|
13
|
+
path.join(here, '../../internal/skills/agents.json'),
|
|
14
|
+
path.join(here, 'agents.json'),
|
|
15
|
+
]
|
|
16
|
+
for (const file of candidates) {
|
|
17
|
+
if (existsSync(file)) {
|
|
18
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
throw new Error('未找到 agents.json')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const AGENTS = loadCatalog()
|
|
25
|
+
|
|
26
|
+
function configHome(home) {
|
|
27
|
+
const value = process.env.XDG_CONFIG_HOME?.trim()
|
|
28
|
+
return value || path.join(home, '.config')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function expandPath(pattern, home = homedir()) {
|
|
32
|
+
if (pattern.startsWith('$')) {
|
|
33
|
+
const slash = pattern.indexOf('/')
|
|
34
|
+
const envName = slash === -1 ? pattern.slice(1) : pattern.slice(1, slash)
|
|
35
|
+
const value = process.env[envName]?.trim()
|
|
36
|
+
if (!value) return null
|
|
37
|
+
return slash === -1 ? value : path.join(value, pattern.slice(slash + 1))
|
|
38
|
+
}
|
|
39
|
+
if (pattern.startsWith('~config/')) {
|
|
40
|
+
return path.join(configHome(home), pattern.slice('~config/'.length))
|
|
41
|
+
}
|
|
42
|
+
if (pattern.startsWith('~/')) {
|
|
43
|
+
return path.join(home, pattern.slice(2))
|
|
44
|
+
}
|
|
45
|
+
return pattern
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function detectedAgents(home = homedir()) {
|
|
49
|
+
return AGENTS.filter((agent) =>
|
|
50
|
+
agent.detect.some((pattern) => {
|
|
51
|
+
const resolved = expandPath(pattern, home)
|
|
52
|
+
return resolved && existsSync(resolved)
|
|
53
|
+
}),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function agentName(id) {
|
|
58
|
+
return AGENTS.find((agent) => agent.id === id)?.name ?? id
|
|
59
|
+
}
|
package/npm/lib/install.js
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
// INPUT: 本机 Node、已安装的 Agent、终端是否可交互
|
|
2
|
-
// OUTPUT:
|
|
3
|
-
// POS: npx @chuhe2640/ahsopus-cli install 的跨平台入口,对齐飞书 CLI
|
|
2
|
+
// OUTPUT: 安装或升级 ahsops,写入 Skills,并按当前站点配置 profile
|
|
3
|
+
// POS: npx @chuhe2640/ahsopus-cli install 的跨平台入口,对齐飞书 CLI 安装向导
|
|
4
4
|
|
|
5
5
|
import * as p from '@clack/prompts'
|
|
6
6
|
import { spawnSync } from 'node:child_process'
|
|
7
7
|
import { existsSync, readFileSync } from 'node:fs'
|
|
8
|
-
import {
|
|
9
|
-
import path from 'node:path'
|
|
8
|
+
import { platform } from 'node:os'
|
|
10
9
|
import { stdin } from 'node:process'
|
|
11
|
-
import {
|
|
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
|
-
]
|
|
10
|
+
import { AGENTS, detectedAgents } from './agents.js'
|
|
11
|
+
import { installUserBinary, resolveInstalledAhsops } from './place-binary.js'
|
|
19
12
|
|
|
20
13
|
const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'))
|
|
21
14
|
|
|
@@ -45,6 +38,17 @@ export function parseInstallFlags(argv) {
|
|
|
45
38
|
return { server, profile }
|
|
46
39
|
}
|
|
47
40
|
|
|
41
|
+
export function semverLessThan(left, right) {
|
|
42
|
+
const parse = (value) => value.replace(/-.*$/, '').split('.').map((part) => Number(part) || 0)
|
|
43
|
+
const a = parse(left)
|
|
44
|
+
const b = parse(right)
|
|
45
|
+
for (let index = 0; index < 3; index += 1) {
|
|
46
|
+
if (a[index] < b[index]) return true
|
|
47
|
+
if (a[index] > b[index]) return false
|
|
48
|
+
}
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
function execFile(command, args, options) {
|
|
49
53
|
if (platform() === 'win32') {
|
|
50
54
|
return spawnSync('cmd.exe', ['/c', command, ...args], options)
|
|
@@ -71,60 +75,52 @@ function runQuiet(command, args, options = {}) {
|
|
|
71
75
|
return run(command, args, { stdio: ['ignore', 'pipe', 'pipe'], ...options })
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
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() {
|
|
78
|
+
function installedVersion(binary) {
|
|
79
|
+
if (!binary || !existsSync(binary)) return null
|
|
87
80
|
try {
|
|
88
|
-
const out = runQuiet(
|
|
89
|
-
const match = String(out).match(
|
|
81
|
+
const out = runQuiet(binary, ['--version'])
|
|
82
|
+
const match = String(`${out.stdout}\n${out.stderr}`).match(/(\d+\.\d+\.\d+[^\s]*)/)
|
|
90
83
|
return match ? match[1] : null
|
|
91
84
|
} catch {
|
|
92
85
|
return null
|
|
93
86
|
}
|
|
94
87
|
}
|
|
95
88
|
|
|
96
|
-
async function
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
89
|
+
async function installCliBinary() {
|
|
90
|
+
const current = resolveInstalledAhsops()
|
|
91
|
+
const currentVersion = installedVersion(current)
|
|
92
|
+
if (currentVersion && currentVersion === pkg.version) {
|
|
93
|
+
p.log.info(`已安装 (v${currentVersion}),跳过`)
|
|
94
|
+
return current
|
|
101
95
|
}
|
|
102
96
|
const spinner = p.spinner()
|
|
103
|
-
|
|
97
|
+
const upgrading = Boolean(currentVersion && semverLessThan(currentVersion, pkg.version))
|
|
98
|
+
spinner.start(
|
|
99
|
+
upgrading
|
|
100
|
+
? `正在升级 ${pkg.name} (v${currentVersion} → v${pkg.version})...`
|
|
101
|
+
: `正在安装 ${pkg.name}...`,
|
|
102
|
+
)
|
|
104
103
|
try {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
} catch {
|
|
112
|
-
spinner.stop(`全局安装失败。运行以下命令重试: npm install -g ${pkg.name}`)
|
|
104
|
+
const file = installUserBinary()
|
|
105
|
+
spinner.stop(upgrading ? `已升级到 v${pkg.version}` : '已全局安装')
|
|
106
|
+
return file
|
|
107
|
+
} catch (error) {
|
|
108
|
+
spinner.stop(`全局安装失败。运行以下命令重试: npx ${pkg.name}@latest install`)
|
|
109
|
+
p.log.error(error instanceof Error ? error.message : String(error))
|
|
113
110
|
process.exit(1)
|
|
114
111
|
}
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
async function chooseAgents() {
|
|
118
115
|
const found = detectedAgents()
|
|
116
|
+
const options = (found.length ? found : AGENTS).map((agent) => ({
|
|
117
|
+
value: agent.id,
|
|
118
|
+
label: agent.name,
|
|
119
|
+
hint: found.some((item) => item.id === agent.id) ? '已检测到' : undefined,
|
|
120
|
+
}))
|
|
119
121
|
const selected = await p.multiselect({
|
|
120
|
-
message:
|
|
121
|
-
|
|
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
|
-
})),
|
|
122
|
+
message: '安装 AI Skills',
|
|
123
|
+
options,
|
|
128
124
|
initialValues: found.map((agent) => agent.id),
|
|
129
125
|
required: false,
|
|
130
126
|
})
|
|
@@ -135,35 +131,56 @@ async function chooseAgents() {
|
|
|
135
131
|
return selected
|
|
136
132
|
}
|
|
137
133
|
|
|
134
|
+
function skillsInSync(binary, target) {
|
|
135
|
+
try {
|
|
136
|
+
const out = runQuiet(binary, ['skills', 'status', '--target', target, '--scope', 'user'])
|
|
137
|
+
const parsed = JSON.parse(out.stdout)
|
|
138
|
+
const items = Array.isArray(parsed.data) ? parsed.data : parsed
|
|
139
|
+
return Array.isArray(items) && items.length > 0 && items.every((item) => item.in_sync)
|
|
140
|
+
} catch {
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
138
145
|
async function installSkills(binary, targets) {
|
|
139
146
|
if (!targets.length) {
|
|
140
147
|
p.log.info('已跳过 Skills 安装')
|
|
141
148
|
return
|
|
142
149
|
}
|
|
150
|
+
if (targets.every((target) => skillsInSync(binary, target))) {
|
|
151
|
+
p.log.info('已安装,跳过')
|
|
152
|
+
return
|
|
153
|
+
}
|
|
143
154
|
const spinner = p.spinner()
|
|
144
155
|
spinner.start('正在安装 Skills...')
|
|
145
|
-
|
|
146
|
-
|
|
156
|
+
let failed = 0
|
|
157
|
+
for (const target of targets) {
|
|
158
|
+
try {
|
|
147
159
|
runQuiet(binary, ['skills', 'install', '--target', target, '--scope', 'user', '--yes'])
|
|
160
|
+
} catch {
|
|
161
|
+
failed += 1
|
|
148
162
|
}
|
|
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
163
|
}
|
|
164
|
+
if (failed === 0) {
|
|
165
|
+
spinner.stop('Skills 已安装')
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
if (failed < targets.length) {
|
|
169
|
+
spinner.stop('Skills 已安装')
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
spinner.stop('Skills 安装失败')
|
|
155
173
|
}
|
|
156
174
|
|
|
157
175
|
function writeProfile(binary, server, profile) {
|
|
158
176
|
const profileName = profile || 'office'
|
|
159
177
|
runQuiet(binary, ['profile', 'add', profileName, '--server', server])
|
|
160
178
|
runQuiet(binary, ['profile', 'use', profileName])
|
|
161
|
-
p.log.success(`已写入 profile ${profileName} → ${server}`)
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
async function maybeLogin(binary) {
|
|
165
182
|
const yes = await p.confirm({
|
|
166
|
-
message: '
|
|
183
|
+
message: '是否允许 AI 访问你的 AHSOpsHub 数据,并以你的名义执行操作?',
|
|
167
184
|
initialValue: true,
|
|
168
185
|
})
|
|
169
186
|
if (p.isCancel(yes)) {
|
|
@@ -171,7 +188,7 @@ async function maybeLogin(binary) {
|
|
|
171
188
|
process.exit(0)
|
|
172
189
|
}
|
|
173
190
|
if (!yes) {
|
|
174
|
-
p.log.info('跳过授权。后续运行 ahsops auth login
|
|
191
|
+
p.log.info('跳过授权。后续运行 ahsops auth login 完成授权')
|
|
175
192
|
return
|
|
176
193
|
}
|
|
177
194
|
p.log.step('授权')
|
|
@@ -179,7 +196,7 @@ async function maybeLogin(binary) {
|
|
|
179
196
|
run(binary, ['auth', 'login', '--recommend'])
|
|
180
197
|
p.log.success('授权完成')
|
|
181
198
|
} catch {
|
|
182
|
-
p.log.warn('
|
|
199
|
+
p.log.warn('授权失败。运行以下命令重试: ahsops auth login')
|
|
183
200
|
}
|
|
184
201
|
}
|
|
185
202
|
|
|
@@ -189,24 +206,18 @@ export async function install() {
|
|
|
189
206
|
|
|
190
207
|
if (!interactive) {
|
|
191
208
|
console.error('正在设置 AHSOpsHub CLI...')
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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')
|
|
209
|
+
const binary = installUserBinary()
|
|
210
|
+
if (server) writeProfile(binary, server, profile)
|
|
211
|
+
console.error('要完成配置,请在终端中运行:')
|
|
212
|
+
console.error(' ahsops auth login')
|
|
201
213
|
return
|
|
202
214
|
}
|
|
203
215
|
|
|
204
216
|
p.intro('正在设置 AHSOpsHub CLI...')
|
|
205
|
-
await
|
|
206
|
-
const binary = globalAhsops()
|
|
217
|
+
const binary = await installCliBinary()
|
|
207
218
|
if (server) writeProfile(binary, server, profile)
|
|
208
219
|
const targets = await chooseAgents()
|
|
209
220
|
await installSkills(binary, targets)
|
|
210
221
|
await maybeLogin(binary)
|
|
211
|
-
p.outro('安装完成!\n可以和你的 AI 工具(如 Claude Code、
|
|
222
|
+
p.outro('安装完成!\n可以和你的 AI 工具(如 Claude Code、Trae等)说:“AHSOpsHub CLI 能帮我做什么?结合我的情况推荐一下从哪里开始”')
|
|
212
223
|
}
|
package/npm/lib/place-binary.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// INPUT: 当前平台,以及 npm 包内的 prebuilt/ 或本机 Go
|
|
2
2
|
// OUTPUT: native/ahsops 可执行文件路径
|
|
3
|
-
// POS:
|
|
3
|
+
// POS: 把预编译 ahsops 写到用户 bin,覆盖已有同名命令;用户安装不要求 Go
|
|
4
4
|
|
|
5
|
-
import { chmodSync, copyFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
5
|
+
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, unlinkSync } from 'node:fs'
|
|
6
6
|
import { spawnSync } from 'node:child_process'
|
|
7
|
+
import { homedir } from 'node:os'
|
|
7
8
|
import path from 'node:path'
|
|
8
9
|
import { fileURLToPath } from 'node:url'
|
|
9
10
|
import { currentTarget } from './platforms.js'
|
|
@@ -17,6 +18,51 @@ export function nativeBinaryPath() {
|
|
|
17
18
|
return path.join(packageRoot(), 'native', `ahsops${ext}`)
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
export function userBinaryTarget() {
|
|
22
|
+
if (process.platform === 'win32') {
|
|
23
|
+
const dir = path.join(process.env.LOCALAPPDATA || homedir(), 'AHSOpsHub', 'bin')
|
|
24
|
+
return { dir, file: path.join(dir, 'ahsops.exe') }
|
|
25
|
+
}
|
|
26
|
+
const dir = path.join(homedir(), '.local', 'bin')
|
|
27
|
+
return { dir, file: path.join(dir, 'ahsops') }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isEphemeralBin(file) {
|
|
31
|
+
const normalized = file.replaceAll('\\', '/')
|
|
32
|
+
return (
|
|
33
|
+
normalized.includes('/.npm/_npx/') ||
|
|
34
|
+
normalized.includes('/.npm/_cacache/') ||
|
|
35
|
+
normalized.includes('/node_modules/')
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function whichAhsops() {
|
|
40
|
+
const probe = process.platform === 'win32' ? 'where' : 'which'
|
|
41
|
+
const result = spawnSync(probe, ['ahsops'], { encoding: 'utf8', stdio: 'pipe' })
|
|
42
|
+
if (result.status !== 0) return ''
|
|
43
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || ''
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveInstalledAhsops() {
|
|
47
|
+
const { file } = userBinaryTarget()
|
|
48
|
+
if (existsSync(file)) return file
|
|
49
|
+
const existing = whichAhsops()
|
|
50
|
+
if (existing && !isEphemeralBin(existing)) return existing
|
|
51
|
+
return ''
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function copyBinary(source, destination) {
|
|
55
|
+
mkdirSync(path.dirname(destination), { recursive: true })
|
|
56
|
+
try {
|
|
57
|
+
const stat = lstatSync(destination)
|
|
58
|
+
if (stat.isFile() || stat.isSymbolicLink()) unlinkSync(destination)
|
|
59
|
+
} catch {
|
|
60
|
+
// 目标不存在时直接写入
|
|
61
|
+
}
|
|
62
|
+
copyFileSync(source, destination)
|
|
63
|
+
chmodSync(destination, 0o755)
|
|
64
|
+
}
|
|
65
|
+
|
|
20
66
|
export function resolvePrebuiltPath(nodePlatform = process.platform, nodeArch = process.arch) {
|
|
21
67
|
const target = currentTarget(nodePlatform, nodeArch)
|
|
22
68
|
return path.join(packageRoot(), 'prebuilt', target.file)
|
|
@@ -27,16 +73,12 @@ function commandExists(name) {
|
|
|
27
73
|
return spawnSync(probe, [name], { encoding: 'utf8', stdio: 'pipe' }).status === 0
|
|
28
74
|
}
|
|
29
75
|
|
|
30
|
-
|
|
31
|
-
const destination = nativeBinaryPath()
|
|
32
|
-
mkdirSync(path.dirname(destination), { recursive: true })
|
|
76
|
+
function resolveSourceBinary() {
|
|
33
77
|
const prebuilt = resolvePrebuiltPath()
|
|
34
|
-
if (existsSync(prebuilt))
|
|
35
|
-
copyFileSync(prebuilt, destination)
|
|
36
|
-
chmodSync(destination, 0o755)
|
|
37
|
-
return destination
|
|
38
|
-
}
|
|
78
|
+
if (existsSync(prebuilt)) return prebuilt
|
|
39
79
|
if (commandExists('go')) {
|
|
80
|
+
const destination = nativeBinaryPath()
|
|
81
|
+
mkdirSync(path.dirname(destination), { recursive: true })
|
|
40
82
|
const result = spawnSync('go', ['build', '-o', destination, './cmd/ahsops'], {
|
|
41
83
|
cwd: packageRoot(),
|
|
42
84
|
stdio: 'inherit',
|
|
@@ -51,6 +93,42 @@ export function placeBinary() {
|
|
|
51
93
|
)
|
|
52
94
|
}
|
|
53
95
|
|
|
96
|
+
export function placeBinary() {
|
|
97
|
+
const destination = nativeBinaryPath()
|
|
98
|
+
copyBinary(resolveSourceBinary(), destination)
|
|
99
|
+
return destination
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function installUserBinary() {
|
|
103
|
+
const source = resolveSourceBinary()
|
|
104
|
+
const { file: userFile } = userBinaryTarget()
|
|
105
|
+
const existing = process.platform === 'win32' ? '' : whichAhsops()
|
|
106
|
+
const destinations = [userFile]
|
|
107
|
+
if (
|
|
108
|
+
existing &&
|
|
109
|
+
!isEphemeralBin(existing) &&
|
|
110
|
+
path.resolve(existing) !== path.resolve(userFile)
|
|
111
|
+
) {
|
|
112
|
+
destinations.push(existing)
|
|
113
|
+
}
|
|
114
|
+
const written = []
|
|
115
|
+
const errors = []
|
|
116
|
+
for (const destination of destinations) {
|
|
117
|
+
try {
|
|
118
|
+
copyBinary(source, destination)
|
|
119
|
+
written.push(destination)
|
|
120
|
+
} catch (error) {
|
|
121
|
+
errors.push(`${destination}: ${error instanceof Error ? error.message : String(error)}`)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!written.length) {
|
|
125
|
+
throw new Error(`无法写入 ahsops。${errors.join('; ')}`)
|
|
126
|
+
}
|
|
127
|
+
if (existing && written.includes(existing)) return existing
|
|
128
|
+
if (written.includes(userFile)) return userFile
|
|
129
|
+
return written[0]
|
|
130
|
+
}
|
|
131
|
+
|
|
54
132
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
55
133
|
try {
|
|
56
134
|
placeBinary()
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chuhe2640/ahsopus-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Install AHSOpsHub CLI (ahsops) and Agent Skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
7
|
+
"ahsopus-cli": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"prepublishOnly": "node npm/lib/build-binaries.js",
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bin/cli.js",
|
|
18
|
+
"npm/lib/agents.js",
|
|
18
19
|
"npm/lib/install.js",
|
|
19
20
|
"npm/lib/place-binary.js",
|
|
20
21
|
"npm/lib/platforms.js",
|
|
22
|
+
"internal/skills/agents.json",
|
|
21
23
|
"prebuilt"
|
|
22
24
|
],
|
|
23
25
|
"os": [
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|