agent-protocol-cli 1.0.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 +55 -0
- package/bin/agent.js +80 -0
- package/bin/commands/info.js +112 -0
- package/bin/commands/install.js +336 -0
- package/bin/commands/list.js +62 -0
- package/bin/commands/search.js +67 -0
- package/bin/commands/update.js +339 -0
- package/bin/commands/validate.js +102 -0
- package/data/index.json +3096 -0
- package/data/manifest.schema.json +694 -0
- package/package.json +60 -0
- package/vectorizer.js +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# agent-protocol-cli
|
|
2
|
+
|
|
3
|
+
**Discover and install AI Skills — no store needed.**
|
|
4
|
+
|
|
5
|
+
The official CLI for the [Agent Manifest Protocol](https://github.com/Polaris899/agent-protocol) — an open, decentralized registry of AI capabilities that work across any runtime.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx agent-protocol-cli search 财报分析
|
|
9
|
+
npx agent-protocol-cli install Polaris899/fsa-analyzer
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- 🔍 **Semantic search** — TF-IDF vector matching with CJK support. Search in any language.
|
|
15
|
+
- 📦 **One-command install** — `agent install user/repo` clones, validates, and registers a Skill.
|
|
16
|
+
- ✅ **Schema validation** — Every manifest is validated against the AMP Schema v1.0.
|
|
17
|
+
- 🔄 **Update management** — `agent update` checks all installed Skills for new versions.
|
|
18
|
+
- 🏷️ **Rich metadata** — Capabilities, intents, permissions, audit status, all in the search results.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Search for Skills
|
|
24
|
+
npx agent-protocol-cli search 天气
|
|
25
|
+
|
|
26
|
+
# Install a Skill
|
|
27
|
+
npx agent-protocol-cli install Polaris899/fsa-analyzer
|
|
28
|
+
|
|
29
|
+
# List installed Skills
|
|
30
|
+
npx agent-protocol-cli list
|
|
31
|
+
|
|
32
|
+
# Get detailed info
|
|
33
|
+
npx agent-protocol-cli info fsa-analyzer
|
|
34
|
+
|
|
35
|
+
# Validate a manifest
|
|
36
|
+
npx agent-protocol-cli validate ./agent.json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Registry
|
|
40
|
+
|
|
41
|
+
Browse the full registry at **[agent-protocol.dev](https://polaris899.github.io/agent-protocol/)**
|
|
42
|
+
|
|
43
|
+
## Protocol Spec
|
|
44
|
+
|
|
45
|
+
The Agent Manifest Protocol defines a standard JSON schema for describing AI Skills:
|
|
46
|
+
|
|
47
|
+
- **Interoperable** — Works with any agent runtime (OpenClaw, OpenAI GPTs, custom)
|
|
48
|
+
- **Decentralized** — Skills live on GitHub, no central store required
|
|
49
|
+
- **Safe by design** — Sandbox levels, permission declarations, verified audits
|
|
50
|
+
|
|
51
|
+
Read the spec: [`spec/manifest.md`](https://github.com/Polaris899/agent-protocol/blob/main/spec/manifest.md)
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/bin/agent.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Manifest Protocol CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* agent search <query> — Search for Skills
|
|
8
|
+
* agent install <repo> — Install a Skill from GitHub
|
|
9
|
+
* agent list — List installed Skills
|
|
10
|
+
* agent update — Update all installed Skills
|
|
11
|
+
* agent info <name|id> — Show Skill info
|
|
12
|
+
* agent validate <path> — Validate a manifest file
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Command } from 'commander';
|
|
16
|
+
import chalk from 'chalk';
|
|
17
|
+
import { searchCommand } from './commands/search.js';
|
|
18
|
+
import { installCommand } from './commands/install.js';
|
|
19
|
+
import { listCommand } from './commands/list.js';
|
|
20
|
+
import { updateCommand } from './commands/update.js';
|
|
21
|
+
import { infoCommand } from './commands/info.js';
|
|
22
|
+
import { validateCommand } from './commands/validate.js';
|
|
23
|
+
|
|
24
|
+
const program = new Command();
|
|
25
|
+
|
|
26
|
+
program
|
|
27
|
+
.name('agent')
|
|
28
|
+
.description('Agent Manifest Protocol CLI — discover and manage AI Skills')
|
|
29
|
+
.version('1.0.0');
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.command('search')
|
|
33
|
+
.description('Search for Skills matching your intent')
|
|
34
|
+
.argument('<query>', 'Search query (natural language or keywords)')
|
|
35
|
+
.option('-l, --limit <n>', 'Max results', '10')
|
|
36
|
+
.option('--json', 'Output as JSON')
|
|
37
|
+
.action(searchCommand);
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command('install')
|
|
41
|
+
.description('Install a Skill from GitHub (user/repo)')
|
|
42
|
+
.argument('<repo>', 'GitHub repository (e.g., openclaw/weather-skill)')
|
|
43
|
+
.option('--dir <path>', 'Install directory', '~/.agents')
|
|
44
|
+
.option('-v, --version <ver>', 'Specific version (tag) to install')
|
|
45
|
+
.option('-b, --branch <branch>', 'Specific branch to install')
|
|
46
|
+
.option('--no-validate', 'Skip schema validation')
|
|
47
|
+
.option('--no-symlink', 'Skip registering symlink in OpenClaw')
|
|
48
|
+
.action(installCommand);
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.command('list')
|
|
52
|
+
.description('List installed Skills')
|
|
53
|
+
.option('--json', 'Output as JSON')
|
|
54
|
+
.action(listCommand);
|
|
55
|
+
|
|
56
|
+
program
|
|
57
|
+
.command('update')
|
|
58
|
+
.description('Update installed Skills')
|
|
59
|
+
.argument('[name]', 'Skill name to update (omit to update all)')
|
|
60
|
+
.option('-a, --all', 'Update all installed Skills')
|
|
61
|
+
.option('--check', 'Check for updates without applying')
|
|
62
|
+
.option('--rollback', 'Rollback to a previous version')
|
|
63
|
+
.option('--to <ref>', 'Rollback target commit/ref')
|
|
64
|
+
.option('-f, --force', 'Force update even if already latest')
|
|
65
|
+
.action(updateCommand);
|
|
66
|
+
|
|
67
|
+
program
|
|
68
|
+
.command('info')
|
|
69
|
+
.description('Show detailed info about a Skill')
|
|
70
|
+
.argument('<name>', 'Skill name or ID')
|
|
71
|
+
.option('--json', 'Output as JSON')
|
|
72
|
+
.action(infoCommand);
|
|
73
|
+
|
|
74
|
+
program
|
|
75
|
+
.command('validate')
|
|
76
|
+
.description('Validate a manifest file against the schema')
|
|
77
|
+
.argument('<path>', 'Path to agent.json or agent.yaml')
|
|
78
|
+
.action(validateCommand);
|
|
79
|
+
|
|
80
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
const AGENTS_DIR = join(homedir(), '.agents');
|
|
7
|
+
const CONFIG_PATH = join(AGENTS_DIR, '.agent-config.json');
|
|
8
|
+
|
|
9
|
+
export async function infoCommand(name, options) {
|
|
10
|
+
const asJson = options.json || false;
|
|
11
|
+
|
|
12
|
+
// Try local installed first
|
|
13
|
+
if (existsSync(CONFIG_PATH)) {
|
|
14
|
+
const config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
15
|
+
const installed = config.installed || {};
|
|
16
|
+
|
|
17
|
+
if (installed[name]) {
|
|
18
|
+
const skill = installed[name];
|
|
19
|
+
const manifestPath = join(skill.dir, 'agent.json');
|
|
20
|
+
|
|
21
|
+
if (existsSync(manifestPath)) {
|
|
22
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
23
|
+
renderManifest(manifest, asJson);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Try searching the registry
|
|
30
|
+
const registryUrls = [
|
|
31
|
+
process.env.AGENT_REGISTRY || '',
|
|
32
|
+
'http://localhost:3456',
|
|
33
|
+
'https://api.agent-protocol.dev',
|
|
34
|
+
].filter(Boolean);
|
|
35
|
+
|
|
36
|
+
for (const baseUrl of registryUrls) {
|
|
37
|
+
try {
|
|
38
|
+
// Try by exact id first
|
|
39
|
+
const idRes = await fetch(`${baseUrl}/manifest?id=${encodeURIComponent(name)}`);
|
|
40
|
+
if (idRes.ok) {
|
|
41
|
+
const manifest = await idRes.json();
|
|
42
|
+
renderManifest(manifest, asJson);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fallback: search by name
|
|
47
|
+
const searchRes = await fetch(`${baseUrl}/search?q=${encodeURIComponent(name)}&limit=1`);
|
|
48
|
+
if (searchRes.ok) {
|
|
49
|
+
const data = await searchRes.json();
|
|
50
|
+
if (data.results && data.results.length > 0) {
|
|
51
|
+
renderManifest(data.results[0], asJson);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(chalk.yellow(` 未找到名为 "${name}" 的 Skill。`));
|
|
59
|
+
console.log(chalk.gray(' 使用 agent search <query> 搜索,或确认名称是否准确'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderManifest(manifest, asJson) {
|
|
63
|
+
if (asJson) {
|
|
64
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(chalk.blue(`📄 ${chalk.bold(manifest.name)}`));
|
|
69
|
+
console.log(` ${chalk.gray(manifest.description || '')}`);
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(` ID: ${chalk.cyan(manifest.id)}`);
|
|
72
|
+
console.log(` 版本: ${chalk.green(manifest.version)}`);
|
|
73
|
+
if (manifest.author?.name) console.log(` 作者: ${manifest.author.name}`);
|
|
74
|
+
if (manifest.license) console.log(` 许可: ${manifest.license}`);
|
|
75
|
+
if (manifest.tags?.length) console.log(` 标签: ${chalk.gray(manifest.tags.join(', '))}`);
|
|
76
|
+
console.log();
|
|
77
|
+
|
|
78
|
+
// Capabilities
|
|
79
|
+
const caps = manifest.capabilities || [];
|
|
80
|
+
console.log(chalk.blue(`🎯 能力 (${caps.length}):`));
|
|
81
|
+
for (const cap of caps) {
|
|
82
|
+
console.log(` ${chalk.bold(cap.name)} (${cap.id})`);
|
|
83
|
+
console.log(` ${chalk.gray(cap.description)}`);
|
|
84
|
+
if (cap.intents?.length) {
|
|
85
|
+
const samples = cap.intents.slice(0, 3);
|
|
86
|
+
console.log(` ${chalk.gray('意图示例: ' + samples.join(' | '))}`);
|
|
87
|
+
}
|
|
88
|
+
console.log(` ${cap.latency ? chalk.gray(`延迟: ${cap.latency} | `) : ''}${chalk.gray(`安全: ${cap.security_level || 'low'} | 费用: ¥${cap.cost_per_call || 0}/次`)}`);
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Runtime
|
|
93
|
+
if (manifest.runtime) {
|
|
94
|
+
console.log(chalk.blue('⚙ 运行时'));
|
|
95
|
+
console.log(` 引擎: ${manifest.runtime.engine}`);
|
|
96
|
+
console.log(` 类型: ${manifest.runtime.type || 'skill'}`);
|
|
97
|
+
if (manifest.runtime.config_url) console.log(` 配置: ${chalk.gray(manifest.runtime.config_url)}`);
|
|
98
|
+
console.log();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Trust
|
|
102
|
+
if (manifest.trust) {
|
|
103
|
+
console.log(chalk.blue('🔒 安全与信任'));
|
|
104
|
+
const perms = manifest.trust.permissions || [];
|
|
105
|
+
console.log(` 权限: ${perms.length > 0 ? chalk.yellow(perms.join(', ')) : chalk.gray('无')}`);
|
|
106
|
+
console.log(` 沙箱: ${manifest.trust.sandbox_level || 'basic'}`);
|
|
107
|
+
if (manifest.trust.verified_by?.length) {
|
|
108
|
+
console.log(` ${chalk.green('✓ 已通过审计: ' + manifest.trust.verified_by.join(', '))}`);
|
|
109
|
+
}
|
|
110
|
+
console.log();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { execSync, spawnSync } from 'child_process';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, symlinkSync, rmSync, copyFileSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join, basename, dirname } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { createInterface } from 'readline';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const AGENTS_DIR = join(homedir(), '.agents');
|
|
11
|
+
const CONFIG_PATH = join(AGENTS_DIR, '.agent-config.json');
|
|
12
|
+
const SCHEMA_PATH = join(__dirname, '..', 'data', 'manifest.schema.json');
|
|
13
|
+
|
|
14
|
+
export async function installCommand(repo, options) {
|
|
15
|
+
const installDir = options.dir || AGENTS_DIR;
|
|
16
|
+
const version = options.version || options.tag || null;
|
|
17
|
+
const branch = options.branch || null;
|
|
18
|
+
const noValidate = options['no-validate'] || false;
|
|
19
|
+
const noSymlink = options['no-symlink'] || false;
|
|
20
|
+
|
|
21
|
+
// Parse user/repo[/path]
|
|
22
|
+
const parts = repo.split('/');
|
|
23
|
+
if (parts.length < 2) {
|
|
24
|
+
console.error(chalk.red('❌ 无效格式。使用: agent install user/repo'));
|
|
25
|
+
console.error(chalk.gray(' 可选: agent install user/repo#v1.0.0'));
|
|
26
|
+
console.error(chalk.gray(' agent install user/repo@main'));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const [user, repoName, ...subPath] = parts;
|
|
31
|
+
const fullName = `${user}/${repoName}`;
|
|
32
|
+
const targetDir = join(installDir, repoName);
|
|
33
|
+
|
|
34
|
+
console.log(chalk.blue(`📦 安装 ${chalk.bold(fullName)}`));
|
|
35
|
+
|
|
36
|
+
// ── Step 1: Pre-flight checks ──
|
|
37
|
+
if (existsSync(targetDir)) {
|
|
38
|
+
const answer = await ask(chalk.yellow(` 目录已存在: ${targetDir}\n 覆盖?[y/N] `));
|
|
39
|
+
if (answer.toLowerCase() !== 'y') {
|
|
40
|
+
console.log(chalk.yellow(' 已取消'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!existsSync(installDir)) {
|
|
47
|
+
mkdirSync(installDir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Step 2: Git clone ──
|
|
51
|
+
const gitUrl = `https://github.com/${fullName}.git`;
|
|
52
|
+
console.log(chalk.gray(` git clone ${gitUrl}`));
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const cloneArgs = ['clone', '--depth', '1'];
|
|
56
|
+
if (branch) cloneArgs.push('--branch', branch);
|
|
57
|
+
if (version) cloneArgs.push('--branch', `v${version.replace(/^v/, '')}`);
|
|
58
|
+
cloneArgs.push(gitUrl, targetDir);
|
|
59
|
+
|
|
60
|
+
const result = spawnSync('git', cloneArgs, {
|
|
61
|
+
stdio: 'pipe',
|
|
62
|
+
timeout: 60000,
|
|
63
|
+
encoding: 'utf-8',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (result.status !== 0) {
|
|
67
|
+
// Try shallow clone with specific ref as fallback
|
|
68
|
+
const fallback = spawnSync('git', [
|
|
69
|
+
'clone', '--depth', '1', '--single-branch',
|
|
70
|
+
...(version || branch ? ['--branch', (version || branch).replace(/^v/, '')] : []),
|
|
71
|
+
gitUrl, targetDir
|
|
72
|
+
], { stdio: 'pipe', timeout: 60000, encoding: 'utf-8' });
|
|
73
|
+
|
|
74
|
+
if (fallback.status !== 0) {
|
|
75
|
+
throw new Error(fallback.stderr?.split('\n')[0] || 'Clone failed');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(chalk.red(`❌ 克隆失败: ${err.message}`));
|
|
80
|
+
cleanupFailedInstall(targetDir);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Step 3: Parse manifest ──
|
|
85
|
+
const manifestPath = findManifest(targetDir);
|
|
86
|
+
let manifest = null;
|
|
87
|
+
let installError = null;
|
|
88
|
+
|
|
89
|
+
if (!manifestPath) {
|
|
90
|
+
installError = `未找到 agent.json/agent.yaml 文件`;
|
|
91
|
+
console.log(chalk.yellow(` ⚠ ${installError}`));
|
|
92
|
+
} else {
|
|
93
|
+
try {
|
|
94
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
95
|
+
console.log(chalk.green(` ✓ 找到 Manifest: ${basename(manifestPath)}`));
|
|
96
|
+
|
|
97
|
+
// ── Step 4: Validate against schema ──
|
|
98
|
+
if (!noValidate) {
|
|
99
|
+
const valid = await validateManifest(manifestPath, manifest);
|
|
100
|
+
if (!valid) {
|
|
101
|
+
console.log(chalk.yellow(' ⚠ Manifest 校验有警告(继续安装)'));
|
|
102
|
+
} else {
|
|
103
|
+
console.log(chalk.green(' ✓ Schema 校验通过'));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Step 5: Check dependencies ──
|
|
108
|
+
if (manifest.dependencies?.skills?.length) {
|
|
109
|
+
console.log(chalk.blue(' 🔗 检查依赖...'));
|
|
110
|
+
const depsOk = await resolveDependencies(manifest.dependencies.skills, installDir);
|
|
111
|
+
if (!depsOk) {
|
|
112
|
+
console.log(chalk.yellow(' ⚠ 部分依赖未满足(继续安装)'));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Step 6: Run lifecycle hooks ──
|
|
117
|
+
if (manifest.lifecycle?.on_install) {
|
|
118
|
+
console.log(chalk.blue(' 🎣 执行安装钩子...'));
|
|
119
|
+
await runLifecycleHook(targetDir, manifest.lifecycle.on_install, 'on_install');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Step 7: Store version info ──
|
|
123
|
+
manifest._installed_version = manifest.version;
|
|
124
|
+
manifest._installed_commit = getCommitHash(targetDir);
|
|
125
|
+
|
|
126
|
+
} catch (err) {
|
|
127
|
+
installError = err.message;
|
|
128
|
+
console.log(chalk.yellow(` ⚠ Manifest 解析失败: ${err.message}`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Step 8: Register via symlink (for OpenClaw) ──
|
|
133
|
+
if (!noSymlink && manifest) {
|
|
134
|
+
try {
|
|
135
|
+
const skillsDir = getSkillsDir();
|
|
136
|
+
if (skillsDir) {
|
|
137
|
+
const linkPath = join(skillsDir, repoName);
|
|
138
|
+
if (!existsSync(linkPath)) {
|
|
139
|
+
symlinkSync(targetDir, linkPath, 'dir');
|
|
140
|
+
console.log(chalk.green(` ✓ 已注册到 OpenClaw: ${linkPath}`));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(chalk.gray(` - OpenClaw 目录中已存在 ${repoName}(跳过)`));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.log(chalk.yellow(` ⚠ 注册 symlink 失败: ${err.message}`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── Step 9: Register in local config ──
|
|
151
|
+
registerSkill(repoName, fullName, targetDir, manifest, version || branch || 'main', installError);
|
|
152
|
+
|
|
153
|
+
// ── Summary ──
|
|
154
|
+
console.log('');
|
|
155
|
+
if (installError && !manifest) {
|
|
156
|
+
console.log(chalk.yellow(`⚠ ${fullName} 已下载到: ${targetDir}`));
|
|
157
|
+
console.log(chalk.gray(' 但未找到有效的 agent.json,请手动配置'));
|
|
158
|
+
} else {
|
|
159
|
+
console.log(chalk.green(`✅ ${chalk.bold(repoName)} v${manifest?.version || '?'} 安装成功!`));
|
|
160
|
+
console.log(chalk.gray(` 路径: ${targetDir}`));
|
|
161
|
+
console.log(chalk.gray(` 命令: agent info ${repoName}`));
|
|
162
|
+
console.log(chalk.gray(` agent update`));
|
|
163
|
+
if (manifest?.lifecycle?.health_check) {
|
|
164
|
+
console.log(chalk.gray(` 健康检查已注册(每 ${manifest.lifecycle.health_check.interval_seconds}s)`));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── Helpers ───
|
|
170
|
+
|
|
171
|
+
function findManifest(dir) {
|
|
172
|
+
// Look in root, then .agent/
|
|
173
|
+
const patterns = [
|
|
174
|
+
join(dir, 'agent.json'),
|
|
175
|
+
join(dir, 'agent.yaml'),
|
|
176
|
+
join(dir, 'agent.yml'),
|
|
177
|
+
join(dir, '.agent', 'manifest.json'),
|
|
178
|
+
join(dir, '.agent', 'manifest.yaml'),
|
|
179
|
+
];
|
|
180
|
+
for (const p of patterns) {
|
|
181
|
+
if (existsSync(p)) return p;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function validateManifest(manifestPath, manifest) {
|
|
187
|
+
// Try Ajv validation
|
|
188
|
+
try {
|
|
189
|
+
const { default: Ajv } = await import('ajv');
|
|
190
|
+
const addFormats = (await import('ajv-formats')).default;
|
|
191
|
+
const ajv = new Ajv({ strict: false });
|
|
192
|
+
addFormats(ajv);
|
|
193
|
+
|
|
194
|
+
if (existsSync(SCHEMA_PATH)) {
|
|
195
|
+
const schema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf-8'));
|
|
196
|
+
const validate = ajv.compile(schema);
|
|
197
|
+
const valid = validate(manifest);
|
|
198
|
+
if (!valid) {
|
|
199
|
+
console.log(chalk.yellow(' 校验问题:'));
|
|
200
|
+
validate.errors.slice(0, 3).forEach(e => {
|
|
201
|
+
console.log(chalk.gray(` ${e.instancePath} ${e.message}`));
|
|
202
|
+
});
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
} catch {}
|
|
208
|
+
return true; // Skip if schema not available
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function resolveDependencies(deps, installDir) {
|
|
212
|
+
let allOk = true;
|
|
213
|
+
const config = loadConfig();
|
|
214
|
+
|
|
215
|
+
for (const dep of deps) {
|
|
216
|
+
const existing = Object.entries(config.installed || {})
|
|
217
|
+
.find(([_, s]) => s.id === dep.id);
|
|
218
|
+
|
|
219
|
+
if (existing) {
|
|
220
|
+
console.log(chalk.gray(` ✓ ${dep.id}(已安装 v${existing[1].version})`));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (dep.optional) {
|
|
225
|
+
console.log(chalk.gray(` - ${dep.id}(可选,跳过)`));
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log(chalk.yellow(` ⚠ ${dep.id}(未安装)`));
|
|
230
|
+
allOk = false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return allOk;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function runLifecycleHook(targetDir, hook, hookName) {
|
|
237
|
+
if (!hook) return;
|
|
238
|
+
|
|
239
|
+
const { default: chalk } = await import('chalk');
|
|
240
|
+
|
|
241
|
+
if (hook.requires_approval) {
|
|
242
|
+
const answer = await ask(chalk.yellow(` 安装需要执行: ${hook.description || hookName}\n 确认执行?[y/N] `));
|
|
243
|
+
if (answer.toLowerCase() !== 'y') {
|
|
244
|
+
console.log(chalk.gray(` 跳过 ${hookName} 钩子`));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
if (hook.command) {
|
|
251
|
+
const result = spawnSync(hook.command, {
|
|
252
|
+
cwd: targetDir,
|
|
253
|
+
shell: true,
|
|
254
|
+
timeout: hook.timeout_ms || 30000,
|
|
255
|
+
stdio: 'pipe',
|
|
256
|
+
encoding: 'utf-8',
|
|
257
|
+
});
|
|
258
|
+
if (result.status !== 0) {
|
|
259
|
+
console.log(chalk.yellow(` ⚠ ${hookName} 钩子返回非零: ${result.stderr?.slice(0, 200)}`));
|
|
260
|
+
}
|
|
261
|
+
} else if (hook.url) {
|
|
262
|
+
// Webhook / HTTP hook — note only
|
|
263
|
+
console.log(chalk.gray(` ${hookName}: webhook 配置为 ${hook.url}`));
|
|
264
|
+
}
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.log(chalk.yellow(` ⚠ ${hookName} 执行失败: ${err.message}`));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function getCommitHash(dir) {
|
|
271
|
+
try {
|
|
272
|
+
const result = spawnSync('git', ['rev-parse', '--short', 'HEAD'], {
|
|
273
|
+
cwd: dir, stdio: 'pipe', encoding: 'utf-8', timeout: 5000,
|
|
274
|
+
});
|
|
275
|
+
return result.stdout?.trim() || 'unknown';
|
|
276
|
+
} catch {
|
|
277
|
+
return 'unknown';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function getSkillsDir() {
|
|
282
|
+
// Look for OpenClaw skills directory in standard locations
|
|
283
|
+
const candidates = [
|
|
284
|
+
join(homedir(), '.openclaw', 'workspace', 'skills'),
|
|
285
|
+
join(homedir(), '.agents'),
|
|
286
|
+
join(homedir(), 'skills'),
|
|
287
|
+
];
|
|
288
|
+
for (const dir of candidates) {
|
|
289
|
+
if (existsSync(dir)) return dir;
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function registerSkill(name, fullName, dir, manifest, ref, error) {
|
|
295
|
+
const config = loadConfig();
|
|
296
|
+
if (!config.installed) config.installed = {};
|
|
297
|
+
|
|
298
|
+
config.installed[name] = {
|
|
299
|
+
id: manifest?.id || `${fullName.replace('/', '.')}`,
|
|
300
|
+
name: manifest?.name || name,
|
|
301
|
+
repo: fullName,
|
|
302
|
+
dir,
|
|
303
|
+
ref,
|
|
304
|
+
version: manifest?.version || '0.1.0',
|
|
305
|
+
capabilities: (manifest?.capabilities || []).map(c => c.id),
|
|
306
|
+
tag_count: (manifest?.tags || []).length,
|
|
307
|
+
installed_at: new Date().toISOString(),
|
|
308
|
+
updated_at: new Date().toISOString(),
|
|
309
|
+
install_error: error || null,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function loadConfig() {
|
|
316
|
+
if (!existsSync(CONFIG_PATH)) return {};
|
|
317
|
+
try {
|
|
318
|
+
return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
319
|
+
} catch {
|
|
320
|
+
return {};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function cleanupFailedInstall(dir) {
|
|
325
|
+
try {
|
|
326
|
+
if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
|
|
327
|
+
} catch {}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function ask(query) {
|
|
331
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
332
|
+
return new Promise(resolve => rl.question(query, answer => {
|
|
333
|
+
rl.close();
|
|
334
|
+
resolve(answer);
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
const AGENTS_DIR = join(homedir(), '.agents');
|
|
7
|
+
const CONFIG_PATH = join(AGENTS_DIR, '.agent-config.json');
|
|
8
|
+
|
|
9
|
+
export async function listCommand(options) {
|
|
10
|
+
const asJson = options.json || false;
|
|
11
|
+
|
|
12
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
13
|
+
console.log(chalk.yellow(' 尚未安装任何 Skill。'));
|
|
14
|
+
console.log(chalk.gray(' 使用 agent search <query> 查找 Skill,然后用 agent install <repo> 安装'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let config;
|
|
19
|
+
try {
|
|
20
|
+
config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
21
|
+
} catch {
|
|
22
|
+
console.log(chalk.red('❌ 配置文件损坏'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const installed = config.installed || {};
|
|
27
|
+
const names = Object.keys(installed);
|
|
28
|
+
|
|
29
|
+
if (names.length === 0) {
|
|
30
|
+
console.log(chalk.yellow(' 尚未安装任何 Skill。'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (asJson) {
|
|
35
|
+
console.log(JSON.stringify(installed, null, 2));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(chalk.blue(`📋 已安装的 Skill (${names.length}):`));
|
|
40
|
+
console.log();
|
|
41
|
+
|
|
42
|
+
for (const [i, name] of names.entries()) {
|
|
43
|
+
const skill = installed[name];
|
|
44
|
+
const manifestPath = join(skill.dir, 'agent.json');
|
|
45
|
+
let displayName = name;
|
|
46
|
+
let desc = '';
|
|
47
|
+
|
|
48
|
+
if (existsSync(manifestPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
51
|
+
displayName = manifest.name || name;
|
|
52
|
+
const caps = manifest.capabilities || [];
|
|
53
|
+
desc = `${caps.length} 个能力`;
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const date = new Date(skill.installed_at).toLocaleDateString('zh-CN');
|
|
58
|
+
console.log(` ${chalk.cyan(String(i + 1).padStart(2, ' '))}. ${chalk.bold(displayName)}`);
|
|
59
|
+
console.log(` ${chalk.gray(`仓库: ${skill.repo} | 安装: ${date} | ${desc}`)}`);
|
|
60
|
+
console.log();
|
|
61
|
+
}
|
|
62
|
+
}
|