agent-resource-management 2.1.1 → 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/dist/main.js +256 -19
- package/package.json +1 -1
- package/src/cmd/agent.ts +163 -2
- package/src/lib/validate.ts +118 -1
- package/src/main.ts +19 -11
package/dist/main.js
CHANGED
|
@@ -577,7 +577,7 @@ function setOutputMode(mode) {
|
|
|
577
577
|
}
|
|
578
578
|
|
|
579
579
|
// src/lib/validate.ts
|
|
580
|
-
import { readFileSync as readFileSync2, existsSync as existsSync2, statSync } from "fs";
|
|
580
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
581
581
|
import { execSync } from "child_process";
|
|
582
582
|
import { join as join2, extname } from "path";
|
|
583
583
|
import { mkdtempSync, rmSync } from "fs";
|
|
@@ -597,7 +597,7 @@ function validateZip(filePath) {
|
|
|
597
597
|
result.errors.push("文件必须是 ZIP 格式");
|
|
598
598
|
return result;
|
|
599
599
|
}
|
|
600
|
-
const stats =
|
|
600
|
+
const stats = statSync2(filePath);
|
|
601
601
|
if (stats.size === 0) {
|
|
602
602
|
result.valid = false;
|
|
603
603
|
result.errors.push("ZIP 文件为空");
|
|
@@ -747,9 +747,95 @@ function validateSkillDir(dirPath) {
|
|
|
747
747
|
}
|
|
748
748
|
return result;
|
|
749
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
|
+
}
|
|
750
836
|
|
|
751
837
|
// src/cmd/skill.ts
|
|
752
|
-
import { writeFileSync as writeFileSync2, existsSync as existsSync3, statSync as
|
|
838
|
+
import { writeFileSync as writeFileSync2, existsSync as existsSync3, statSync as statSync3 } from "fs";
|
|
753
839
|
import { join as join3, basename, dirname } from "path";
|
|
754
840
|
import { execSync as execSync2 } from "child_process";
|
|
755
841
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
|
|
@@ -1010,7 +1096,7 @@ async function deleteSkill(name) {
|
|
|
1010
1096
|
}
|
|
1011
1097
|
}
|
|
1012
1098
|
async function validateSkill(filePath) {
|
|
1013
|
-
const isDir = existsSync3(filePath) &&
|
|
1099
|
+
const isDir = existsSync3(filePath) && statSync3(filePath).isDirectory();
|
|
1014
1100
|
const result = isDir ? validateSkillDir(filePath) : validateZip(filePath);
|
|
1015
1101
|
if (shouldOutputJson()) {
|
|
1016
1102
|
outputJson({
|
|
@@ -1462,10 +1548,153 @@ async function downloadAgent(name, outputDir) {
|
|
|
1462
1548
|
process.exit(1);
|
|
1463
1549
|
}
|
|
1464
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
|
+
}
|
|
1465
1694
|
|
|
1466
1695
|
// src/cmd/knowledge.ts
|
|
1467
|
-
import { writeFileSync as writeFileSync4, existsSync as existsSync5, statSync as
|
|
1468
|
-
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";
|
|
1469
1698
|
import { execSync as execSync4 } from "child_process";
|
|
1470
1699
|
import { mkdtempSync as mkdtempSync4, rmSync as rmSync4 } from "fs";
|
|
1471
1700
|
async function listKnowledge() {
|
|
@@ -1619,12 +1848,12 @@ async function uploadKnowledge(filePath) {
|
|
|
1619
1848
|
error(`上传失败: 目录不存在: ${filePath}`);
|
|
1620
1849
|
process.exit(1);
|
|
1621
1850
|
}
|
|
1622
|
-
const knowledgeName =
|
|
1851
|
+
const knowledgeName = basename3(filePath);
|
|
1623
1852
|
const tempDir = mkdtempSync4("/tmp/knowledge-upload-");
|
|
1624
1853
|
const zipPath = join5(tempDir, `${knowledgeName}.zip`);
|
|
1625
1854
|
try {
|
|
1626
|
-
if (
|
|
1627
|
-
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" });
|
|
1628
1857
|
} else {
|
|
1629
1858
|
execSync4(`cp "${filePath}" "${zipPath}"`, { stdio: "pipe" });
|
|
1630
1859
|
}
|
|
@@ -1928,7 +2157,7 @@ async function main() {
|
|
|
1928
2157
|
break;
|
|
1929
2158
|
case "create":
|
|
1930
2159
|
if (!args[2]) {
|
|
1931
|
-
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]`);
|
|
1932
2161
|
process.exit(1);
|
|
1933
2162
|
}
|
|
1934
2163
|
{
|
|
@@ -1938,6 +2167,7 @@ async function main() {
|
|
|
1938
2167
|
const knowledges = [];
|
|
1939
2168
|
const skillConfigs = [];
|
|
1940
2169
|
const knowledgeConfigs = [];
|
|
2170
|
+
let fromFolder;
|
|
1941
2171
|
for (let i = 3;i < args.length; i++) {
|
|
1942
2172
|
const arg = args[i];
|
|
1943
2173
|
if (arg.startsWith("--description=")) {
|
|
@@ -1954,17 +2184,23 @@ async function main() {
|
|
|
1954
2184
|
skillConfigs.push(arg.split("=").slice(1).join("="));
|
|
1955
2185
|
} else if (arg.startsWith("--knowledge-config=")) {
|
|
1956
2186
|
knowledgeConfigs.push(arg.split("=").slice(1).join("="));
|
|
2187
|
+
} else if (arg.startsWith("--from=")) {
|
|
2188
|
+
fromFolder = arg.split("=").slice(1).join("=");
|
|
1957
2189
|
}
|
|
1958
2190
|
}
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
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
|
+
}
|
|
1968
2204
|
}
|
|
1969
2205
|
break;
|
|
1970
2206
|
case "update":
|
|
@@ -2073,6 +2309,7 @@ async function main() {
|
|
|
2073
2309
|
arm agent info <name> 查看 Agent 详情
|
|
2074
2310
|
arm agent download <name> [dir] 下载 Agent
|
|
2075
2311
|
arm agent create <name> 创建 Agent (--description, --prompt, --avatar, --skill, --knowledge)
|
|
2312
|
+
arm agent create --from=<folder> 从本地文件夹创建 Agent
|
|
2076
2313
|
arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
|
|
2077
2314
|
arm agent delete <id> 删除 Agent
|
|
2078
2315
|
arm agent bind <id> --skill=<id> 绑定 Skill 到 Agent
|
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/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
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';
|
|
@@ -215,7 +215,7 @@ async function main() {
|
|
|
215
215
|
break;
|
|
216
216
|
case 'create':
|
|
217
217
|
if (!args[2]) {
|
|
218
|
-
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]');
|
|
219
219
|
process.exit(1);
|
|
220
220
|
}
|
|
221
221
|
{
|
|
@@ -225,6 +225,7 @@ async function main() {
|
|
|
225
225
|
const knowledges: string[] = [];
|
|
226
226
|
const skillConfigs: string[] = [];
|
|
227
227
|
const knowledgeConfigs: string[] = [];
|
|
228
|
+
let fromFolder: string | undefined;
|
|
228
229
|
|
|
229
230
|
for (let i = 3; i < args.length; i++) {
|
|
230
231
|
const arg = args[i];
|
|
@@ -242,18 +243,24 @@ async function main() {
|
|
|
242
243
|
skillConfigs.push(arg.split('=').slice(1).join('='));
|
|
243
244
|
} else if (arg.startsWith('--knowledge-config=')) {
|
|
244
245
|
knowledgeConfigs.push(arg.split('=').slice(1).join('='));
|
|
246
|
+
} else if (arg.startsWith('--from=')) {
|
|
247
|
+
fromFolder = arg.split('=').slice(1).join('=');
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
+
}
|
|
257
264
|
}
|
|
258
265
|
break;
|
|
259
266
|
case 'update':
|
|
@@ -368,6 +375,7 @@ async function main() {
|
|
|
368
375
|
arm agent info <name> 查看 Agent 详情
|
|
369
376
|
arm agent download <name> [dir] 下载 Agent
|
|
370
377
|
arm agent create <name> 创建 Agent (--description, --prompt, --avatar, --skill, --knowledge)
|
|
378
|
+
arm agent create --from=<folder> 从本地文件夹创建 Agent
|
|
371
379
|
arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
|
|
372
380
|
arm agent delete <id> 删除 Agent
|
|
373
381
|
arm agent bind <id> --skill=<id> 绑定 Skill 到 Agent
|