agent-resource-management 2.1.3 → 2.1.5

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";
@@ -1976,10 +2229,18 @@ function setServer(url) {
1976
2229
  }
1977
2230
 
1978
2231
  // src/main.ts
2232
+ import { readFileSync as readFileSync4 } from "fs";
2233
+ import { join as join6 } from "path";
2234
+ var __dirname = "/Users/lk/Documents/Dev/aims/xuanwu/xuanwu-agents/agent-resource-management/cli/src";
1979
2235
  var args = process.argv.slice(2);
1980
2236
  var command = args[0];
1981
2237
  var subCommand = args[1];
2238
+ var VERSION = JSON.parse(readFileSync4(join6(__dirname, "../package.json"), "utf-8")).version;
1982
2239
  async function main() {
2240
+ if (command === "--version" || command === "-v") {
2241
+ console.log(`arm v${VERSION}`);
2242
+ return;
2243
+ }
1983
2244
  switch (command) {
1984
2245
  case "register":
1985
2246
  if (args[1] && args[1].startsWith("--")) {
@@ -2264,6 +2525,7 @@ async function main() {
2264
2525
  case "bind":
2265
2526
  if (!args[2]) {
2266
2527
  console.error("用法: arm agent bind <id> --skill=<skillId> [--skill-config='{...}'] 或 arm agent bind <id> --knowledge=<knowledgeId> [--knowledge-config='{...}'] [--json]");
2528
+ console.error("注意: version 缺省时自动绑定新版本");
2267
2529
  process.exit(1);
2268
2530
  }
2269
2531
  {
@@ -2285,9 +2547,9 @@ async function main() {
2285
2547
  }
2286
2548
  }
2287
2549
  if (skillId) {
2288
- await bindSkill(id, skillId, skillConfig);
2550
+ await bindSkill(id, skillId, undefined, skillConfig);
2289
2551
  } else if (knowledgeId) {
2290
- await bindKnowledge(id, knowledgeId, knowledgeConfig);
2552
+ await bindKnowledge(id, knowledgeId, undefined, knowledgeConfig);
2291
2553
  } else {
2292
2554
  console.error("用法: arm agent bind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>");
2293
2555
  process.exit(1);
@@ -2296,31 +2558,67 @@ async function main() {
2296
2558
  break;
2297
2559
  case "unbind":
2298
2560
  if (!args[2]) {
2299
- 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]");
2300
2562
  process.exit(1);
2301
2563
  }
2302
2564
  {
2303
2565
  const id = args[2];
2304
2566
  let skillId;
2305
2567
  let knowledgeId;
2568
+ let skillVersion;
2569
+ let knowledgeVersion;
2306
2570
  for (let i = 3;i < args.length; i++) {
2307
2571
  const arg = args[i];
2308
2572
  if (arg.startsWith("--skill=")) {
2309
2573
  skillId = arg.split("=").slice(1).join("=");
2310
2574
  } else if (arg.startsWith("--knowledge=")) {
2311
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;
2312
2582
  }
2313
2583
  }
2314
2584
  if (skillId) {
2315
- await unbindSkill(id, skillId);
2585
+ await unbindSkill(id, skillId, skillVersion);
2316
2586
  } else if (knowledgeId) {
2317
- await unbindKnowledge(id, knowledgeId);
2587
+ await unbindKnowledge(id, knowledgeId, knowledgeVersion);
2318
2588
  } else {
2319
2589
  console.error("用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>");
2320
2590
  process.exit(1);
2321
2591
  }
2322
2592
  }
2323
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;
2324
2622
  default:
2325
2623
  console.log(`
2326
2624
  可用命令:
@@ -2332,10 +2630,11 @@ async function main() {
2332
2630
  arm agent create --from=<folder> 从本地文件夹创建 Agent
2333
2631
  arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
2334
2632
  arm agent delete <id> 删除 Agent
2335
- arm agent bind <id> --skill=<id> 绑定 Skill 到 Agent
2336
- arm agent unbind <id> --skill=<id> 解绑 Skill
2337
- arm agent bind <id> --knowledge=<id> 绑定 Knowledge 到 Agent
2338
- 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
2339
2638
  所有命令支持 --json 参数获取机器可读输出
2340
2639
  `);
2341
2640
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-resource-management",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
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,15 +1,25 @@
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';
7
7
 
8
+ import { readFileSync } from 'fs';
9
+ import { join } from 'path';
10
+
8
11
  const args = process.argv.slice(2);
9
12
  const command = args[0];
10
13
  const subCommand = args[1];
11
14
 
15
+ const VERSION = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8')).version;
16
+
12
17
  async function main() {
18
+ if (command === '--version' || command === '-v') {
19
+ console.log(`arm v${VERSION}`);
20
+ return;
21
+ }
22
+
13
23
  switch (command) {
14
24
  case 'register':
15
25
  if (args[1] && args[1].startsWith('--')) {
@@ -306,6 +316,7 @@ async function main() {
306
316
  case 'bind':
307
317
  if (!args[2]) {
308
318
  console.error('用法: arm agent bind <id> --skill=<skillId> [--skill-config=\'{...}\'] 或 arm agent bind <id> --knowledge=<knowledgeId> [--knowledge-config=\'{...}\'] [--json]');
319
+ console.error('注意: version 缺省时自动绑定新版本');
309
320
  process.exit(1);
310
321
  }
311
322
  {
@@ -329,9 +340,9 @@ async function main() {
329
340
  }
330
341
 
331
342
  if (skillId) {
332
- await bindSkill(id, skillId, skillConfig);
343
+ await bindSkill(id, skillId, undefined, skillConfig);
333
344
  } else if (knowledgeId) {
334
- await bindKnowledge(id, knowledgeId, knowledgeConfig);
345
+ await bindKnowledge(id, knowledgeId, undefined, knowledgeConfig);
335
346
  } else {
336
347
  console.error('用法: arm agent bind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>');
337
348
  process.exit(1);
@@ -340,13 +351,15 @@ async function main() {
340
351
  break;
341
352
  case 'unbind':
342
353
  if (!args[2]) {
343
- 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]');
344
355
  process.exit(1);
345
356
  }
346
357
  {
347
358
  const id = args[2];
348
359
  let skillId: string | undefined;
349
360
  let knowledgeId: string | undefined;
361
+ let skillVersion: string | undefined;
362
+ let knowledgeVersion: string | undefined;
350
363
 
351
364
  for (let i = 3; i < args.length; i++) {
352
365
  const arg = args[i];
@@ -354,19 +367,53 @@ async function main() {
354
367
  skillId = arg.split('=').slice(1).join('=');
355
368
  } else if (arg.startsWith('--knowledge=')) {
356
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;
357
374
  }
358
375
  }
359
376
 
360
377
  if (skillId) {
361
- await unbindSkill(id, skillId);
378
+ await unbindSkill(id, skillId, skillVersion);
362
379
  } else if (knowledgeId) {
363
- await unbindKnowledge(id, knowledgeId);
380
+ await unbindKnowledge(id, knowledgeId, knowledgeVersion);
364
381
  } else {
365
382
  console.error('用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>');
366
383
  process.exit(1);
367
384
  }
368
385
  }
369
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;
370
417
  default:
371
418
  console.log(`
372
419
  可用命令:
@@ -378,10 +425,11 @@ async function main() {
378
425
  arm agent create --from=<folder> 从本地文件夹创建 Agent
379
426
  arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
380
427
  arm agent delete <id> 删除 Agent
381
- arm agent bind <id> --skill=<id> 绑定 Skill 到 Agent
382
- arm agent unbind <id> --skill=<id> 解绑 Skill
383
- arm agent bind <id> --knowledge=<id> 绑定 Knowledge 到 Agent
384
- 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
385
433
  所有命令支持 --json 参数获取机器可读输出
386
434
  `);
387
435
  }
package/dist/test.md DELETED
@@ -1,3 +0,0 @@
1
- # test
2
- - hi
3
- - huw
package/dist/test2.md DELETED
@@ -1,3 +0,0 @@
1
- # test
2
- - hi
3
- - huw