agent-resource-management 2.1.4 → 2.1.6

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 CHANGED
@@ -268,34 +268,42 @@ class ApiClient {
268
268
  throw new Error(res.msg);
269
269
  }
270
270
  }
271
- async bindSkillToAgent(agentId, skillId, config) {
271
+ async bindSkillToAgent(agentId, skillId, version, config) {
272
272
  const res = await this.request(`/agents/${agentId}/skills`, {
273
273
  method: "POST",
274
- body: JSON.stringify({ skillId, config })
274
+ body: JSON.stringify({ skillId, version, config })
275
275
  });
276
276
  if (!res.ok) {
277
277
  throw new Error(res.msg);
278
278
  }
279
279
  }
280
- async unbindSkillFromAgent(agentId, skillId) {
281
- const res = await this.request(`/agents/${agentId}/skills?skillId=${skillId}`, {
280
+ async unbindSkillFromAgent(agentId, skillId, version) {
281
+ let path = `/agents/${agentId}/skills?skillId=${skillId}`;
282
+ if (version) {
283
+ path += `&version=${version}`;
284
+ }
285
+ const res = await this.request(path, {
282
286
  method: "DELETE"
283
287
  });
284
288
  if (!res.ok) {
285
289
  throw new Error(res.msg);
286
290
  }
287
291
  }
288
- async bindKnowledgeToAgent(agentId, knowledgeId, retrievalConfig) {
292
+ async bindKnowledgeToAgent(agentId, knowledgeId, version, retrievalConfig) {
289
293
  const res = await this.request(`/agents/${agentId}/knowledges`, {
290
294
  method: "POST",
291
- body: JSON.stringify({ knowledgeId, retrievalConfig })
295
+ body: JSON.stringify({ knowledgeId, version, retrievalConfig })
292
296
  });
293
297
  if (!res.ok) {
294
298
  throw new Error(res.msg);
295
299
  }
296
300
  }
297
- async unbindKnowledgeFromAgent(agentId, knowledgeId) {
298
- const res = await this.request(`/agents/${agentId}/knowledges?knowledgeId=${knowledgeId}`, {
301
+ async unbindKnowledgeFromAgent(agentId, knowledgeId, version) {
302
+ let path = `/agents/${agentId}/knowledges?knowledgeId=${knowledgeId}`;
303
+ if (version) {
304
+ path += `&version=${version}`;
305
+ }
306
+ const res = await this.request(path, {
299
307
  method: "DELETE"
300
308
  });
301
309
  if (!res.ok) {
@@ -306,6 +314,13 @@ class ApiClient {
306
314
  const result = await this.listAgents(name, 1, 1);
307
315
  return result.agents.find((a) => a.name === name) || null;
308
316
  }
317
+ async getAgentBindingsHistory(agentId) {
318
+ const res = await this.request(`/agents/${agentId}/bindings/history`);
319
+ if (!res.ok) {
320
+ throw new Error(res.msg);
321
+ }
322
+ return res.data;
323
+ }
309
324
  }
310
325
 
311
326
  // src/lib/storage.ts
@@ -577,7 +592,7 @@ function setOutputMode(mode) {
577
592
  }
578
593
 
579
594
  // src/lib/validate.ts
580
- import { readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
595
+ import { readFileSync as readFileSync2, existsSync as existsSync2, statSync } from "fs";
581
596
  import { execSync } from "child_process";
582
597
  import { join as join2, extname } from "path";
583
598
  import { mkdtempSync, rmSync } from "fs";
@@ -597,7 +612,7 @@ function validateZip(filePath) {
597
612
  result.errors.push("文件必须是 ZIP 格式");
598
613
  return result;
599
614
  }
600
- const stats = statSync2(filePath);
615
+ const stats = statSync(filePath);
601
616
  if (stats.size === 0) {
602
617
  result.valid = false;
603
618
  result.errors.push("ZIP 文件为空");
@@ -809,9 +824,9 @@ function validateAgentDir(dirPath) {
809
824
  result.errors.push("AGENT.md 缺少 frontmatter (--- 包裹的 YAML)");
810
825
  }
811
826
  const skillsDir = join2(dirPath, "skills");
812
- if (existsSync2(skillsDir) && statSync2(skillsDir).isDirectory()) {
827
+ if (existsSync2(skillsDir) && statSync(skillsDir).isDirectory()) {
813
828
  const skillDirs = execSync(`ls -1 "${skillsDir}"`, { encoding: "utf-8" }).split(`
814
- `).filter((l) => l.trim() && existsSync2(join2(skillsDir, l)) && statSync2(join2(skillsDir, l)).isDirectory());
829
+ `).filter((l) => l.trim() && existsSync2(join2(skillsDir, l)) && statSync(join2(skillsDir, l)).isDirectory());
815
830
  for (const skillDir of skillDirs) {
816
831
  const skillMdPath = join2(skillsDir, skillDir, "SKILL.md");
817
832
  if (!existsSync2(skillMdPath)) {
@@ -820,7 +835,7 @@ function validateAgentDir(dirPath) {
820
835
  }
821
836
  }
822
837
  const knowledgesDir = join2(dirPath, "knowledges");
823
- if (existsSync2(knowledgesDir) && statSync2(knowledgesDir).isDirectory()) {
838
+ if (existsSync2(knowledgesDir) && statSync(knowledgesDir).isDirectory()) {
824
839
  const mdFiles = execSync(`ls -1 "${knowledgesDir}"`, { encoding: "utf-8" }).split(`
825
840
  `).filter((l) => l.trim().endsWith(".md"));
826
841
  if (mdFiles.length === 0) {
@@ -835,7 +850,7 @@ function validateAgentDir(dirPath) {
835
850
  }
836
851
 
837
852
  // src/cmd/skill.ts
838
- import { writeFileSync as writeFileSync2, existsSync as existsSync3, statSync as statSync3 } from "fs";
853
+ import { writeFileSync as writeFileSync2, existsSync as existsSync3, statSync as statSync2 } from "fs";
839
854
  import { join as join3, basename, dirname } from "path";
840
855
  import { execSync as execSync2 } from "child_process";
841
856
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
@@ -967,7 +982,7 @@ async function downloadSkill(name, outputDir) {
967
982
  const nonZipEntries = entries.filter((e) => e !== `${name}.zip`);
968
983
  if (nonZipEntries.length === 1) {
969
984
  const onlyEntry = join3(tempDir, nonZipEntries[0]);
970
- if (statSync3(onlyEntry).isDirectory()) {
985
+ if (statSync2(onlyEntry).isDirectory()) {
971
986
  execSync2(`cp -r "${onlyEntry}"/* "${targetDir}/"`, { stdio: "pipe" });
972
987
  } else {
973
988
  execSync2(`cp -r "${onlyEntry}" "${targetDir}/"`, { stdio: "pipe" });
@@ -1116,7 +1131,7 @@ async function deleteSkill(name) {
1116
1131
  }
1117
1132
  }
1118
1133
  async function validateSkill(filePath) {
1119
- const isDir = existsSync3(filePath) && statSync3(filePath).isDirectory();
1134
+ const isDir = existsSync3(filePath) && statSync2(filePath).isDirectory();
1120
1135
  const result = isDir ? validateSkillDir(filePath) : validateZip(filePath);
1121
1136
  if (shouldOutputJson()) {
1122
1137
  outputJson({
@@ -1172,7 +1187,7 @@ async function validateSkill(filePath) {
1172
1187
  }
1173
1188
 
1174
1189
  // src/cmd/agent.ts
1175
- import { writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
1190
+ import { writeFileSync as writeFileSync3, existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
1176
1191
  import { join as join4 } from "path";
1177
1192
  import { execSync as execSync3 } from "child_process";
1178
1193
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync3 } from "fs";
@@ -1283,7 +1298,7 @@ async function deleteAgent(id) {
1283
1298
  process.exit(1);
1284
1299
  }
1285
1300
  }
1286
- async function bindSkill(id, skillId, config) {
1301
+ async function bindSkill(id, skillId, version = "1.0.0", config) {
1287
1302
  const configStore = loadConfig();
1288
1303
  if (!configStore?.token) {
1289
1304
  if (shouldOutputJson()) {
@@ -1296,12 +1311,12 @@ async function bindSkill(id, skillId, config) {
1296
1311
  const client = new ApiClient(configStore.serverUrl, configStore.token);
1297
1312
  try {
1298
1313
  const parsedConfig = config ? JSON.parse(config) : undefined;
1299
- await client.bindSkillToAgent(id, skillId, parsedConfig);
1314
+ await client.bindSkillToAgent(id, skillId, version, parsedConfig);
1300
1315
  if (shouldOutputJson()) {
1301
- outputJson({ success: true, data: { agentId: id, skillId, config: parsedConfig } });
1316
+ outputJson({ success: true, data: { agentId: id, skillId, version, config: parsedConfig } });
1302
1317
  return;
1303
1318
  }
1304
- success(`Skill "${skillId}" 已绑定到 Agent "${id}"`);
1319
+ success(`Skill "${skillId}@${version}" 已绑定到 Agent "${id}"`);
1305
1320
  } catch (err) {
1306
1321
  if (shouldOutputJson()) {
1307
1322
  outputJson({ success: false, error: { code: "BIND_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
@@ -1311,7 +1326,7 @@ async function bindSkill(id, skillId, config) {
1311
1326
  process.exit(1);
1312
1327
  }
1313
1328
  }
1314
- async function unbindSkill(id, skillId) {
1329
+ async function unbindSkill(id, skillId, version) {
1315
1330
  const config = loadConfig();
1316
1331
  if (!config?.token) {
1317
1332
  if (shouldOutputJson()) {
@@ -1323,12 +1338,12 @@ async function unbindSkill(id, skillId) {
1323
1338
  }
1324
1339
  const client = new ApiClient(config.serverUrl, config.token);
1325
1340
  try {
1326
- await client.unbindSkillFromAgent(id, skillId);
1341
+ await client.unbindSkillFromAgent(id, skillId, version);
1327
1342
  if (shouldOutputJson()) {
1328
- outputJson({ success: true, data: { agentId: id, skillId } });
1343
+ outputJson({ success: true, data: { agentId: id, skillId, version } });
1329
1344
  return;
1330
1345
  }
1331
- success(`Skill "${skillId}" 已从 Agent "${id}" 解绑`);
1346
+ success(`Skill "${skillId}${version ? "@" + version : ""}" 已从 Agent "${id}" 解绑`);
1332
1347
  } catch (err) {
1333
1348
  if (shouldOutputJson()) {
1334
1349
  outputJson({ success: false, error: { code: "UNBIND_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
@@ -1338,7 +1353,7 @@ async function unbindSkill(id, skillId) {
1338
1353
  process.exit(1);
1339
1354
  }
1340
1355
  }
1341
- async function bindKnowledge(id, knowledgeId, retrievalConfig) {
1356
+ async function bindKnowledge(id, knowledgeId, version = "1.0.0", retrievalConfig) {
1342
1357
  const configStore = loadConfig();
1343
1358
  if (!configStore?.token) {
1344
1359
  if (shouldOutputJson()) {
@@ -1351,12 +1366,12 @@ async function bindKnowledge(id, knowledgeId, retrievalConfig) {
1351
1366
  const client = new ApiClient(configStore.serverUrl, configStore.token);
1352
1367
  try {
1353
1368
  const parsedConfig = retrievalConfig ? JSON.parse(retrievalConfig) : undefined;
1354
- await client.bindKnowledgeToAgent(id, knowledgeId, parsedConfig);
1369
+ await client.bindKnowledgeToAgent(id, knowledgeId, version, parsedConfig);
1355
1370
  if (shouldOutputJson()) {
1356
- outputJson({ success: true, data: { agentId: id, knowledgeId, retrievalConfig: parsedConfig } });
1371
+ outputJson({ success: true, data: { agentId: id, knowledgeId, version, retrievalConfig: parsedConfig } });
1357
1372
  return;
1358
1373
  }
1359
- success(`Knowledge "${knowledgeId}" 已绑定到 Agent "${id}"`);
1374
+ success(`Knowledge "${knowledgeId}@${version}" 已绑定到 Agent "${id}"`);
1360
1375
  } catch (err) {
1361
1376
  if (shouldOutputJson()) {
1362
1377
  outputJson({ success: false, error: { code: "BIND_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
@@ -1366,7 +1381,7 @@ async function bindKnowledge(id, knowledgeId, retrievalConfig) {
1366
1381
  process.exit(1);
1367
1382
  }
1368
1383
  }
1369
- async function unbindKnowledge(id, knowledgeId) {
1384
+ async function unbindKnowledge(id, knowledgeId, version) {
1370
1385
  const config = loadConfig();
1371
1386
  if (!config?.token) {
1372
1387
  if (shouldOutputJson()) {
@@ -1378,12 +1393,12 @@ async function unbindKnowledge(id, knowledgeId) {
1378
1393
  }
1379
1394
  const client = new ApiClient(config.serverUrl, config.token);
1380
1395
  try {
1381
- await client.unbindKnowledgeFromAgent(id, knowledgeId);
1396
+ await client.unbindKnowledgeFromAgent(id, knowledgeId, version);
1382
1397
  if (shouldOutputJson()) {
1383
- outputJson({ success: true, data: { agentId: id, knowledgeId } });
1398
+ outputJson({ success: true, data: { agentId: id, knowledgeId, version } });
1384
1399
  return;
1385
1400
  }
1386
- success(`Knowledge "${knowledgeId}" 已从 Agent "${id}" 解绑`);
1401
+ success(`Knowledge "${knowledgeId}${version ? "@" + version : ""}" 已从 Agent "${id}" 解绑`);
1387
1402
  } catch (err) {
1388
1403
  if (shouldOutputJson()) {
1389
1404
  outputJson({ success: false, error: { code: "UNBIND_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
@@ -1603,9 +1618,9 @@ async function createAgentFromFolder(folderPath) {
1603
1618
  const uploadedSkills = [];
1604
1619
  const uploadedKnowledges = [];
1605
1620
  const skillsDir = join4(folderPath, "skills");
1606
- if (existsSync4(skillsDir) && statSync(skillsDir).isDirectory()) {
1621
+ if (existsSync4(skillsDir) && statSync3(skillsDir).isDirectory()) {
1607
1622
  const skillDirs = execSync3(`ls -1 "${skillsDir}"`, { encoding: "utf-8" }).split(`
1608
- `).filter((l) => l.trim() && existsSync4(join4(skillsDir, l)) && statSync(join4(skillsDir, l)).isDirectory());
1623
+ `).filter((l) => l.trim() && existsSync4(join4(skillsDir, l)) && statSync3(join4(skillsDir, l)).isDirectory());
1609
1624
  for (const skillDir of skillDirs) {
1610
1625
  const skillPath = join4(skillsDir, skillDir);
1611
1626
  try {
@@ -1637,7 +1652,7 @@ async function createAgentFromFolder(folderPath) {
1637
1652
  }
1638
1653
  }
1639
1654
  const knowledgesDir = join4(folderPath, "knowledges");
1640
- if (existsSync4(knowledgesDir) && statSync(knowledgesDir).isDirectory()) {
1655
+ if (existsSync4(knowledgesDir) && statSync3(knowledgesDir).isDirectory()) {
1641
1656
  const mdFiles = execSync3(`ls -1 "${knowledgesDir}"`, { encoding: "utf-8" }).split(`
1642
1657
  `).filter((l) => l.trim().endsWith(".md"));
1643
1658
  for (const mdFile of mdFiles) {
@@ -1680,13 +1695,13 @@ async function createAgentFromFolder(folderPath) {
1680
1695
  if (shouldOutputJson()) {
1681
1696
  info(`绑定技能: ${skill.name}`);
1682
1697
  }
1683
- await client.bindSkillToAgent(agent.id, skill.id);
1698
+ await client.bindSkillToAgent(agent.id, skill.id, undefined);
1684
1699
  }
1685
1700
  for (const knowledge of uploadedKnowledges) {
1686
1701
  if (shouldOutputJson()) {
1687
1702
  info(`绑定知识: ${knowledge.title}`);
1688
1703
  }
1689
- await client.bindKnowledgeToAgent(agent.id, knowledge.id);
1704
+ await client.bindKnowledgeToAgent(agent.id, knowledge.id, undefined);
1690
1705
  }
1691
1706
  if (shouldOutputJson()) {
1692
1707
  outputJson({
@@ -1711,6 +1726,244 @@ async function createAgentFromFolder(folderPath) {
1711
1726
  process.exit(1);
1712
1727
  }
1713
1728
  }
1729
+ async function syncAgent(folderPath, options = {}) {
1730
+ const config = loadConfig();
1731
+ if (!config?.token) {
1732
+ if (shouldOutputJson()) {
1733
+ outputJson({ success: false, error: { code: "NOT_LOGGED_IN", message: "未登录,请先运行 arm login" } });
1734
+ process.exit(1);
1735
+ }
1736
+ error("未登录,请先运行 arm login");
1737
+ process.exit(1);
1738
+ }
1739
+ if (!existsSync4(folderPath)) {
1740
+ if (shouldOutputJson()) {
1741
+ outputJson({ success: false, error: { code: "FILE_NOT_FOUND", message: `目录不存在: ${folderPath}` } });
1742
+ process.exit(1);
1743
+ }
1744
+ error(`目录不存在: ${folderPath}`);
1745
+ process.exit(1);
1746
+ }
1747
+ const validation = validateAgentDir(folderPath);
1748
+ if (!validation.valid) {
1749
+ if (shouldOutputJson()) {
1750
+ outputJson({ success: false, error: { code: "VALIDATION_FAILED", message: validation.errors.join(", ") } });
1751
+ process.exit(1);
1752
+ }
1753
+ error(`验证失败: ${validation.errors.join(", ")}`);
1754
+ process.exit(1);
1755
+ }
1756
+ const metadata = validation.metadata;
1757
+ const agentName = metadata.name;
1758
+ const client = new ApiClient(config.serverUrl, config.token);
1759
+ if (shouldOutputJson()) {
1760
+ info(`正在同步 Agent: ${agentName}...`);
1761
+ }
1762
+ try {
1763
+ const result = await client.listAgents(agentName, 1, 1);
1764
+ const existingAgent = result.agents.find((a) => a.name === agentName);
1765
+ if (!existingAgent) {
1766
+ if (shouldOutputJson()) {
1767
+ outputJson({ success: false, error: { code: "NOT_FOUND", message: `Agent "${agentName}" 不存在,请先使用 arm agent create --from 创建` } });
1768
+ process.exit(1);
1769
+ }
1770
+ error(`Agent "${agentName}" 不存在,请先使用 arm agent create --from 创建`);
1771
+ process.exit(1);
1772
+ }
1773
+ const cloudAgent = await client.getAgent(existingAgent.id);
1774
+ const localSkills = await parseLocalSkills(folderPath);
1775
+ const localKnowledges = await parseLocalKnowledges(folderPath);
1776
+ const cloudSkillBindings = cloudAgent.skills || [];
1777
+ const cloudKnowledgeBindings = cloudAgent.knowledges || [];
1778
+ const changes = {
1779
+ metadataChanged: false,
1780
+ newSkills: [],
1781
+ removedSkills: [],
1782
+ newKnowledges: [],
1783
+ removedKnowledges: []
1784
+ };
1785
+ if (cloudAgent.prompt !== metadata.prompt || cloudAgent.description !== metadata.description) {
1786
+ changes.metadataChanged = true;
1787
+ }
1788
+ const cloudSkillNames = new Set(cloudSkillBindings.map((s) => s.skill?.name).filter(Boolean));
1789
+ for (const localSkill of localSkills) {
1790
+ const existingBinding = cloudSkillBindings.find((sb) => sb.skill?.name === localSkill.name && sb.version === localSkill.version);
1791
+ if (!existingBinding) {
1792
+ changes.newSkills.push(localSkill);
1793
+ }
1794
+ }
1795
+ const localSkillNames = new Set(localSkills.map((s) => s.name));
1796
+ for (const cloudBinding of cloudSkillBindings) {
1797
+ if (cloudBinding.skill?.name && !localSkillNames.has(cloudBinding.skill.name)) {
1798
+ changes.removedSkills.push(cloudBinding.skill.name);
1799
+ }
1800
+ }
1801
+ const cloudKnowledgeNames = new Set(cloudKnowledgeBindings.map((k) => k.knowledge?.name).filter(Boolean));
1802
+ for (const localKnowledge of localKnowledges) {
1803
+ const existingBinding = cloudKnowledgeBindings.find((kb) => kb.knowledge?.name === localKnowledge.name && kb.version === localKnowledge.version);
1804
+ if (!existingBinding) {
1805
+ changes.newKnowledges.push(localKnowledge);
1806
+ }
1807
+ }
1808
+ const localKnowledgeNames = new Set(localKnowledges.map((k) => k.name));
1809
+ for (const cloudBinding of cloudKnowledgeBindings) {
1810
+ if (cloudBinding.knowledge?.name && !localKnowledgeNames.has(cloudBinding.knowledge.name)) {
1811
+ changes.removedKnowledges.push(cloudBinding.knowledge.name);
1812
+ }
1813
+ }
1814
+ if (options.dryRun) {
1815
+ if (shouldOutputJson()) {
1816
+ outputJson({
1817
+ success: true,
1818
+ data: {
1819
+ agentName,
1820
+ agentId: existingAgent.id,
1821
+ changes,
1822
+ dryRun: true
1823
+ }
1824
+ });
1825
+ return;
1826
+ }
1827
+ console.log(`
1828
+ [DRY-RUN] 预览 ${agentName} 的变更:
1829
+ `);
1830
+ if (changes.metadataChanged) {
1831
+ console.log(" 元信息: 将更新 (prompt/description 变更)");
1832
+ }
1833
+ if (changes.newSkills.length > 0) {
1834
+ console.log(` 新增 Skills: ${changes.newSkills.map((s) => `${s.name}@${s.version}`).join(", ")}`);
1835
+ }
1836
+ if (changes.removedSkills.length > 0) {
1837
+ console.log(` 移除 Skills: ${changes.removedSkills.join(", ")}`);
1838
+ }
1839
+ if (changes.newKnowledges.length > 0) {
1840
+ console.log(` 新增 Knowledges: ${changes.newKnowledges.map((k) => `${k.name}@${k.version}`).join(", ")}`);
1841
+ }
1842
+ if (changes.removedKnowledges.length > 0) {
1843
+ console.log(` 移除 Knowledges: ${changes.removedKnowledges.join(", ")}`);
1844
+ }
1845
+ if (!changes.metadataChanged && changes.newSkills.length === 0 && changes.removedSkills.length === 0 && changes.newKnowledges.length === 0 && changes.removedKnowledges.length === 0) {
1846
+ console.log(" 无变更");
1847
+ }
1848
+ return;
1849
+ }
1850
+ if (changes.metadataChanged) {
1851
+ if (shouldOutputJson()) {
1852
+ info("更新 Agent 元信息...");
1853
+ }
1854
+ await client.updateAgent(existingAgent.id, {
1855
+ prompt: metadata.prompt,
1856
+ description: metadata.description
1857
+ });
1858
+ }
1859
+ for (const skill of changes.newSkills) {
1860
+ if (shouldOutputJson()) {
1861
+ info(`上传并绑定新技能: ${skill.name} (版本自动分配)`);
1862
+ }
1863
+ const skillTempDir = mkdtempSync3("/tmp/skill-sync-");
1864
+ const zipPath = join4(skillTempDir, `${skill.name}.zip`);
1865
+ execSync3(`cd "${skill.path}" && zip -r "${zipPath}" . -x ".*"`, { stdio: "pipe" });
1866
+ try {
1867
+ const uploadedSkill = await client.uploadSkill(zipPath);
1868
+ await client.bindSkillToAgent(existingAgent.id, uploadedSkill.id, undefined);
1869
+ } finally {
1870
+ rmSync3(skillTempDir, { recursive: true, force: true });
1871
+ }
1872
+ }
1873
+ for (const skillName of changes.removedSkills) {
1874
+ const binding = cloudSkillBindings.find((sb) => sb.skill?.name === skillName);
1875
+ if (binding) {
1876
+ if (shouldOutputJson()) {
1877
+ info(`解绑技能: ${skillName}`);
1878
+ }
1879
+ await client.unbindSkillFromAgent(existingAgent.id, binding.skillId, binding.version);
1880
+ }
1881
+ }
1882
+ for (const knowledge of changes.newKnowledges) {
1883
+ if (shouldOutputJson()) {
1884
+ info(`上传并绑定新知识: ${knowledge.name} (版本自动分配)`);
1885
+ }
1886
+ const uploadedKnowledge = await client.uploadKnowledge(knowledge.path);
1887
+ await client.bindKnowledgeToAgent(existingAgent.id, uploadedKnowledge.id, undefined);
1888
+ }
1889
+ for (const knowledgeName of changes.removedKnowledges) {
1890
+ const binding = cloudKnowledgeBindings.find((kb) => kb.knowledge?.name === knowledgeName);
1891
+ if (binding) {
1892
+ if (shouldOutputJson()) {
1893
+ info(`解绑知识: ${knowledgeName}`);
1894
+ }
1895
+ await client.unbindKnowledgeFromAgent(existingAgent.id, binding.knowledgeId, binding.version);
1896
+ }
1897
+ }
1898
+ if (shouldOutputJson()) {
1899
+ outputJson({
1900
+ success: true,
1901
+ data: {
1902
+ agentName,
1903
+ agentId: existingAgent.id,
1904
+ changes
1905
+ }
1906
+ });
1907
+ return;
1908
+ }
1909
+ success(`Agent "${agentName}" 同步完成`);
1910
+ const changeCount = (changes.metadataChanged ? 1 : 0) + changes.newSkills.length + changes.removedSkills.length + changes.newKnowledges.length + changes.removedKnowledges.length;
1911
+ if (changeCount === 0) {
1912
+ info("无变更");
1913
+ } else {
1914
+ console.log(` 更新了 ${changeCount} 项`);
1915
+ }
1916
+ } catch (err) {
1917
+ if (shouldOutputJson()) {
1918
+ outputJson({ success: false, error: { code: "SYNC_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
1919
+ process.exit(1);
1920
+ }
1921
+ error(`同步失败: ${err instanceof Error ? err.message : "未知错误"}`);
1922
+ process.exit(1);
1923
+ }
1924
+ }
1925
+ async function parseLocalSkills(folderPath) {
1926
+ const skills = [];
1927
+ const skillsDir = join4(folderPath, "skills");
1928
+ if (!existsSync4(skillsDir) || !statSync3(skillsDir).isDirectory()) {
1929
+ return skills;
1930
+ }
1931
+ try {
1932
+ const skillDirs = execSync3(`ls -1 "${skillsDir}"`, { encoding: "utf-8" }).split(`
1933
+ `).filter((l) => l.trim() && existsSync4(join4(skillsDir, l)) && statSync3(join4(skillsDir, l)).isDirectory());
1934
+ for (const skillDir of skillDirs) {
1935
+ const skillPath = join4(skillsDir, skillDir);
1936
+ const skillMdPath = join4(skillPath, "SKILL.md");
1937
+ if (existsSync4(skillMdPath)) {
1938
+ const content = readFileSync3(skillMdPath, "utf-8");
1939
+ const versionMatch = content.match(/^---\n[\s\S]*?version:\s*(.+)\n/);
1940
+ const version = versionMatch ? versionMatch[1].trim() : "1.0.0";
1941
+ skills.push({ name: skillDir, path: skillPath, version });
1942
+ }
1943
+ }
1944
+ } catch {}
1945
+ return skills;
1946
+ }
1947
+ async function parseLocalKnowledges(folderPath) {
1948
+ const knowledges = [];
1949
+ const knowledgesDir = join4(folderPath, "knowledges");
1950
+ if (!existsSync4(knowledgesDir) || !statSync3(knowledgesDir).isDirectory()) {
1951
+ return knowledges;
1952
+ }
1953
+ try {
1954
+ const mdFiles = execSync3(`ls -1 "${knowledgesDir}"`, { encoding: "utf-8" }).split(`
1955
+ `).filter((l) => l.trim().endsWith(".md"));
1956
+ for (const mdFile of mdFiles) {
1957
+ const mdPath = join4(knowledgesDir, mdFile);
1958
+ const name = mdFile.replace(".md", "");
1959
+ const content = readFileSync3(mdPath, "utf-8");
1960
+ const versionMatch = content.match(/^---\n[\s\S]*?version:\s*(.+)\n/);
1961
+ const version = versionMatch ? versionMatch[1].trim() : "1.0.0";
1962
+ knowledges.push({ name, path: mdPath, version });
1963
+ }
1964
+ } catch {}
1965
+ return knowledges;
1966
+ }
1714
1967
 
1715
1968
  // src/cmd/knowledge.ts
1716
1969
  import { writeFileSync as writeFileSync4, existsSync as existsSync5, statSync as statSync4 } from "fs";
@@ -2272,6 +2525,7 @@ async function main() {
2272
2525
  case "bind":
2273
2526
  if (!args[2]) {
2274
2527
  console.error("用法: arm agent bind <id> --skill=<skillId> [--skill-config='{...}'] 或 arm agent bind <id> --knowledge=<knowledgeId> [--knowledge-config='{...}'] [--json]");
2528
+ console.error("注意: version 缺省时自动绑定新版本");
2275
2529
  process.exit(1);
2276
2530
  }
2277
2531
  {
@@ -2293,9 +2547,9 @@ async function main() {
2293
2547
  }
2294
2548
  }
2295
2549
  if (skillId) {
2296
- await bindSkill(id, skillId, skillConfig);
2550
+ await bindSkill(id, skillId, undefined, skillConfig);
2297
2551
  } else if (knowledgeId) {
2298
- await bindKnowledge(id, knowledgeId, knowledgeConfig);
2552
+ await bindKnowledge(id, knowledgeId, undefined, knowledgeConfig);
2299
2553
  } else {
2300
2554
  console.error("用法: arm agent bind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>");
2301
2555
  process.exit(1);
@@ -2304,31 +2558,67 @@ async function main() {
2304
2558
  break;
2305
2559
  case "unbind":
2306
2560
  if (!args[2]) {
2307
- console.error("用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId> [--json]");
2561
+ console.error("用法: arm agent unbind <id> --skill=<skillId> [--version=<ver>] 或 --knowledge=<knowledgeId> [--version=<ver>] [--json]");
2308
2562
  process.exit(1);
2309
2563
  }
2310
2564
  {
2311
2565
  const id = args[2];
2312
2566
  let skillId;
2313
2567
  let knowledgeId;
2568
+ let skillVersion;
2569
+ let knowledgeVersion;
2314
2570
  for (let i = 3;i < args.length; i++) {
2315
2571
  const arg = args[i];
2316
2572
  if (arg.startsWith("--skill=")) {
2317
2573
  skillId = arg.split("=").slice(1).join("=");
2318
2574
  } else if (arg.startsWith("--knowledge=")) {
2319
2575
  knowledgeId = arg.split("=").slice(1).join("=");
2576
+ } else if (arg.startsWith("--version=")) {
2577
+ const ver = arg.split("=").slice(1).join("=");
2578
+ if (skillId)
2579
+ skillVersion = ver;
2580
+ if (knowledgeId)
2581
+ knowledgeVersion = ver;
2320
2582
  }
2321
2583
  }
2322
2584
  if (skillId) {
2323
- await unbindSkill(id, skillId);
2585
+ await unbindSkill(id, skillId, skillVersion);
2324
2586
  } else if (knowledgeId) {
2325
- await unbindKnowledge(id, knowledgeId);
2587
+ await unbindKnowledge(id, knowledgeId, knowledgeVersion);
2326
2588
  } else {
2327
2589
  console.error("用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>");
2328
2590
  process.exit(1);
2329
2591
  }
2330
2592
  }
2331
2593
  break;
2594
+ case "sync":
2595
+ if (!args[2]) {
2596
+ console.error("用法: arm agent sync <folder> [--dry-run] [--force] [--json]");
2597
+ process.exit(1);
2598
+ }
2599
+ {
2600
+ const folder = args[2];
2601
+ const syncOptions = {};
2602
+ for (let i = 3;i < args.length; i++) {
2603
+ const arg = args[i];
2604
+ if (arg === "--dry-run") {
2605
+ syncOptions.dryRun = true;
2606
+ } else if (arg === "--force") {
2607
+ syncOptions.force = true;
2608
+ }
2609
+ }
2610
+ await syncAgent(folder, syncOptions);
2611
+ }
2612
+ break;
2613
+ case "bindings":
2614
+ if (!args[2]) {
2615
+ console.error("用法: arm agent bindings <name> [--history] [--json]");
2616
+ process.exit(1);
2617
+ }
2618
+ if (subCommand === "history") {
2619
+ console.log("bindings history 需要通过 info 命令查看");
2620
+ }
2621
+ break;
2332
2622
  default:
2333
2623
  console.log(`
2334
2624
  可用命令:
@@ -2340,10 +2630,11 @@ async function main() {
2340
2630
  arm agent create --from=<folder> 从本地文件夹创建 Agent
2341
2631
  arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
2342
2632
  arm agent delete <id> 删除 Agent
2343
- arm agent bind <id> --skill=<id> 绑定 Skill 到 Agent
2344
- arm agent unbind <id> --skill=<id> 解绑 Skill
2345
- arm agent bind <id> --knowledge=<id> 绑定 Knowledge 到 Agent
2346
- arm agent unbind <id> --knowledge=<id> 解绑 Knowledge
2633
+ arm agent bind <id> --skill=<id> [--version=<ver>] 绑定 Skill 到 Agent
2634
+ arm agent unbind <id> --skill=<id> [--version=<ver>] 解绑 Skill
2635
+ arm agent bind <id> --knowledge=<id> [--version=<ver>] 绑定 Knowledge 到 Agent
2636
+ arm agent unbind <id> --knowledge=<id> [--version=<ver>] 解绑 Knowledge
2637
+ arm agent sync <folder> [--dry-run] 同步本地文件夹到云端 Agent
2347
2638
  所有命令支持 --json 参数获取机器可读输出
2348
2639
  `);
2349
2640
  }
@@ -2377,12 +2668,14 @@ Agent Resource Management (arm)
2377
2668
  arm agent info <name> 查看 Agent 详情
2378
2669
  arm agent download <name> [dir] 下载 Agent
2379
2670
  arm agent create <name> 创建 Agent
2671
+ arm agent create --from=<folder> 从本地文件夹创建 Agent
2380
2672
  arm agent update <id> 更新 Agent
2381
2673
  arm agent delete <id> 删除 Agent
2382
- arm agent bind <id> --skill=<id> 绑定 Skill
2383
- arm agent unbind <id> --skill=<id> 解绑 Skill
2384
- arm agent bind <id> --knowledge=<id> 绑定 Knowledge
2385
- arm agent unbind <id> --knowledge=<id> 解绑 Knowledge
2674
+ arm agent bind <id> --skill=<id> [--version=<ver>] 绑定 Skill
2675
+ arm agent unbind <id> --skill=<id> [--version=<ver>] 解绑 Skill
2676
+ arm agent bind <id> --knowledge=<id> [--version=<ver>] 绑定 Knowledge
2677
+ arm agent unbind <id> --knowledge=<id> [--version=<ver>] 解绑 Knowledge
2678
+ arm agent sync <folder> [--dry-run] 同步本地文件夹到云端 Agent
2386
2679
  arm server 显示当前服务端
2387
2680
  arm server set <url> 设置服务端
2388
2681
  使用 arm <entity> -h 查看详细帮助
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-resource-management",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "arm": "./dist/main.js"
package/src/cmd/agent.ts CHANGED
@@ -3,7 +3,7 @@ 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
5
  import { validateAgentDir } from '../lib/validate';
6
- import { writeFileSync, existsSync, readFileSync } from 'fs';
6
+ import { writeFileSync, existsSync, readFileSync, statSync } from 'fs';
7
7
  import { join, basename } from 'path';
8
8
  import { execSync } from 'child_process';
9
9
  import { mkdtempSync, rmSync } from 'fs';
@@ -156,7 +156,7 @@ export async function deleteAgent(id: string): Promise<void> {
156
156
  }
157
157
  }
158
158
 
159
- export async function bindSkill(id: string, skillId: string, config?: string): Promise<void> {
159
+ export async function bindSkill(id: string, skillId: string, version: string = '1.0.0', config?: string): Promise<void> {
160
160
  const configStore = loadConfig();
161
161
  if (!configStore?.token) {
162
162
  if (shouldOutputJson()) {
@@ -170,13 +170,13 @@ export async function bindSkill(id: string, skillId: string, config?: string): P
170
170
  const client = new ApiClient(configStore.serverUrl, configStore.token);
171
171
  try {
172
172
  const parsedConfig = config ? JSON.parse(config) : undefined;
173
- await client.bindSkillToAgent(id, skillId, parsedConfig);
173
+ await client.bindSkillToAgent(id, skillId, version, parsedConfig);
174
174
 
175
175
  if (shouldOutputJson()) {
176
- outputJson({ success: true, data: { agentId: id, skillId, config: parsedConfig } });
176
+ outputJson({ success: true, data: { agentId: id, skillId, version, config: parsedConfig } });
177
177
  return;
178
178
  }
179
- success(`Skill "${skillId}" 已绑定到 Agent "${id}"`);
179
+ success(`Skill "${skillId}@${version}" 已绑定到 Agent "${id}"`);
180
180
  } catch (err) {
181
181
  if (shouldOutputJson()) {
182
182
  outputJson({ success: false, error: { code: 'BIND_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
@@ -187,7 +187,7 @@ export async function bindSkill(id: string, skillId: string, config?: string): P
187
187
  }
188
188
  }
189
189
 
190
- export async function unbindSkill(id: string, skillId: string): Promise<void> {
190
+ export async function unbindSkill(id: string, skillId: string, version?: string): Promise<void> {
191
191
  const config = loadConfig();
192
192
  if (!config?.token) {
193
193
  if (shouldOutputJson()) {
@@ -200,13 +200,13 @@ export async function unbindSkill(id: string, skillId: string): Promise<void> {
200
200
 
201
201
  const client = new ApiClient(config.serverUrl, config.token);
202
202
  try {
203
- await client.unbindSkillFromAgent(id, skillId);
203
+ await client.unbindSkillFromAgent(id, skillId, version);
204
204
 
205
205
  if (shouldOutputJson()) {
206
- outputJson({ success: true, data: { agentId: id, skillId } });
206
+ outputJson({ success: true, data: { agentId: id, skillId, version } });
207
207
  return;
208
208
  }
209
- success(`Skill "${skillId}" 已从 Agent "${id}" 解绑`);
209
+ success(`Skill "${skillId}${version ? '@' + version : ''}" 已从 Agent "${id}" 解绑`);
210
210
  } catch (err) {
211
211
  if (shouldOutputJson()) {
212
212
  outputJson({ success: false, error: { code: 'UNBIND_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
@@ -217,7 +217,7 @@ export async function unbindSkill(id: string, skillId: string): Promise<void> {
217
217
  }
218
218
  }
219
219
 
220
- export async function bindKnowledge(id: string, knowledgeId: string, retrievalConfig?: string): Promise<void> {
220
+ export async function bindKnowledge(id: string, knowledgeId: string, version: string = '1.0.0', retrievalConfig?: string): Promise<void> {
221
221
  const configStore = loadConfig();
222
222
  if (!configStore?.token) {
223
223
  if (shouldOutputJson()) {
@@ -231,13 +231,13 @@ export async function bindKnowledge(id: string, knowledgeId: string, retrievalCo
231
231
  const client = new ApiClient(configStore.serverUrl, configStore.token);
232
232
  try {
233
233
  const parsedConfig = retrievalConfig ? JSON.parse(retrievalConfig) : undefined;
234
- await client.bindKnowledgeToAgent(id, knowledgeId, parsedConfig);
234
+ await client.bindKnowledgeToAgent(id, knowledgeId, version, parsedConfig);
235
235
 
236
236
  if (shouldOutputJson()) {
237
- outputJson({ success: true, data: { agentId: id, knowledgeId, retrievalConfig: parsedConfig } });
237
+ outputJson({ success: true, data: { agentId: id, knowledgeId, version, retrievalConfig: parsedConfig } });
238
238
  return;
239
239
  }
240
- success(`Knowledge "${knowledgeId}" 已绑定到 Agent "${id}"`);
240
+ success(`Knowledge "${knowledgeId}@${version}" 已绑定到 Agent "${id}"`);
241
241
  } catch (err) {
242
242
  if (shouldOutputJson()) {
243
243
  outputJson({ success: false, error: { code: 'BIND_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
@@ -248,7 +248,7 @@ export async function bindKnowledge(id: string, knowledgeId: string, retrievalCo
248
248
  }
249
249
  }
250
250
 
251
- export async function unbindKnowledge(id: string, knowledgeId: string): Promise<void> {
251
+ export async function unbindKnowledge(id: string, knowledgeId: string, version?: string): Promise<void> {
252
252
  const config = loadConfig();
253
253
  if (!config?.token) {
254
254
  if (shouldOutputJson()) {
@@ -261,13 +261,13 @@ export async function unbindKnowledge(id: string, knowledgeId: string): Promise<
261
261
 
262
262
  const client = new ApiClient(config.serverUrl, config.token);
263
263
  try {
264
- await client.unbindKnowledgeFromAgent(id, knowledgeId);
264
+ await client.unbindKnowledgeFromAgent(id, knowledgeId, version);
265
265
 
266
266
  if (shouldOutputJson()) {
267
- outputJson({ success: true, data: { agentId: id, knowledgeId } });
267
+ outputJson({ success: true, data: { agentId: id, knowledgeId, version } });
268
268
  return;
269
269
  }
270
- success(`Knowledge "${knowledgeId}" 已从 Agent "${id}" 解绑`);
270
+ success(`Knowledge "${knowledgeId}${version ? '@' + version : ''}" 已从 Agent "${id}" 解绑`);
271
271
  } catch (err) {
272
272
  if (shouldOutputJson()) {
273
273
  outputJson({ success: false, error: { code: 'UNBIND_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
@@ -592,14 +592,14 @@ export async function createAgentFromFolder(folderPath: string): Promise<void> {
592
592
  if (shouldOutputJson()) {
593
593
  info(`绑定技能: ${skill.name}`);
594
594
  }
595
- await client.bindSkillToAgent(agent.id, skill.id);
595
+ await client.bindSkillToAgent(agent.id, skill.id, undefined);
596
596
  }
597
597
 
598
598
  for (const knowledge of uploadedKnowledges) {
599
599
  if (shouldOutputJson()) {
600
600
  info(`绑定知识: ${knowledge.title}`);
601
601
  }
602
- await client.bindKnowledgeToAgent(agent.id, knowledge.id);
602
+ await client.bindKnowledgeToAgent(agent.id, knowledge.id, undefined);
603
603
  }
604
604
 
605
605
  if (shouldOutputJson()) {
@@ -624,4 +624,305 @@ export async function createAgentFromFolder(folderPath: string): Promise<void> {
624
624
  error(`创建失败: ${err instanceof Error ? err.message : '未知错误'}`);
625
625
  process.exit(1);
626
626
  }
627
+ }
628
+
629
+ export interface SyncOptions {
630
+ dryRun?: boolean;
631
+ force?: boolean;
632
+ }
633
+
634
+ interface LocalSkill {
635
+ name: string;
636
+ path: string;
637
+ version: string;
638
+ }
639
+
640
+ interface LocalKnowledge {
641
+ name: string;
642
+ path: string;
643
+ version: string;
644
+ }
645
+
646
+ export async function syncAgent(folderPath: string, options: SyncOptions = {}): Promise<void> {
647
+ const config = loadConfig();
648
+ if (!config?.token) {
649
+ if (shouldOutputJson()) {
650
+ outputJson({ success: false, error: { code: 'NOT_LOGGED_IN', message: '未登录,请先运行 arm login' } });
651
+ process.exit(1);
652
+ }
653
+ error('未登录,请先运行 arm login');
654
+ process.exit(1);
655
+ }
656
+
657
+ if (!existsSync(folderPath)) {
658
+ if (shouldOutputJson()) {
659
+ outputJson({ success: false, error: { code: 'FILE_NOT_FOUND', message: `目录不存在: ${folderPath}` } });
660
+ process.exit(1);
661
+ }
662
+ error(`目录不存在: ${folderPath}`);
663
+ process.exit(1);
664
+ }
665
+
666
+ const validation = validateAgentDir(folderPath);
667
+ if (!validation.valid) {
668
+ if (shouldOutputJson()) {
669
+ outputJson({ success: false, error: { code: 'VALIDATION_FAILED', message: validation.errors.join(', ') } });
670
+ process.exit(1);
671
+ }
672
+ error(`验证失败: ${validation.errors.join(', ')}`);
673
+ process.exit(1);
674
+ }
675
+
676
+ const metadata = validation.metadata!;
677
+ const agentName = metadata.name!;
678
+ const client = new ApiClient(config.serverUrl, config.token);
679
+
680
+ if (shouldOutputJson()) {
681
+ info(`正在同步 Agent: ${agentName}...`);
682
+ }
683
+
684
+ try {
685
+ const result = await client.listAgents(agentName, 1, 1);
686
+ const existingAgent = result.agents.find(a => a.name === agentName);
687
+
688
+ if (!existingAgent) {
689
+ if (shouldOutputJson()) {
690
+ outputJson({ success: false, error: { code: 'NOT_FOUND', message: `Agent "${agentName}" 不存在,请先使用 arm agent create --from 创建` } });
691
+ process.exit(1);
692
+ }
693
+ error(`Agent "${agentName}" 不存在,请先使用 arm agent create --from 创建`);
694
+ process.exit(1);
695
+ }
696
+
697
+ const cloudAgent = await client.getAgent(existingAgent.id);
698
+
699
+ const localSkills = await parseLocalSkills(folderPath);
700
+ const localKnowledges = await parseLocalKnowledges(folderPath);
701
+
702
+ const cloudSkillBindings = cloudAgent.skills || [];
703
+ const cloudKnowledgeBindings = cloudAgent.knowledges || [];
704
+
705
+ const changes: {
706
+ metadataChanged: boolean;
707
+ newSkills: LocalSkill[];
708
+ removedSkills: string[];
709
+ newKnowledges: LocalKnowledge[];
710
+ removedKnowledges: string[];
711
+ } = {
712
+ metadataChanged: false,
713
+ newSkills: [],
714
+ removedSkills: [],
715
+ newKnowledges: [],
716
+ removedKnowledges: [],
717
+ };
718
+
719
+ if (cloudAgent.prompt !== metadata.prompt || cloudAgent.description !== metadata.description) {
720
+ changes.metadataChanged = true;
721
+ }
722
+
723
+ const cloudSkillNames = new Set(cloudSkillBindings.map(s => s.skill?.name).filter(Boolean));
724
+ for (const localSkill of localSkills) {
725
+ const existingBinding = cloudSkillBindings.find(
726
+ sb => sb.skill?.name === localSkill.name && sb.version === localSkill.version
727
+ );
728
+ if (!existingBinding) {
729
+ changes.newSkills.push(localSkill);
730
+ }
731
+ }
732
+ const localSkillNames = new Set(localSkills.map(s => s.name));
733
+ for (const cloudBinding of cloudSkillBindings) {
734
+ if (cloudBinding.skill?.name && !localSkillNames.has(cloudBinding.skill.name)) {
735
+ changes.removedSkills.push(cloudBinding.skill.name);
736
+ }
737
+ }
738
+
739
+ const cloudKnowledgeNames = new Set(cloudKnowledgeBindings.map(k => k.knowledge?.name).filter(Boolean));
740
+ for (const localKnowledge of localKnowledges) {
741
+ const existingBinding = cloudKnowledgeBindings.find(
742
+ kb => kb.knowledge?.name === localKnowledge.name && kb.version === localKnowledge.version
743
+ );
744
+ if (!existingBinding) {
745
+ changes.newKnowledges.push(localKnowledge);
746
+ }
747
+ }
748
+ const localKnowledgeNames = new Set(localKnowledges.map(k => k.name));
749
+ for (const cloudBinding of cloudKnowledgeBindings) {
750
+ if (cloudBinding.knowledge?.name && !localKnowledgeNames.has(cloudBinding.knowledge.name)) {
751
+ changes.removedKnowledges.push(cloudBinding.knowledge.name);
752
+ }
753
+ }
754
+
755
+ if (options.dryRun) {
756
+ if (shouldOutputJson()) {
757
+ outputJson({
758
+ success: true,
759
+ data: {
760
+ agentName,
761
+ agentId: existingAgent.id,
762
+ changes,
763
+ dryRun: true,
764
+ },
765
+ });
766
+ return;
767
+ }
768
+ console.log(`\n[DRY-RUN] 预览 ${agentName} 的变更:\n`);
769
+ if (changes.metadataChanged) {
770
+ console.log(' 元信息: 将更新 (prompt/description 变更)');
771
+ }
772
+ if (changes.newSkills.length > 0) {
773
+ console.log(` 新增 Skills: ${changes.newSkills.map(s => `${s.name}@${s.version}`).join(', ')}`);
774
+ }
775
+ if (changes.removedSkills.length > 0) {
776
+ console.log(` 移除 Skills: ${changes.removedSkills.join(', ')}`);
777
+ }
778
+ if (changes.newKnowledges.length > 0) {
779
+ console.log(` 新增 Knowledges: ${changes.newKnowledges.map(k => `${k.name}@${k.version}`).join(', ')}`);
780
+ }
781
+ if (changes.removedKnowledges.length > 0) {
782
+ console.log(` 移除 Knowledges: ${changes.removedKnowledges.join(', ')}`);
783
+ }
784
+ if (!changes.metadataChanged && changes.newSkills.length === 0 && changes.removedSkills.length === 0 &&
785
+ changes.newKnowledges.length === 0 && changes.removedKnowledges.length === 0) {
786
+ console.log(' 无变更');
787
+ }
788
+ return;
789
+ }
790
+
791
+ if (changes.metadataChanged) {
792
+ if (shouldOutputJson()) {
793
+ info('更新 Agent 元信息...');
794
+ }
795
+ await client.updateAgent(existingAgent.id, {
796
+ prompt: metadata.prompt,
797
+ description: metadata.description,
798
+ });
799
+ }
800
+
801
+ for (const skill of changes.newSkills) {
802
+ if (shouldOutputJson()) {
803
+ info(`上传并绑定新技能: ${skill.name} (版本自动分配)`);
804
+ }
805
+ const skillTempDir = mkdtempSync('/tmp/skill-sync-');
806
+ const zipPath = join(skillTempDir, `${skill.name}.zip`);
807
+ execSync(`cd "${skill.path}" && zip -r "${zipPath}" . -x ".*"`, { stdio: 'pipe' });
808
+ try {
809
+ const uploadedSkill = await client.uploadSkill(zipPath);
810
+ await client.bindSkillToAgent(existingAgent.id, uploadedSkill.id, undefined);
811
+ } finally {
812
+ rmSync(skillTempDir, { recursive: true, force: true });
813
+ }
814
+ }
815
+
816
+ for (const skillName of changes.removedSkills) {
817
+ const binding = cloudSkillBindings.find(sb => sb.skill?.name === skillName);
818
+ if (binding) {
819
+ if (shouldOutputJson()) {
820
+ info(`解绑技能: ${skillName}`);
821
+ }
822
+ await client.unbindSkillFromAgent(existingAgent.id, binding.skillId, binding.version);
823
+ }
824
+ }
825
+
826
+ for (const knowledge of changes.newKnowledges) {
827
+ if (shouldOutputJson()) {
828
+ info(`上传并绑定新知识: ${knowledge.name} (版本自动分配)`);
829
+ }
830
+ const uploadedKnowledge = await client.uploadKnowledge(knowledge.path);
831
+ await client.bindKnowledgeToAgent(existingAgent.id, uploadedKnowledge.id, undefined);
832
+ }
833
+
834
+ for (const knowledgeName of changes.removedKnowledges) {
835
+ const binding = cloudKnowledgeBindings.find(kb => kb.knowledge?.name === knowledgeName);
836
+ if (binding) {
837
+ if (shouldOutputJson()) {
838
+ info(`解绑知识: ${knowledgeName}`);
839
+ }
840
+ await client.unbindKnowledgeFromAgent(existingAgent.id, binding.knowledgeId, binding.version);
841
+ }
842
+ }
843
+
844
+ if (shouldOutputJson()) {
845
+ outputJson({
846
+ success: true,
847
+ data: {
848
+ agentName,
849
+ agentId: existingAgent.id,
850
+ changes,
851
+ },
852
+ });
853
+ return;
854
+ }
855
+ success(`Agent "${agentName}" 同步完成`);
856
+ const changeCount = (changes.metadataChanged ? 1 : 0) + changes.newSkills.length +
857
+ changes.removedSkills.length + changes.newKnowledges.length + changes.removedKnowledges.length;
858
+ if (changeCount === 0) {
859
+ info('无变更');
860
+ } else {
861
+ console.log(` 更新了 ${changeCount} 项`);
862
+ }
863
+ } catch (err) {
864
+ if (shouldOutputJson()) {
865
+ outputJson({ success: false, error: { code: 'SYNC_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
866
+ process.exit(1);
867
+ }
868
+ error(`同步失败: ${err instanceof Error ? err.message : '未知错误'}`);
869
+ process.exit(1);
870
+ }
871
+ }
872
+
873
+ async function parseLocalSkills(folderPath: string): Promise<LocalSkill[]> {
874
+ const skills: LocalSkill[] = [];
875
+ const skillsDir = join(folderPath, 'skills');
876
+
877
+ if (!existsSync(skillsDir) || !statSync(skillsDir).isDirectory()) {
878
+ return skills;
879
+ }
880
+
881
+ try {
882
+ const skillDirs = execSync(`ls -1 "${skillsDir}"`, { encoding: 'utf-8' })
883
+ .split('\n')
884
+ .filter(l => l.trim() && existsSync(join(skillsDir, l)) && statSync(join(skillsDir, l)).isDirectory());
885
+
886
+ for (const skillDir of skillDirs) {
887
+ const skillPath = join(skillsDir, skillDir);
888
+ const skillMdPath = join(skillPath, 'SKILL.md');
889
+
890
+ if (existsSync(skillMdPath)) {
891
+ const content = readFileSync(skillMdPath, 'utf-8');
892
+ const versionMatch = content.match(/^---\n[\s\S]*?version:\s*(.+)\n/);
893
+ const version = versionMatch ? versionMatch[1].trim() : '1.0.0';
894
+ skills.push({ name: skillDir, path: skillPath, version });
895
+ }
896
+ }
897
+ } catch {
898
+ }
899
+
900
+ return skills;
901
+ }
902
+
903
+ async function parseLocalKnowledges(folderPath: string): Promise<LocalKnowledge[]> {
904
+ const knowledges: LocalKnowledge[] = [];
905
+ const knowledgesDir = join(folderPath, 'knowledges');
906
+
907
+ if (!existsSync(knowledgesDir) || !statSync(knowledgesDir).isDirectory()) {
908
+ return knowledges;
909
+ }
910
+
911
+ try {
912
+ const mdFiles = execSync(`ls -1 "${knowledgesDir}"`, { encoding: 'utf-8' })
913
+ .split('\n')
914
+ .filter(l => l.trim().endsWith('.md'));
915
+
916
+ for (const mdFile of mdFiles) {
917
+ const mdPath = join(knowledgesDir, mdFile);
918
+ const name = mdFile.replace('.md', '');
919
+ const content = readFileSync(mdPath, 'utf-8');
920
+ const versionMatch = content.match(/^---\n[\s\S]*?version:\s*(.+)\n/);
921
+ const version = versionMatch ? versionMatch[1].trim() : '1.0.0';
922
+ knowledges.push({ name, path: mdPath, version });
923
+ }
924
+ } catch {
925
+ }
926
+
927
+ return knowledges;
627
928
  }
package/src/lib/client.ts CHANGED
@@ -327,18 +327,22 @@ export class ApiClient {
327
327
  }
328
328
  }
329
329
 
330
- async bindSkillToAgent(agentId: string, skillId: string, config?: Record<string, unknown>): Promise<void> {
330
+ async bindSkillToAgent(agentId: string, skillId: string, version: string, config?: Record<string, unknown>): Promise<void> {
331
331
  const res = await this.request<null>(`/agents/${agentId}/skills`, {
332
332
  method: 'POST',
333
- body: JSON.stringify({ skillId, config }),
333
+ body: JSON.stringify({ skillId, version, config }),
334
334
  });
335
335
  if (!res.ok) {
336
336
  throw new Error(res.msg);
337
337
  }
338
338
  }
339
339
 
340
- async unbindSkillFromAgent(agentId: string, skillId: string): Promise<void> {
341
- const res = await this.request<null>(`/agents/${agentId}/skills?skillId=${skillId}`, {
340
+ async unbindSkillFromAgent(agentId: string, skillId: string, version?: string): Promise<void> {
341
+ let path = `/agents/${agentId}/skills?skillId=${skillId}`;
342
+ if (version) {
343
+ path += `&version=${version}`;
344
+ }
345
+ const res = await this.request<null>(path, {
342
346
  method: 'DELETE',
343
347
  });
344
348
  if (!res.ok) {
@@ -346,18 +350,22 @@ export class ApiClient {
346
350
  }
347
351
  }
348
352
 
349
- async bindKnowledgeToAgent(agentId: string, knowledgeId: string, retrievalConfig?: { topK?: number; similarityThreshold?: number }): Promise<void> {
353
+ async bindKnowledgeToAgent(agentId: string, knowledgeId: string, version: string, retrievalConfig?: { topK?: number; similarityThreshold?: number }): Promise<void> {
350
354
  const res = await this.request<null>(`/agents/${agentId}/knowledges`, {
351
355
  method: 'POST',
352
- body: JSON.stringify({ knowledgeId, retrievalConfig }),
356
+ body: JSON.stringify({ knowledgeId, version, retrievalConfig }),
353
357
  });
354
358
  if (!res.ok) {
355
359
  throw new Error(res.msg);
356
360
  }
357
361
  }
358
362
 
359
- async unbindKnowledgeFromAgent(agentId: string, knowledgeId: string): Promise<void> {
360
- const res = await this.request<null>(`/agents/${agentId}/knowledges?knowledgeId=${knowledgeId}`, {
363
+ async unbindKnowledgeFromAgent(agentId: string, knowledgeId: string, version?: string): Promise<void> {
364
+ let path = `/agents/${agentId}/knowledges?knowledgeId=${knowledgeId}`;
365
+ if (version) {
366
+ path += `&version=${version}`;
367
+ }
368
+ const res = await this.request<null>(path, {
361
369
  method: 'DELETE',
362
370
  });
363
371
  if (!res.ok) {
@@ -369,4 +377,31 @@ export class ApiClient {
369
377
  const result = await this.listAgents(name, 1, 1);
370
378
  return result.agents.find(a => a.name === name) || null;
371
379
  }
380
+
381
+ async getAgentBindingsHistory(agentId: string): Promise<{
382
+ skillBindings: Array<{
383
+ id: string;
384
+ skillId: string;
385
+ skillName: string;
386
+ version: string;
387
+ config: Record<string, unknown> | null;
388
+ createdAt: string;
389
+ deletedAt: string | null;
390
+ }>;
391
+ knowledgeBindings: Array<{
392
+ id: string;
393
+ knowledgeId: string;
394
+ knowledgeName: string;
395
+ version: string;
396
+ retrievalConfig: Record<string, unknown> | null;
397
+ createdAt: string;
398
+ deletedAt: string | null;
399
+ }>;
400
+ }> {
401
+ const res = await this.request<any>(`/agents/${agentId}/bindings/history`);
402
+ if (!res.ok) {
403
+ throw new Error(res.msg);
404
+ }
405
+ return res.data;
406
+ }
372
407
  }
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, createAgentFromFolder } from './cmd/agent';
3
+ import { listAgents, searchAgents, infoAgent, downloadAgent, createAgent, updateAgent, deleteAgent, bindSkill, unbindSkill, bindKnowledge, unbindKnowledge, createAgentFromFolder, syncAgent } 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';
@@ -316,6 +316,7 @@ async function main() {
316
316
  case 'bind':
317
317
  if (!args[2]) {
318
318
  console.error('用法: arm agent bind <id> --skill=<skillId> [--skill-config=\'{...}\'] 或 arm agent bind <id> --knowledge=<knowledgeId> [--knowledge-config=\'{...}\'] [--json]');
319
+ console.error('注意: version 缺省时自动绑定新版本');
319
320
  process.exit(1);
320
321
  }
321
322
  {
@@ -339,9 +340,9 @@ async function main() {
339
340
  }
340
341
 
341
342
  if (skillId) {
342
- await bindSkill(id, skillId, skillConfig);
343
+ await bindSkill(id, skillId, undefined, skillConfig);
343
344
  } else if (knowledgeId) {
344
- await bindKnowledge(id, knowledgeId, knowledgeConfig);
345
+ await bindKnowledge(id, knowledgeId, undefined, knowledgeConfig);
345
346
  } else {
346
347
  console.error('用法: arm agent bind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>');
347
348
  process.exit(1);
@@ -350,13 +351,15 @@ async function main() {
350
351
  break;
351
352
  case 'unbind':
352
353
  if (!args[2]) {
353
- console.error('用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId> [--json]');
354
+ console.error('用法: arm agent unbind <id> --skill=<skillId> [--version=<ver>] 或 --knowledge=<knowledgeId> [--version=<ver>] [--json]');
354
355
  process.exit(1);
355
356
  }
356
357
  {
357
358
  const id = args[2];
358
359
  let skillId: string | undefined;
359
360
  let knowledgeId: string | undefined;
361
+ let skillVersion: string | undefined;
362
+ let knowledgeVersion: string | undefined;
360
363
 
361
364
  for (let i = 3; i < args.length; i++) {
362
365
  const arg = args[i];
@@ -364,19 +367,53 @@ async function main() {
364
367
  skillId = arg.split('=').slice(1).join('=');
365
368
  } else if (arg.startsWith('--knowledge=')) {
366
369
  knowledgeId = arg.split('=').slice(1).join('=');
370
+ } else if (arg.startsWith('--version=')) {
371
+ const ver = arg.split('=').slice(1).join('=');
372
+ if (skillId) skillVersion = ver;
373
+ if (knowledgeId) knowledgeVersion = ver;
367
374
  }
368
375
  }
369
376
 
370
377
  if (skillId) {
371
- await unbindSkill(id, skillId);
378
+ await unbindSkill(id, skillId, skillVersion);
372
379
  } else if (knowledgeId) {
373
- await unbindKnowledge(id, knowledgeId);
380
+ await unbindKnowledge(id, knowledgeId, knowledgeVersion);
374
381
  } else {
375
382
  console.error('用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>');
376
383
  process.exit(1);
377
384
  }
378
385
  }
379
386
  break;
387
+ case 'sync':
388
+ if (!args[2]) {
389
+ console.error('用法: arm agent sync <folder> [--dry-run] [--force] [--json]');
390
+ process.exit(1);
391
+ }
392
+ {
393
+ const folder = args[2];
394
+ const syncOptions: { dryRun?: boolean; force?: boolean } = {};
395
+
396
+ for (let i = 3; i < args.length; i++) {
397
+ const arg = args[i];
398
+ if (arg === '--dry-run') {
399
+ syncOptions.dryRun = true;
400
+ } else if (arg === '--force') {
401
+ syncOptions.force = true;
402
+ }
403
+ }
404
+
405
+ await syncAgent(folder, syncOptions);
406
+ }
407
+ break;
408
+ case 'bindings':
409
+ if (!args[2]) {
410
+ console.error('用法: arm agent bindings <name> [--history] [--json]');
411
+ process.exit(1);
412
+ }
413
+ if (subCommand === 'history') {
414
+ console.log('bindings history 需要通过 info 命令查看');
415
+ }
416
+ break;
380
417
  default:
381
418
  console.log(`
382
419
  可用命令:
@@ -388,10 +425,11 @@ async function main() {
388
425
  arm agent create --from=<folder> 从本地文件夹创建 Agent
389
426
  arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
390
427
  arm agent delete <id> 删除 Agent
391
- arm agent bind <id> --skill=<id> 绑定 Skill 到 Agent
392
- arm agent unbind <id> --skill=<id> 解绑 Skill
393
- arm agent bind <id> --knowledge=<id> 绑定 Knowledge 到 Agent
394
- arm agent unbind <id> --knowledge=<id> 解绑 Knowledge
428
+ arm agent bind <id> --skill=<id> [--version=<ver>] 绑定 Skill 到 Agent
429
+ arm agent unbind <id> --skill=<id> [--version=<ver>] 解绑 Skill
430
+ arm agent bind <id> --knowledge=<id> [--version=<ver>] 绑定 Knowledge 到 Agent
431
+ arm agent unbind <id> --knowledge=<id> [--version=<ver>] 解绑 Knowledge
432
+ arm agent sync <folder> [--dry-run] 同步本地文件夹到云端 Agent
395
433
  所有命令支持 --json 参数获取机器可读输出
396
434
  `);
397
435
  }
@@ -426,12 +464,14 @@ Agent Resource Management (arm)
426
464
  arm agent info <name> 查看 Agent 详情
427
465
  arm agent download <name> [dir] 下载 Agent
428
466
  arm agent create <name> 创建 Agent
467
+ arm agent create --from=<folder> 从本地文件夹创建 Agent
429
468
  arm agent update <id> 更新 Agent
430
469
  arm agent delete <id> 删除 Agent
431
- arm agent bind <id> --skill=<id> 绑定 Skill
432
- arm agent unbind <id> --skill=<id> 解绑 Skill
433
- arm agent bind <id> --knowledge=<id> 绑定 Knowledge
434
- arm agent unbind <id> --knowledge=<id> 解绑 Knowledge
470
+ arm agent bind <id> --skill=<id> [--version=<ver>] 绑定 Skill
471
+ arm agent unbind <id> --skill=<id> [--version=<ver>] 解绑 Skill
472
+ arm agent bind <id> --knowledge=<id> [--version=<ver>] 绑定 Knowledge
473
+ arm agent unbind <id> --knowledge=<id> [--version=<ver>] 解绑 Knowledge
474
+ arm agent sync <folder> [--dry-run] 同步本地文件夹到云端 Agent
435
475
  arm server 显示当前服务端
436
476
  arm server set <url> 设置服务端
437
477
  使用 arm <entity> -h 查看详细帮助