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 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
+ }