agent-resource-management 2.1.0 → 2.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/README.md +62 -5
- package/dist/main.js +326 -19
- package/dist/test.md +3 -0
- package/dist/test2.md +3 -0
- package/package.json +1 -1
- package/src/cmd/agent.ts +163 -2
- package/src/cmd/auth.ts +50 -0
- package/src/cmd/knowledge.ts +3 -0
- package/src/lib/client.ts +12 -1
- package/src/lib/validate.ts +118 -1
- package/src/main.ts +37 -12
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ Agent Resource Management CLI 是一个用于管理 Skills、Knowledges 和 Agen
|
|
|
13
13
|
- 上传/下载/删除
|
|
14
14
|
- 本地验证
|
|
15
15
|
- 多服务端支持
|
|
16
|
+
- **JSON 输出模式** - 便于 AI Agent 集成
|
|
16
17
|
|
|
17
18
|
## 安装
|
|
18
19
|
|
|
@@ -96,10 +97,10 @@ arm agent download workspace-assistant ./agents
|
|
|
96
97
|
### 6. 上传
|
|
97
98
|
|
|
98
99
|
```bash
|
|
99
|
-
# 上传本地 Skill
|
|
100
|
+
# 上传本地 Skill 目录或 ZIP 文件
|
|
100
101
|
arm skill upload ./my-skills/pdf-tool
|
|
101
102
|
|
|
102
|
-
# 上传本地 Knowledge
|
|
103
|
+
# 上传本地 Knowledge 文件或目录
|
|
103
104
|
arm knowledge upload ./my-knowledges/api-doc
|
|
104
105
|
```
|
|
105
106
|
|
|
@@ -113,6 +114,39 @@ arm skill validate ./my-skills/pdf-tool
|
|
|
113
114
|
arm skill validate ./my-skills/pdf-tool.zip
|
|
114
115
|
```
|
|
115
116
|
|
|
117
|
+
### 8. JSON 输出模式
|
|
118
|
+
|
|
119
|
+
CLI 默认输出 JSON 格式,便于 AI Agent 解析和集成:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# 查看当前输出模式
|
|
123
|
+
arm output
|
|
124
|
+
|
|
125
|
+
# 切换为文本模式(人类可读)
|
|
126
|
+
arm output text
|
|
127
|
+
|
|
128
|
+
# 切换为 JSON 模式(默认)
|
|
129
|
+
arm output json
|
|
130
|
+
|
|
131
|
+
# 所有命令都支持 --json 参数强制输出 JSON
|
|
132
|
+
arm agent create my-agent --json
|
|
133
|
+
arm skill ls --json
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
JSON 输出格式:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"success": true,
|
|
141
|
+
"data": { ... }
|
|
142
|
+
}
|
|
143
|
+
// 或
|
|
144
|
+
{
|
|
145
|
+
"success": false,
|
|
146
|
+
"error": { "code": "ERROR_CODE", "message": "错误信息" }
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
116
150
|
## 命令参考
|
|
117
151
|
|
|
118
152
|
### 认证命令
|
|
@@ -131,7 +165,7 @@ arm skill validate ./my-skills/pdf-tool.zip
|
|
|
131
165
|
| `arm skill search <keyword>` | 搜索 Skills |
|
|
132
166
|
| `arm skill info <name>` | 查看 Skill 详情 |
|
|
133
167
|
| `arm skill download <name> [dir]` | 下载 Skill 到指定目录 |
|
|
134
|
-
| `arm skill upload <path>` | 上传本地 Skill
|
|
168
|
+
| `arm skill upload <path>` | 上传本地 Skill 目录或 ZIP 文件 |
|
|
135
169
|
| `arm skill my` | 查看我发布的 Skills |
|
|
136
170
|
| `arm skill delete <name>` | 删除我发布的 Skill |
|
|
137
171
|
| `arm skill validate <path>` | 验证 Skill 格式(支持目录和 ZIP) |
|
|
@@ -144,7 +178,7 @@ arm skill validate ./my-skills/pdf-tool.zip
|
|
|
144
178
|
| `arm knowledge search <keyword>` | 搜索 Knowledges |
|
|
145
179
|
| `arm knowledge info <name>` | 查看 Knowledge 详情 |
|
|
146
180
|
| `arm knowledge download <name> [dir]` | 下载 Knowledge 到指定目录 |
|
|
147
|
-
| `arm knowledge upload <path>` | 上传本地 Knowledge
|
|
181
|
+
| `arm knowledge upload <path>` | 上传本地 Knowledge 文件或目录 |
|
|
148
182
|
| `arm knowledge my` | 查看我发布的 Knowledges |
|
|
149
183
|
| `arm knowledge delete <name>` | 删除我发布的 Knowledge |
|
|
150
184
|
|
|
@@ -164,6 +198,26 @@ arm skill validate ./my-skills/pdf-tool.zip
|
|
|
164
198
|
| `arm server` | 显示当前服务端 |
|
|
165
199
|
| `arm server set <url>` | 设置默认服务端 |
|
|
166
200
|
|
|
201
|
+
### 输出模式命令
|
|
202
|
+
|
|
203
|
+
| 命令 | 说明 |
|
|
204
|
+
|------|------|
|
|
205
|
+
| `arm output` | 查看当前输出模式 |
|
|
206
|
+
| `arm output json` | 设置为 JSON 模式(默认) |
|
|
207
|
+
| `arm output text` | 设置为文本模式 |
|
|
208
|
+
|
|
209
|
+
### 输出格式说明
|
|
210
|
+
|
|
211
|
+
所有命令都支持 `--json` 或 `-j` 参数强制输出 JSON 格式。当默认模式为 `json` 时,所有命令直接返回 JSON。
|
|
212
|
+
|
|
213
|
+
**Agent 命令(创建/更新/删除/绑定/解绑)** 始终支持 `--json`:
|
|
214
|
+
```bash
|
|
215
|
+
arm agent create my-agent --json
|
|
216
|
+
arm agent update <id> --name=new-name --json
|
|
217
|
+
arm agent delete <id> --json
|
|
218
|
+
arm agent bind <id> --skill=<id> --json
|
|
219
|
+
```
|
|
220
|
+
|
|
167
221
|
## Skill 格式规范
|
|
168
222
|
|
|
169
223
|
上传的 Skill 目录必须包含 `SKILL.md` 文件,且 frontmatter 必须符合以下格式:
|
|
@@ -202,10 +256,13 @@ allowed-tools: tool1 tool2 # 可选,空格分隔
|
|
|
202
256
|
"id": "user-id",
|
|
203
257
|
"name": "username",
|
|
204
258
|
"email": "user@example.com"
|
|
205
|
-
}
|
|
259
|
+
},
|
|
260
|
+
"outputMode": "json"
|
|
206
261
|
}
|
|
207
262
|
```
|
|
208
263
|
|
|
264
|
+
其中 `outputMode` 可选值为 `json`(默认)或 `text`。
|
|
265
|
+
|
|
209
266
|
## 开发
|
|
210
267
|
|
|
211
268
|
```bash
|
package/dist/main.js
CHANGED
|
@@ -40,6 +40,16 @@ class ApiClient {
|
|
|
40
40
|
}
|
|
41
41
|
return res.data;
|
|
42
42
|
}
|
|
43
|
+
async register(email, password, name) {
|
|
44
|
+
const res = await this.request("/auth/register", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
body: JSON.stringify({ email, password, name })
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
throw new Error(res.msg);
|
|
50
|
+
}
|
|
51
|
+
return res.data;
|
|
52
|
+
}
|
|
43
53
|
async me() {
|
|
44
54
|
const res = await this.request("/auth/me");
|
|
45
55
|
if (!res.ok) {
|
|
@@ -462,6 +472,47 @@ function formatKnowledgeDetail(knowledge) {
|
|
|
462
472
|
}
|
|
463
473
|
|
|
464
474
|
// src/cmd/auth.ts
|
|
475
|
+
async function register(name, email, password) {
|
|
476
|
+
const { readline } = await import("readline");
|
|
477
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
478
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
479
|
+
let finalEmail = email;
|
|
480
|
+
let finalPassword = password;
|
|
481
|
+
let finalName = name;
|
|
482
|
+
if (!finalEmail) {
|
|
483
|
+
finalEmail = await ask("请输入邮箱: ");
|
|
484
|
+
}
|
|
485
|
+
if (!finalPassword) {
|
|
486
|
+
finalPassword = await ask("请输入密码: ");
|
|
487
|
+
}
|
|
488
|
+
if (!finalName) {
|
|
489
|
+
finalName = await ask("请输入用户名: ");
|
|
490
|
+
}
|
|
491
|
+
rl.close();
|
|
492
|
+
if (!finalEmail || !finalPassword || !finalName) {
|
|
493
|
+
error("邮箱、密码和用户名都不能为空");
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
if (finalPassword.length < 8) {
|
|
497
|
+
error("密码至少需要 8 个字符");
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
const config = loadConfig();
|
|
501
|
+
const serverUrl = config?.serverUrl || "http://localhost:3000";
|
|
502
|
+
const client = new ApiClient(serverUrl);
|
|
503
|
+
try {
|
|
504
|
+
const result = await client.register(finalEmail, finalPassword, finalName);
|
|
505
|
+
saveConfig({
|
|
506
|
+
serverUrl,
|
|
507
|
+
token: result.token,
|
|
508
|
+
user: result.user
|
|
509
|
+
});
|
|
510
|
+
success(`注册成功! 欢迎, ${result.user.name}`);
|
|
511
|
+
} catch (err) {
|
|
512
|
+
error(`注册失败: ${err instanceof Error ? err.message : "未知错误"}`);
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
465
516
|
async function login(serverUrl, apiKey) {
|
|
466
517
|
try {
|
|
467
518
|
const client = new ApiClient(serverUrl);
|
|
@@ -526,7 +577,7 @@ function setOutputMode(mode) {
|
|
|
526
577
|
}
|
|
527
578
|
|
|
528
579
|
// src/lib/validate.ts
|
|
529
|
-
import { readFileSync as readFileSync2, existsSync as existsSync2, statSync } from "fs";
|
|
580
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
530
581
|
import { execSync } from "child_process";
|
|
531
582
|
import { join as join2, extname } from "path";
|
|
532
583
|
import { mkdtempSync, rmSync } from "fs";
|
|
@@ -546,7 +597,7 @@ function validateZip(filePath) {
|
|
|
546
597
|
result.errors.push("文件必须是 ZIP 格式");
|
|
547
598
|
return result;
|
|
548
599
|
}
|
|
549
|
-
const stats =
|
|
600
|
+
const stats = statSync2(filePath);
|
|
550
601
|
if (stats.size === 0) {
|
|
551
602
|
result.valid = false;
|
|
552
603
|
result.errors.push("ZIP 文件为空");
|
|
@@ -696,9 +747,95 @@ function validateSkillDir(dirPath) {
|
|
|
696
747
|
}
|
|
697
748
|
return result;
|
|
698
749
|
}
|
|
750
|
+
function validateAgentDir(dirPath) {
|
|
751
|
+
const result = {
|
|
752
|
+
valid: true,
|
|
753
|
+
errors: [],
|
|
754
|
+
warnings: []
|
|
755
|
+
};
|
|
756
|
+
if (!existsSync2(dirPath)) {
|
|
757
|
+
result.valid = false;
|
|
758
|
+
result.errors.push(`目录不存在: ${dirPath}`);
|
|
759
|
+
return result;
|
|
760
|
+
}
|
|
761
|
+
const agentMdPath = join2(dirPath, "AGENT.md");
|
|
762
|
+
if (!existsSync2(agentMdPath)) {
|
|
763
|
+
result.valid = false;
|
|
764
|
+
result.errors.push("目录内缺少 AGENT.md 文件");
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
try {
|
|
768
|
+
const agentMdContent = readFileSync2(agentMdPath, "utf-8");
|
|
769
|
+
const frontmatterMatch = agentMdContent.match(/^---\n([\s\S]*?)\n---/);
|
|
770
|
+
if (frontmatterMatch) {
|
|
771
|
+
const frontmatter = frontmatterMatch[1];
|
|
772
|
+
result.metadata = {};
|
|
773
|
+
const nameMatch = frontmatter.match(/name:\s*(.+)/);
|
|
774
|
+
const versionMatch = frontmatter.match(/version:\s*(.+)/);
|
|
775
|
+
const descMatch = frontmatter.match(/description:\s*(.+)/);
|
|
776
|
+
const skillsMatch = frontmatter.match(/skills:\s*([\s\S]*?)(?=\n\w|---)/);
|
|
777
|
+
const knowledgesMatch = frontmatter.match(/knowledges:\s*([\s\S]*?)(?=\n\w|---)/);
|
|
778
|
+
if (nameMatch) {
|
|
779
|
+
result.metadata.name = nameMatch[1].trim();
|
|
780
|
+
} else {
|
|
781
|
+
result.valid = false;
|
|
782
|
+
result.errors.push("缺少 name 字段");
|
|
783
|
+
}
|
|
784
|
+
if (versionMatch) {
|
|
785
|
+
result.metadata.version = versionMatch[1].trim();
|
|
786
|
+
}
|
|
787
|
+
if (descMatch) {
|
|
788
|
+
result.metadata.description = descMatch[1].trim();
|
|
789
|
+
}
|
|
790
|
+
if (skillsMatch) {
|
|
791
|
+
const skillsLines = skillsMatch[1].split(`
|
|
792
|
+
`).filter((l) => l.trim().startsWith("-"));
|
|
793
|
+
result.metadata.skills = skillsLines.map((l) => l.replace(/^\s*-\s*/, "").trim());
|
|
794
|
+
}
|
|
795
|
+
if (knowledgesMatch) {
|
|
796
|
+
const knowledgesLines = knowledgesMatch[1].split(`
|
|
797
|
+
`).filter((l) => l.trim().startsWith("-"));
|
|
798
|
+
result.metadata.knowledges = knowledgesLines.map((l) => l.replace(/^\s*-\s*/, "").trim());
|
|
799
|
+
}
|
|
800
|
+
const contentAfterFrontmatter = agentMdContent.replace(/^---[\s\S]*?---\n/, "");
|
|
801
|
+
const promptMatch = contentAfterFrontmatter.match(/#\s*System\s*Prompt\n([\s\S]*?)$/);
|
|
802
|
+
if (promptMatch) {
|
|
803
|
+
result.metadata.prompt = promptMatch[1].trim();
|
|
804
|
+
} else if (contentAfterFrontmatter.trim()) {
|
|
805
|
+
result.metadata.prompt = contentAfterFrontmatter.trim();
|
|
806
|
+
}
|
|
807
|
+
} else {
|
|
808
|
+
result.valid = false;
|
|
809
|
+
result.errors.push("AGENT.md 缺少 frontmatter (--- 包裹的 YAML)");
|
|
810
|
+
}
|
|
811
|
+
const skillsDir = join2(dirPath, "skills");
|
|
812
|
+
if (existsSync2(skillsDir) && statSync2(skillsDir).isDirectory()) {
|
|
813
|
+
const skillDirs = execSync(`ls -1 "${skillsDir}"`, { encoding: "utf-8" }).split(`
|
|
814
|
+
`).filter((l) => l.trim() && existsSync2(join2(skillsDir, l)) && statSync2(join2(skillsDir, l)).isDirectory());
|
|
815
|
+
for (const skillDir of skillDirs) {
|
|
816
|
+
const skillMdPath = join2(skillsDir, skillDir, "SKILL.md");
|
|
817
|
+
if (!existsSync2(skillMdPath)) {
|
|
818
|
+
result.warnings.push(`skills/${skillDir} 目录内缺少 SKILL.md 文件`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const knowledgesDir = join2(dirPath, "knowledges");
|
|
823
|
+
if (existsSync2(knowledgesDir) && statSync2(knowledgesDir).isDirectory()) {
|
|
824
|
+
const mdFiles = execSync(`ls -1 "${knowledgesDir}"`, { encoding: "utf-8" }).split(`
|
|
825
|
+
`).filter((l) => l.trim().endsWith(".md"));
|
|
826
|
+
if (mdFiles.length === 0) {
|
|
827
|
+
result.warnings.push("knowledges/ 目录内没有 .md 文件");
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
} catch (err) {
|
|
831
|
+
result.valid = false;
|
|
832
|
+
result.errors.push(`AGENT.md 读取失败: ${err instanceof Error ? err.message : "未知错误"}`);
|
|
833
|
+
}
|
|
834
|
+
return result;
|
|
835
|
+
}
|
|
699
836
|
|
|
700
837
|
// src/cmd/skill.ts
|
|
701
|
-
import { writeFileSync as writeFileSync2, existsSync as existsSync3, statSync as
|
|
838
|
+
import { writeFileSync as writeFileSync2, existsSync as existsSync3, statSync as statSync3 } from "fs";
|
|
702
839
|
import { join as join3, basename, dirname } from "path";
|
|
703
840
|
import { execSync as execSync2 } from "child_process";
|
|
704
841
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
|
|
@@ -959,7 +1096,7 @@ async function deleteSkill(name) {
|
|
|
959
1096
|
}
|
|
960
1097
|
}
|
|
961
1098
|
async function validateSkill(filePath) {
|
|
962
|
-
const isDir = existsSync3(filePath) &&
|
|
1099
|
+
const isDir = existsSync3(filePath) && statSync3(filePath).isDirectory();
|
|
963
1100
|
const result = isDir ? validateSkillDir(filePath) : validateZip(filePath);
|
|
964
1101
|
if (shouldOutputJson()) {
|
|
965
1102
|
outputJson({
|
|
@@ -1411,10 +1548,153 @@ async function downloadAgent(name, outputDir) {
|
|
|
1411
1548
|
process.exit(1);
|
|
1412
1549
|
}
|
|
1413
1550
|
}
|
|
1551
|
+
async function createAgentFromFolder(folderPath) {
|
|
1552
|
+
const config = loadConfig();
|
|
1553
|
+
if (!config?.token) {
|
|
1554
|
+
if (shouldOutputJson()) {
|
|
1555
|
+
outputJson({ success: false, error: { code: "NOT_LOGGED_IN", message: "未登录,请先运行 arm login" } });
|
|
1556
|
+
process.exit(1);
|
|
1557
|
+
}
|
|
1558
|
+
error("未登录,请先运行 arm login");
|
|
1559
|
+
process.exit(1);
|
|
1560
|
+
}
|
|
1561
|
+
if (!existsSync4(folderPath)) {
|
|
1562
|
+
if (shouldOutputJson()) {
|
|
1563
|
+
outputJson({ success: false, error: { code: "FILE_NOT_FOUND", message: `目录不存在: ${folderPath}` } });
|
|
1564
|
+
process.exit(1);
|
|
1565
|
+
}
|
|
1566
|
+
error(`目录不存在: ${folderPath}`);
|
|
1567
|
+
process.exit(1);
|
|
1568
|
+
}
|
|
1569
|
+
const validation = validateAgentDir(folderPath);
|
|
1570
|
+
if (!validation.valid) {
|
|
1571
|
+
if (shouldOutputJson()) {
|
|
1572
|
+
outputJson({ success: false, error: { code: "VALIDATION_FAILED", message: validation.errors.join(", ") } });
|
|
1573
|
+
process.exit(1);
|
|
1574
|
+
}
|
|
1575
|
+
error(`验证失败: ${validation.errors.join(", ")}`);
|
|
1576
|
+
process.exit(1);
|
|
1577
|
+
}
|
|
1578
|
+
if (shouldOutputJson()) {
|
|
1579
|
+
info("正在解析 Agent 文件夹...");
|
|
1580
|
+
}
|
|
1581
|
+
const metadata = validation.metadata;
|
|
1582
|
+
const client = new ApiClient(config.serverUrl, config.token);
|
|
1583
|
+
const uploadedSkills = [];
|
|
1584
|
+
const uploadedKnowledges = [];
|
|
1585
|
+
const skillsDir = join4(folderPath, "skills");
|
|
1586
|
+
if (existsSync4(skillsDir) && statSync(skillsDir).isDirectory()) {
|
|
1587
|
+
const skillDirs = execSync3(`ls -1 "${skillsDir}"`, { encoding: "utf-8" }).split(`
|
|
1588
|
+
`).filter((l) => l.trim() && existsSync4(join4(skillsDir, l)) && statSync(join4(skillsDir, l)).isDirectory());
|
|
1589
|
+
for (const skillDir of skillDirs) {
|
|
1590
|
+
const skillPath = join4(skillsDir, skillDir);
|
|
1591
|
+
try {
|
|
1592
|
+
const existingSkill = await client.getSkill(skillDir).catch(() => null);
|
|
1593
|
+
if (existingSkill) {
|
|
1594
|
+
if (shouldOutputJson()) {
|
|
1595
|
+
info(`技能 ${skillDir} 已存在,将仅绑定`);
|
|
1596
|
+
}
|
|
1597
|
+
uploadedSkills.push({ name: skillDir, id: existingSkill.id });
|
|
1598
|
+
} else {
|
|
1599
|
+
if (shouldOutputJson()) {
|
|
1600
|
+
info(`上传新技能: ${skillDir}`);
|
|
1601
|
+
}
|
|
1602
|
+
const skillTempDir = mkdtempSync3("/tmp/skill-upload-");
|
|
1603
|
+
const zipPath = join4(skillTempDir, `${skillDir}.zip`);
|
|
1604
|
+
execSync3(`cd "${skillPath}" && zip -r "${zipPath}" . -x ".*"`, { stdio: "pipe" });
|
|
1605
|
+
const uploadedSkill = await client.uploadSkill(zipPath);
|
|
1606
|
+
uploadedSkills.push({ name: skillDir, id: uploadedSkill.id });
|
|
1607
|
+
rmSync3(skillTempDir, { recursive: true, force: true });
|
|
1608
|
+
}
|
|
1609
|
+
} catch (err) {
|
|
1610
|
+
if (shouldOutputJson()) {
|
|
1611
|
+
outputJson({ success: false, error: { code: "SKILL_UPLOAD_FAILED", message: `处理技能 ${skillDir} 失败: ${err instanceof Error ? err.message : "未知错误"}` } });
|
|
1612
|
+
process.exit(1);
|
|
1613
|
+
}
|
|
1614
|
+
error(`处理技能 ${skillDir} 失败: ${err instanceof Error ? err.message : "未知错误"}`);
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
const knowledgesDir = join4(folderPath, "knowledges");
|
|
1620
|
+
if (existsSync4(knowledgesDir) && statSync(knowledgesDir).isDirectory()) {
|
|
1621
|
+
const mdFiles = execSync3(`ls -1 "${knowledgesDir}"`, { encoding: "utf-8" }).split(`
|
|
1622
|
+
`).filter((l) => l.trim().endsWith(".md"));
|
|
1623
|
+
for (const mdFile of mdFiles) {
|
|
1624
|
+
const mdPath = join4(knowledgesDir, mdFile);
|
|
1625
|
+
const knowledgeName = mdFile.replace(".md", "");
|
|
1626
|
+
try {
|
|
1627
|
+
const existingKnowledge = await client.getKnowledge(knowledgeName).catch(() => null);
|
|
1628
|
+
if (existingKnowledge) {
|
|
1629
|
+
if (shouldOutputJson()) {
|
|
1630
|
+
info(`知识 ${knowledgeName} 已存在,将仅绑定`);
|
|
1631
|
+
}
|
|
1632
|
+
uploadedKnowledges.push({ title: knowledgeName, id: existingKnowledge.id });
|
|
1633
|
+
} else {
|
|
1634
|
+
if (shouldOutputJson()) {
|
|
1635
|
+
info(`上传新知识: ${knowledgeName}`);
|
|
1636
|
+
}
|
|
1637
|
+
const uploadedKnowledge = await client.uploadKnowledge(mdPath);
|
|
1638
|
+
uploadedKnowledges.push({ title: knowledgeName, id: uploadedKnowledge.id });
|
|
1639
|
+
}
|
|
1640
|
+
} catch (err) {
|
|
1641
|
+
if (shouldOutputJson()) {
|
|
1642
|
+
outputJson({ success: false, error: { code: "KNOWLEDGE_UPLOAD_FAILED", message: `处理知识 ${knowledgeName} 失败: ${err instanceof Error ? err.message : "未知错误"}` } });
|
|
1643
|
+
process.exit(1);
|
|
1644
|
+
}
|
|
1645
|
+
error(`处理知识 ${knowledgeName} 失败: ${err instanceof Error ? err.message : "未知错误"}`);
|
|
1646
|
+
process.exit(1);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
if (shouldOutputJson()) {
|
|
1651
|
+
info(`创建 Agent: ${metadata.name}`);
|
|
1652
|
+
}
|
|
1653
|
+
try {
|
|
1654
|
+
const agent = await client.createAgent({
|
|
1655
|
+
name: metadata.name,
|
|
1656
|
+
description: metadata.description,
|
|
1657
|
+
prompt: metadata.prompt
|
|
1658
|
+
});
|
|
1659
|
+
for (const skill of uploadedSkills) {
|
|
1660
|
+
if (shouldOutputJson()) {
|
|
1661
|
+
info(`绑定技能: ${skill.name}`);
|
|
1662
|
+
}
|
|
1663
|
+
await client.bindSkillToAgent(agent.id, skill.id);
|
|
1664
|
+
}
|
|
1665
|
+
for (const knowledge of uploadedKnowledges) {
|
|
1666
|
+
if (shouldOutputJson()) {
|
|
1667
|
+
info(`绑定知识: ${knowledge.title}`);
|
|
1668
|
+
}
|
|
1669
|
+
await client.bindKnowledgeToAgent(agent.id, knowledge.id);
|
|
1670
|
+
}
|
|
1671
|
+
if (shouldOutputJson()) {
|
|
1672
|
+
outputJson({
|
|
1673
|
+
success: true,
|
|
1674
|
+
data: {
|
|
1675
|
+
id: agent.id,
|
|
1676
|
+
name: agent.name,
|
|
1677
|
+
skillsCount: uploadedSkills.length,
|
|
1678
|
+
knowledgesCount: uploadedKnowledges.length
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
success(`Agent "${metadata.name}" 创建成功 (ID: ${agent.id})`);
|
|
1684
|
+
success(`已绑定 ${uploadedSkills.length} 个技能和 ${uploadedKnowledges.length} 个知识`);
|
|
1685
|
+
} catch (err) {
|
|
1686
|
+
if (shouldOutputJson()) {
|
|
1687
|
+
outputJson({ success: false, error: { code: "CREATE_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
|
|
1688
|
+
process.exit(1);
|
|
1689
|
+
}
|
|
1690
|
+
error(`创建失败: ${err instanceof Error ? err.message : "未知错误"}`);
|
|
1691
|
+
process.exit(1);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1414
1694
|
|
|
1415
1695
|
// src/cmd/knowledge.ts
|
|
1416
|
-
import { writeFileSync as writeFileSync4, existsSync as existsSync5, statSync as
|
|
1417
|
-
import { join as join5, basename as
|
|
1696
|
+
import { writeFileSync as writeFileSync4, existsSync as existsSync5, statSync as statSync4 } from "fs";
|
|
1697
|
+
import { join as join5, basename as basename3, dirname as dirname2 } from "path";
|
|
1418
1698
|
import { execSync as execSync4 } from "child_process";
|
|
1419
1699
|
import { mkdtempSync as mkdtempSync4, rmSync as rmSync4 } from "fs";
|
|
1420
1700
|
async function listKnowledge() {
|
|
@@ -1568,12 +1848,12 @@ async function uploadKnowledge(filePath) {
|
|
|
1568
1848
|
error(`上传失败: 目录不存在: ${filePath}`);
|
|
1569
1849
|
process.exit(1);
|
|
1570
1850
|
}
|
|
1571
|
-
const knowledgeName =
|
|
1851
|
+
const knowledgeName = basename3(filePath);
|
|
1572
1852
|
const tempDir = mkdtempSync4("/tmp/knowledge-upload-");
|
|
1573
1853
|
const zipPath = join5(tempDir, `${knowledgeName}.zip`);
|
|
1574
1854
|
try {
|
|
1575
|
-
if (
|
|
1576
|
-
execSync4(`cd "${dirname2(filePath)}" && zip -r "${zipPath}" "${
|
|
1855
|
+
if (statSync4(filePath).isDirectory()) {
|
|
1856
|
+
execSync4(`cd "${dirname2(filePath)}" && zip -r "${zipPath}" "${basename3(filePath)}" -x ".*"`, { stdio: "pipe" });
|
|
1577
1857
|
} else {
|
|
1578
1858
|
execSync4(`cp "${filePath}" "${zipPath}"`, { stdio: "pipe" });
|
|
1579
1859
|
}
|
|
@@ -1587,6 +1867,9 @@ async function uploadKnowledge(filePath) {
|
|
|
1587
1867
|
return;
|
|
1588
1868
|
}
|
|
1589
1869
|
success(`上传成功! Knowledge: ${knowledge.name}`);
|
|
1870
|
+
if (knowledge.shareUrl) {
|
|
1871
|
+
info(`分享链接: ${knowledge.shareUrl}`);
|
|
1872
|
+
}
|
|
1590
1873
|
} catch (err) {
|
|
1591
1874
|
if (shouldOutputJson()) {
|
|
1592
1875
|
outputJson({ success: false, error: { code: "UPLOAD_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
|
|
@@ -1678,6 +1961,21 @@ var command = args[0];
|
|
|
1678
1961
|
var subCommand = args[1];
|
|
1679
1962
|
async function main() {
|
|
1680
1963
|
switch (command) {
|
|
1964
|
+
case "register":
|
|
1965
|
+
if (args[1] && args[1].startsWith("--")) {
|
|
1966
|
+
const options = {};
|
|
1967
|
+
for (let i = 1;i < args.length; i++) {
|
|
1968
|
+
const arg = args[i];
|
|
1969
|
+
if (arg.startsWith("--")) {
|
|
1970
|
+
const [key, value] = arg.slice(2).split("=");
|
|
1971
|
+
options[key] = value;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
await register(options.name, options.email, options.password);
|
|
1975
|
+
} else {
|
|
1976
|
+
await register(args[1], args[2], args[3]);
|
|
1977
|
+
}
|
|
1978
|
+
break;
|
|
1681
1979
|
case "login":
|
|
1682
1980
|
if (!args[1] || !args[2]) {
|
|
1683
1981
|
console.error("用法: arm login <server-url> <api-key>");
|
|
@@ -1859,7 +2157,7 @@ async function main() {
|
|
|
1859
2157
|
break;
|
|
1860
2158
|
case "create":
|
|
1861
2159
|
if (!args[2]) {
|
|
1862
|
-
console.error(`用法: arm agent create <name> [--description="..."] [--prompt="..."] [--avatar="..."] [--skill=id] [--knowledge=id] [--skill-config='{...}'] [--knowledge-config='{...}'] [--json]`);
|
|
2160
|
+
console.error(`用法: arm agent create <name> [--description="..."] [--prompt="..."] [--avatar="..."] [--skill=id] [--knowledge=id] [--skill-config='{...}'] [--knowledge-config='{...}'] [--from=<folder-path>] [--json]`);
|
|
1863
2161
|
process.exit(1);
|
|
1864
2162
|
}
|
|
1865
2163
|
{
|
|
@@ -1869,6 +2167,7 @@ async function main() {
|
|
|
1869
2167
|
const knowledges = [];
|
|
1870
2168
|
const skillConfigs = [];
|
|
1871
2169
|
const knowledgeConfigs = [];
|
|
2170
|
+
let fromFolder;
|
|
1872
2171
|
for (let i = 3;i < args.length; i++) {
|
|
1873
2172
|
const arg = args[i];
|
|
1874
2173
|
if (arg.startsWith("--description=")) {
|
|
@@ -1885,17 +2184,23 @@ async function main() {
|
|
|
1885
2184
|
skillConfigs.push(arg.split("=").slice(1).join("="));
|
|
1886
2185
|
} else if (arg.startsWith("--knowledge-config=")) {
|
|
1887
2186
|
knowledgeConfigs.push(arg.split("=").slice(1).join("="));
|
|
2187
|
+
} else if (arg.startsWith("--from=")) {
|
|
2188
|
+
fromFolder = arg.split("=").slice(1).join("=");
|
|
1888
2189
|
}
|
|
1889
2190
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2191
|
+
if (fromFolder) {
|
|
2192
|
+
await createAgentFromFolder(fromFolder);
|
|
2193
|
+
} else {
|
|
2194
|
+
await createAgent(name, {
|
|
2195
|
+
description: options.description,
|
|
2196
|
+
prompt: options.prompt,
|
|
2197
|
+
avatar: options.avatar,
|
|
2198
|
+
skills,
|
|
2199
|
+
knowledges,
|
|
2200
|
+
skillConfigs,
|
|
2201
|
+
knowledgeConfigs
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
1899
2204
|
}
|
|
1900
2205
|
break;
|
|
1901
2206
|
case "update":
|
|
@@ -2004,6 +2309,7 @@ async function main() {
|
|
|
2004
2309
|
arm agent info <name> 查看 Agent 详情
|
|
2005
2310
|
arm agent download <name> [dir] 下载 Agent
|
|
2006
2311
|
arm agent create <name> 创建 Agent (--description, --prompt, --avatar, --skill, --knowledge)
|
|
2312
|
+
arm agent create --from=<folder> 从本地文件夹创建 Agent
|
|
2007
2313
|
arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
|
|
2008
2314
|
arm agent delete <id> 删除 Agent
|
|
2009
2315
|
arm agent bind <id> --skill=<id> 绑定 Skill 到 Agent
|
|
@@ -2019,6 +2325,7 @@ async function main() {
|
|
|
2019
2325
|
Agent Resource Management (arm)
|
|
2020
2326
|
|
|
2021
2327
|
用法:
|
|
2328
|
+
arm register [--name=<name>] [--email=<email>] [--password=<password>] 注册 (交互式或参数)
|
|
2022
2329
|
arm login <server-url> <api-key> 登录
|
|
2023
2330
|
arm logout 登出
|
|
2024
2331
|
arm output [json|text] 设置/查看输出模式 (默认json)
|
package/dist/test.md
ADDED
package/dist/test2.md
ADDED
package/package.json
CHANGED
package/src/cmd/agent.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { ApiClient } from '../lib/client';
|
|
|
2
2
|
import { loadConfig } from '../lib/storage';
|
|
3
3
|
import { formatAgent, formatAgentDetail, success, error, info } from '../lib/formatter';
|
|
4
4
|
import { shouldOutputJson, outputJson } from '../lib/output';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { validateAgentDir } from '../lib/validate';
|
|
6
|
+
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { join, basename } from 'path';
|
|
7
8
|
import { execSync } from 'child_process';
|
|
8
9
|
import { mkdtempSync, rmSync } from 'fs';
|
|
9
10
|
|
|
@@ -463,4 +464,164 @@ export async function downloadAgent(name: string, outputDir?: string): Promise<v
|
|
|
463
464
|
error(`下载失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
|
464
465
|
process.exit(1);
|
|
465
466
|
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export async function createAgentFromFolder(folderPath: string): Promise<void> {
|
|
470
|
+
const config = loadConfig();
|
|
471
|
+
if (!config?.token) {
|
|
472
|
+
if (shouldOutputJson()) {
|
|
473
|
+
outputJson({ success: false, error: { code: 'NOT_LOGGED_IN', message: '未登录,请先运行 arm login' } });
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
error('未登录,请先运行 arm login');
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (!existsSync(folderPath)) {
|
|
481
|
+
if (shouldOutputJson()) {
|
|
482
|
+
outputJson({ success: false, error: { code: 'FILE_NOT_FOUND', message: `目录不存在: ${folderPath}` } });
|
|
483
|
+
process.exit(1);
|
|
484
|
+
}
|
|
485
|
+
error(`目录不存在: ${folderPath}`);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const validation = validateAgentDir(folderPath);
|
|
490
|
+
if (!validation.valid) {
|
|
491
|
+
if (shouldOutputJson()) {
|
|
492
|
+
outputJson({ success: false, error: { code: 'VALIDATION_FAILED', message: validation.errors.join(', ') } });
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
error(`验证失败: ${validation.errors.join(', ')}`);
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (shouldOutputJson()) {
|
|
500
|
+
info('正在解析 Agent 文件夹...');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const metadata = validation.metadata!;
|
|
504
|
+
const client = new ApiClient(config.serverUrl, config.token);
|
|
505
|
+
|
|
506
|
+
const uploadedSkills: { name: string; id: string }[] = [];
|
|
507
|
+
const uploadedKnowledges: { title: string; id: string }[] = [];
|
|
508
|
+
|
|
509
|
+
const skillsDir = join(folderPath, 'skills');
|
|
510
|
+
if (existsSync(skillsDir) && statSync(skillsDir).isDirectory()) {
|
|
511
|
+
const skillDirs = execSync(`ls -1 "${skillsDir}"`, { encoding: 'utf-8' })
|
|
512
|
+
.split('\n')
|
|
513
|
+
.filter(l => l.trim() && existsSync(join(skillsDir, l)) && statSync(join(skillsDir, l)).isDirectory());
|
|
514
|
+
|
|
515
|
+
for (const skillDir of skillDirs) {
|
|
516
|
+
const skillPath = join(skillsDir, skillDir);
|
|
517
|
+
try {
|
|
518
|
+
const existingSkill = await client.getSkill(skillDir).catch(() => null);
|
|
519
|
+
if (existingSkill) {
|
|
520
|
+
if (shouldOutputJson()) {
|
|
521
|
+
info(`技能 ${skillDir} 已存在,将仅绑定`);
|
|
522
|
+
}
|
|
523
|
+
uploadedSkills.push({ name: skillDir, id: existingSkill.id });
|
|
524
|
+
} else {
|
|
525
|
+
if (shouldOutputJson()) {
|
|
526
|
+
info(`上传新技能: ${skillDir}`);
|
|
527
|
+
}
|
|
528
|
+
const skillTempDir = mkdtempSync('/tmp/skill-upload-');
|
|
529
|
+
const zipPath = join(skillTempDir, `${skillDir}.zip`);
|
|
530
|
+
execSync(`cd "${skillPath}" && zip -r "${zipPath}" . -x ".*"`, { stdio: 'pipe' });
|
|
531
|
+
const uploadedSkill = await client.uploadSkill(zipPath);
|
|
532
|
+
uploadedSkills.push({ name: skillDir, id: uploadedSkill.id });
|
|
533
|
+
rmSync(skillTempDir, { recursive: true, force: true });
|
|
534
|
+
}
|
|
535
|
+
} catch (err) {
|
|
536
|
+
if (shouldOutputJson()) {
|
|
537
|
+
outputJson({ success: false, error: { code: 'SKILL_UPLOAD_FAILED', message: `处理技能 ${skillDir} 失败: ${err instanceof Error ? err.message : '未知错误'}` } });
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
error(`处理技能 ${skillDir} 失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const knowledgesDir = join(folderPath, 'knowledges');
|
|
547
|
+
if (existsSync(knowledgesDir) && statSync(knowledgesDir).isDirectory()) {
|
|
548
|
+
const mdFiles = execSync(`ls -1 "${knowledgesDir}"`, { encoding: 'utf-8' })
|
|
549
|
+
.split('\n')
|
|
550
|
+
.filter(l => l.trim().endsWith('.md'));
|
|
551
|
+
|
|
552
|
+
for (const mdFile of mdFiles) {
|
|
553
|
+
const mdPath = join(knowledgesDir, mdFile);
|
|
554
|
+
const knowledgeName = mdFile.replace('.md', '');
|
|
555
|
+
try {
|
|
556
|
+
const existingKnowledge = await client.getKnowledge(knowledgeName).catch(() => null);
|
|
557
|
+
if (existingKnowledge) {
|
|
558
|
+
if (shouldOutputJson()) {
|
|
559
|
+
info(`知识 ${knowledgeName} 已存在,将仅绑定`);
|
|
560
|
+
}
|
|
561
|
+
uploadedKnowledges.push({ title: knowledgeName, id: existingKnowledge.id });
|
|
562
|
+
} else {
|
|
563
|
+
if (shouldOutputJson()) {
|
|
564
|
+
info(`上传新知识: ${knowledgeName}`);
|
|
565
|
+
}
|
|
566
|
+
const uploadedKnowledge = await client.uploadKnowledge(mdPath);
|
|
567
|
+
uploadedKnowledges.push({ title: knowledgeName, id: uploadedKnowledge.id });
|
|
568
|
+
}
|
|
569
|
+
} catch (err) {
|
|
570
|
+
if (shouldOutputJson()) {
|
|
571
|
+
outputJson({ success: false, error: { code: 'KNOWLEDGE_UPLOAD_FAILED', message: `处理知识 ${knowledgeName} 失败: ${err instanceof Error ? err.message : '未知错误'}` } });
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
error(`处理知识 ${knowledgeName} 失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
|
575
|
+
process.exit(1);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (shouldOutputJson()) {
|
|
581
|
+
info(`创建 Agent: ${metadata.name}`);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const agent = await client.createAgent({
|
|
586
|
+
name: metadata.name!,
|
|
587
|
+
description: metadata.description,
|
|
588
|
+
prompt: metadata.prompt,
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
for (const skill of uploadedSkills) {
|
|
592
|
+
if (shouldOutputJson()) {
|
|
593
|
+
info(`绑定技能: ${skill.name}`);
|
|
594
|
+
}
|
|
595
|
+
await client.bindSkillToAgent(agent.id, skill.id);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
for (const knowledge of uploadedKnowledges) {
|
|
599
|
+
if (shouldOutputJson()) {
|
|
600
|
+
info(`绑定知识: ${knowledge.title}`);
|
|
601
|
+
}
|
|
602
|
+
await client.bindKnowledgeToAgent(agent.id, knowledge.id);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (shouldOutputJson()) {
|
|
606
|
+
outputJson({
|
|
607
|
+
success: true,
|
|
608
|
+
data: {
|
|
609
|
+
id: agent.id,
|
|
610
|
+
name: agent.name,
|
|
611
|
+
skillsCount: uploadedSkills.length,
|
|
612
|
+
knowledgesCount: uploadedKnowledges.length,
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
success(`Agent "${metadata.name}" 创建成功 (ID: ${agent.id})`);
|
|
618
|
+
success(`已绑定 ${uploadedSkills.length} 个技能和 ${uploadedKnowledges.length} 个知识`);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
if (shouldOutputJson()) {
|
|
621
|
+
outputJson({ success: false, error: { code: 'CREATE_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
|
|
622
|
+
process.exit(1);
|
|
623
|
+
}
|
|
624
|
+
error(`创建失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
466
627
|
}
|
package/src/cmd/auth.ts
CHANGED
|
@@ -2,6 +2,56 @@ import { ApiClient } from '../lib/client';
|
|
|
2
2
|
import { loadConfig, saveConfig } from '../lib/storage';
|
|
3
3
|
import { success, error, info } from '../lib/formatter';
|
|
4
4
|
|
|
5
|
+
export async function register(name?: string, email?: string, password?: string): Promise<void> {
|
|
6
|
+
const { readline } = await import('readline');
|
|
7
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8
|
+
|
|
9
|
+
const ask = (question: string): Promise<string> =>
|
|
10
|
+
new Promise((resolve) => rl.question(question, resolve));
|
|
11
|
+
|
|
12
|
+
let finalEmail = email;
|
|
13
|
+
let finalPassword = password;
|
|
14
|
+
let finalName = name;
|
|
15
|
+
|
|
16
|
+
if (!finalEmail) {
|
|
17
|
+
finalEmail = await ask('请输入邮箱: ');
|
|
18
|
+
}
|
|
19
|
+
if (!finalPassword) {
|
|
20
|
+
finalPassword = await ask('请输入密码: ');
|
|
21
|
+
}
|
|
22
|
+
if (!finalName) {
|
|
23
|
+
finalName = await ask('请输入用户名: ');
|
|
24
|
+
}
|
|
25
|
+
rl.close();
|
|
26
|
+
|
|
27
|
+
if (!finalEmail || !finalPassword || !finalName) {
|
|
28
|
+
error('邮箱、密码和用户名都不能为空');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (finalPassword.length < 8) {
|
|
33
|
+
error('密码至少需要 8 个字符');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const config = loadConfig();
|
|
38
|
+
const serverUrl = config?.serverUrl || 'http://localhost:3000';
|
|
39
|
+
const client = new ApiClient(serverUrl);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const result = await client.register(finalEmail, finalPassword, finalName);
|
|
43
|
+
saveConfig({
|
|
44
|
+
serverUrl,
|
|
45
|
+
token: result.token,
|
|
46
|
+
user: result.user,
|
|
47
|
+
});
|
|
48
|
+
success(`注册成功! 欢迎, ${result.user.name}`);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
error(`注册失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
5
55
|
export async function login(serverUrl: string, apiKey: string): Promise<void> {
|
|
6
56
|
try {
|
|
7
57
|
const client = new ApiClient(serverUrl);
|
package/src/cmd/knowledge.ts
CHANGED
|
@@ -185,6 +185,9 @@ export async function uploadKnowledge(filePath: string): Promise<void> {
|
|
|
185
185
|
return;
|
|
186
186
|
}
|
|
187
187
|
success(`上传成功! Knowledge: ${knowledge.name}`);
|
|
188
|
+
if (knowledge.shareUrl) {
|
|
189
|
+
info(`分享链接: ${knowledge.shareUrl}`);
|
|
190
|
+
}
|
|
188
191
|
} catch (err) {
|
|
189
192
|
if (shouldOutputJson()) {
|
|
190
193
|
outputJson({ success: false, error: { code: 'UPLOAD_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
|
package/src/lib/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { User, Skill, SkillListResponse, ApiResponse, LoginResponse, Agent, AgentListResponse } from '@pkg/types/skill';
|
|
1
|
+
import type { User, Skill, SkillListResponse, ApiResponse, LoginResponse, RegisterResponse, Agent, AgentListResponse } from '@pkg/types/skill';
|
|
2
2
|
|
|
3
3
|
export class ApiClient {
|
|
4
4
|
private token: string | null = null;
|
|
@@ -49,6 +49,17 @@ export class ApiClient {
|
|
|
49
49
|
return res.data;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
async register(email: string, password: string, name: string): Promise<RegisterResponse> {
|
|
53
|
+
const res = await this.request<RegisterResponse>('/auth/register', {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
body: JSON.stringify({ email, password, name }),
|
|
56
|
+
});
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new Error(res.msg);
|
|
59
|
+
}
|
|
60
|
+
return res.data;
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
async me(): Promise<User> {
|
|
53
64
|
const res = await this.request<User>('/auth/me');
|
|
54
65
|
if (!res.ok) {
|
package/src/lib/validate.ts
CHANGED
|
@@ -203,7 +203,124 @@ export function validateSkillDir(dirPath: string): ValidationResult {
|
|
|
203
203
|
result.errors.push('SKILL.md 缺少 frontmatter (--- 包裹的 YAML)');
|
|
204
204
|
}
|
|
205
205
|
} catch (err) {
|
|
206
|
-
|
|
206
|
+
result.warnings.push(`SKILL.md 读取失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface AgentValidationResult {
|
|
213
|
+
valid: boolean;
|
|
214
|
+
errors: string[];
|
|
215
|
+
warnings: string[];
|
|
216
|
+
metadata?: {
|
|
217
|
+
name?: string;
|
|
218
|
+
version?: string;
|
|
219
|
+
description?: string;
|
|
220
|
+
prompt?: string;
|
|
221
|
+
skills?: string[];
|
|
222
|
+
knowledges?: string[];
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function validateAgentDir(dirPath: string): AgentValidationResult {
|
|
227
|
+
const result: AgentValidationResult = {
|
|
228
|
+
valid: true,
|
|
229
|
+
errors: [],
|
|
230
|
+
warnings: [],
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (!existsSync(dirPath)) {
|
|
234
|
+
result.valid = false;
|
|
235
|
+
result.errors.push(`目录不存在: ${dirPath}`);
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const agentMdPath = join(dirPath, 'AGENT.md');
|
|
240
|
+
if (!existsSync(agentMdPath)) {
|
|
241
|
+
result.valid = false;
|
|
242
|
+
result.errors.push('目录内缺少 AGENT.md 文件');
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
const agentMdContent = readFileSync(agentMdPath, 'utf-8');
|
|
248
|
+
const frontmatterMatch = agentMdContent.match(/^---\n([\s\S]*?)\n---/);
|
|
249
|
+
|
|
250
|
+
if (frontmatterMatch) {
|
|
251
|
+
const frontmatter = frontmatterMatch[1];
|
|
252
|
+
result.metadata = {};
|
|
253
|
+
|
|
254
|
+
const nameMatch = frontmatter.match(/name:\s*(.+)/);
|
|
255
|
+
const versionMatch = frontmatter.match(/version:\s*(.+)/);
|
|
256
|
+
const descMatch = frontmatter.match(/description:\s*(.+)/);
|
|
257
|
+
const skillsMatch = frontmatter.match(/skills:\s*([\s\S]*?)(?=\n\w|---)/);
|
|
258
|
+
const knowledgesMatch = frontmatter.match(/knowledges:\s*([\s\S]*?)(?=\n\w|---)/);
|
|
259
|
+
|
|
260
|
+
if (nameMatch) {
|
|
261
|
+
result.metadata.name = nameMatch[1].trim();
|
|
262
|
+
} else {
|
|
263
|
+
result.valid = false;
|
|
264
|
+
result.errors.push('缺少 name 字段');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (versionMatch) {
|
|
268
|
+
result.metadata.version = versionMatch[1].trim();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (descMatch) {
|
|
272
|
+
result.metadata.description = descMatch[1].trim();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (skillsMatch) {
|
|
276
|
+
const skillsLines = skillsMatch[1].split('\n').filter(l => l.trim().startsWith('-'));
|
|
277
|
+
result.metadata.skills = skillsLines.map(l => l.replace(/^\s*-\s*/, '').trim());
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (knowledgesMatch) {
|
|
281
|
+
const knowledgesLines = knowledgesMatch[1].split('\n').filter(l => l.trim().startsWith('-'));
|
|
282
|
+
result.metadata.knowledges = knowledgesLines.map(l => l.replace(/^\s*-\s*/, '').trim());
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const contentAfterFrontmatter = agentMdContent.replace(/^---[\s\S]*?---\n/, '');
|
|
286
|
+
const promptMatch = contentAfterFrontmatter.match(/#\s*System\s*Prompt\n([\s\S]*?)$/);
|
|
287
|
+
if (promptMatch) {
|
|
288
|
+
result.metadata.prompt = promptMatch[1].trim();
|
|
289
|
+
} else if (contentAfterFrontmatter.trim()) {
|
|
290
|
+
result.metadata.prompt = contentAfterFrontmatter.trim();
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
result.valid = false;
|
|
294
|
+
result.errors.push('AGENT.md 缺少 frontmatter (--- 包裹的 YAML)');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const skillsDir = join(dirPath, 'skills');
|
|
298
|
+
if (existsSync(skillsDir) && statSync(skillsDir).isDirectory()) {
|
|
299
|
+
const skillDirs = execSync(`ls -1 "${skillsDir}"`, { encoding: 'utf-8' })
|
|
300
|
+
.split('\n')
|
|
301
|
+
.filter(l => l.trim() && existsSync(join(skillsDir, l)) && statSync(join(skillsDir, l)).isDirectory());
|
|
302
|
+
|
|
303
|
+
for (const skillDir of skillDirs) {
|
|
304
|
+
const skillMdPath = join(skillsDir, skillDir, 'SKILL.md');
|
|
305
|
+
if (!existsSync(skillMdPath)) {
|
|
306
|
+
result.warnings.push(`skills/${skillDir} 目录内缺少 SKILL.md 文件`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const knowledgesDir = join(dirPath, 'knowledges');
|
|
312
|
+
if (existsSync(knowledgesDir) && statSync(knowledgesDir).isDirectory()) {
|
|
313
|
+
const mdFiles = execSync(`ls -1 "${knowledgesDir}"`, { encoding: 'utf-8' })
|
|
314
|
+
.split('\n')
|
|
315
|
+
.filter(l => l.trim().endsWith('.md'));
|
|
316
|
+
|
|
317
|
+
if (mdFiles.length === 0) {
|
|
318
|
+
result.warnings.push('knowledges/ 目录内没有 .md 文件');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} catch (err) {
|
|
322
|
+
result.valid = false;
|
|
323
|
+
result.errors.push(`AGENT.md 读取失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
|
207
324
|
}
|
|
208
325
|
|
|
209
326
|
return result;
|
package/src/main.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { login, logout, getCurrentUser } from './cmd/auth';
|
|
1
|
+
import { login, logout, getCurrentUser, register } from './cmd/auth';
|
|
2
2
|
import { listSkills, searchSkills, infoSkill, downloadSkill, uploadSkill, mySkills, deleteSkill, validateSkill } from './cmd/skill';
|
|
3
|
-
import { listAgents, searchAgents, infoAgent, downloadAgent, createAgent, updateAgent, deleteAgent, bindSkill, unbindSkill, bindKnowledge, unbindKnowledge } from './cmd/agent';
|
|
3
|
+
import { listAgents, searchAgents, infoAgent, downloadAgent, createAgent, updateAgent, deleteAgent, bindSkill, unbindSkill, bindKnowledge, unbindKnowledge, createAgentFromFolder } from './cmd/agent';
|
|
4
4
|
import { listKnowledge, searchKnowledge, infoKnowledge, downloadKnowledge, uploadKnowledge, myKnowledge, deleteKnowledge } from './cmd/knowledge';
|
|
5
5
|
import { showServer, setServer } from './cmd/server';
|
|
6
6
|
import { getOutputMode, setOutputMode } from './lib/output';
|
|
@@ -11,6 +11,22 @@ const subCommand = args[1];
|
|
|
11
11
|
|
|
12
12
|
async function main() {
|
|
13
13
|
switch (command) {
|
|
14
|
+
case 'register':
|
|
15
|
+
if (args[1] && args[1].startsWith('--')) {
|
|
16
|
+
const options: Record<string, string | undefined> = {};
|
|
17
|
+
for (let i = 1; i < args.length; i++) {
|
|
18
|
+
const arg = args[i];
|
|
19
|
+
if (arg.startsWith('--')) {
|
|
20
|
+
const [key, value] = arg.slice(2).split('=');
|
|
21
|
+
options[key] = value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
await register(options.name, options.email, options.password);
|
|
25
|
+
} else {
|
|
26
|
+
await register(args[1], args[2], args[3]);
|
|
27
|
+
}
|
|
28
|
+
break;
|
|
29
|
+
|
|
14
30
|
case 'login':
|
|
15
31
|
if (!args[1] || !args[2]) {
|
|
16
32
|
console.error('用法: arm login <server-url> <api-key>');
|
|
@@ -199,7 +215,7 @@ async function main() {
|
|
|
199
215
|
break;
|
|
200
216
|
case 'create':
|
|
201
217
|
if (!args[2]) {
|
|
202
|
-
console.error('用法: arm agent create <name> [--description="..."] [--prompt="..."] [--avatar="..."] [--skill=id] [--knowledge=id] [--skill-config=\'{...}\'] [--knowledge-config=\'{...}\'] [--json]');
|
|
218
|
+
console.error('用法: arm agent create <name> [--description="..."] [--prompt="..."] [--avatar="..."] [--skill=id] [--knowledge=id] [--skill-config=\'{...}\'] [--knowledge-config=\'{...}\'] [--from=<folder-path>] [--json]');
|
|
203
219
|
process.exit(1);
|
|
204
220
|
}
|
|
205
221
|
{
|
|
@@ -209,6 +225,7 @@ async function main() {
|
|
|
209
225
|
const knowledges: string[] = [];
|
|
210
226
|
const skillConfigs: string[] = [];
|
|
211
227
|
const knowledgeConfigs: string[] = [];
|
|
228
|
+
let fromFolder: string | undefined;
|
|
212
229
|
|
|
213
230
|
for (let i = 3; i < args.length; i++) {
|
|
214
231
|
const arg = args[i];
|
|
@@ -226,18 +243,24 @@ async function main() {
|
|
|
226
243
|
skillConfigs.push(arg.split('=').slice(1).join('='));
|
|
227
244
|
} else if (arg.startsWith('--knowledge-config=')) {
|
|
228
245
|
knowledgeConfigs.push(arg.split('=').slice(1).join('='));
|
|
246
|
+
} else if (arg.startsWith('--from=')) {
|
|
247
|
+
fromFolder = arg.split('=').slice(1).join('=');
|
|
229
248
|
}
|
|
230
249
|
}
|
|
231
250
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
251
|
+
if (fromFolder) {
|
|
252
|
+
await createAgentFromFolder(fromFolder);
|
|
253
|
+
} else {
|
|
254
|
+
await createAgent(name, {
|
|
255
|
+
description: options.description,
|
|
256
|
+
prompt: options.prompt,
|
|
257
|
+
avatar: options.avatar,
|
|
258
|
+
skills,
|
|
259
|
+
knowledges,
|
|
260
|
+
skillConfigs,
|
|
261
|
+
knowledgeConfigs,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
241
264
|
}
|
|
242
265
|
break;
|
|
243
266
|
case 'update':
|
|
@@ -352,6 +375,7 @@ async function main() {
|
|
|
352
375
|
arm agent info <name> 查看 Agent 详情
|
|
353
376
|
arm agent download <name> [dir] 下载 Agent
|
|
354
377
|
arm agent create <name> 创建 Agent (--description, --prompt, --avatar, --skill, --knowledge)
|
|
378
|
+
arm agent create --from=<folder> 从本地文件夹创建 Agent
|
|
355
379
|
arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
|
|
356
380
|
arm agent delete <id> 删除 Agent
|
|
357
381
|
arm agent bind <id> --skill=<id> 绑定 Skill 到 Agent
|
|
@@ -368,6 +392,7 @@ async function main() {
|
|
|
368
392
|
Agent Resource Management (arm)
|
|
369
393
|
|
|
370
394
|
用法:
|
|
395
|
+
arm register [--name=<name>] [--email=<email>] [--password=<password>] 注册 (交互式或参数)
|
|
371
396
|
arm login <server-url> <api-key> 登录
|
|
372
397
|
arm logout 登出
|
|
373
398
|
arm output [json|text] 设置/查看输出模式 (默认json)
|