@11agents/cli 0.1.33 → 0.1.35

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.
@@ -326,6 +326,16 @@ class XiaohongshuAdbPublisher:
326
326
  expected_title=title,
327
327
  expected_caption=composed_caption,
328
328
  )
329
+ if link_result.status != "retrieved" or not link_result.platform_permalink:
330
+ link_shot = (
331
+ Path(link_result.screenshot_path)
332
+ if link_result.screenshot_path
333
+ else run_dir / "link" / "failed-copy-link.png"
334
+ )
335
+ raise XiaohongshuAutomationError(
336
+ f"Xiaohongshu post-publish link recovery failed: {link_result.error or link_result.status}",
337
+ link_shot,
338
+ )
329
339
 
330
340
  ended_epoch = int(time.time())
331
341
  if status == "published":
@@ -381,6 +391,10 @@ class XiaohongshuAdbPublisher:
381
391
  remote_media_path=remote,
382
392
  screenshot_path=str(human_shot),
383
393
  error=str(exc),
394
+ platform_permalink=link_result.platform_permalink if link_result else "",
395
+ link_status=link_result.status if link_result else "",
396
+ link_error=link_result.error if link_result else "",
397
+ link_screenshot_path=link_result.screenshot_path if link_result else "",
384
398
  verification_status="failed",
385
399
  verification_error=str(exc),
386
400
  profile_screenshot_path=str(run_dir / "profile.png") if (run_dir / "profile.png").exists() else "",
@@ -401,6 +415,10 @@ class XiaohongshuAdbPublisher:
401
415
  remote_media_path=remote,
402
416
  screenshot_path=str(failure_shot),
403
417
  error=str(exc),
418
+ platform_permalink=link_result.platform_permalink if link_result else "",
419
+ link_status=link_result.status if link_result else "",
420
+ link_error=link_result.error if link_result else "",
421
+ link_screenshot_path=link_result.screenshot_path if link_result else "",
404
422
  )
405
423
 
406
424
  def copy_current_note_link(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@11agents/cli",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "11agents local runtime and telemetry CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -874,6 +874,99 @@ function normalizeSkillBundle(skill = {}) {
874
874
  }
875
875
  }
876
876
 
877
+ async function callProjectMcp(toolName, toolArgs, syncConfig, deps) {
878
+ const result = await deps.requestJson('/mcp', {
879
+ method: 'POST',
880
+ body: {
881
+ jsonrpc: '2.0',
882
+ id: 1,
883
+ method: 'tools/call',
884
+ params: { name: toolName, arguments: toolArgs },
885
+ },
886
+ config: syncConfig,
887
+ })
888
+ if (result.error) throw new Error(result.error.message || `MCP ${toolName} failed`)
889
+ const text = result.result?.content?.[0]?.text || '{}'
890
+ return JSON.parse(text)
891
+ }
892
+
893
+ async function syncSkillsViaMcp({ task, workdir, flags, deps, projectToken, config }) {
894
+ const agentId = task.agent?.id
895
+ if (!agentId) return { changed: false, count: 0 }
896
+
897
+ const syncConfig = { ...config, token: projectToken }
898
+ const skillsDir = path.join(workdir, 'agents', slugify(agentNameForTask(task), 'agent'), 'skills')
899
+
900
+ const manifest = await callProjectMcp('skill_list', { agent_id: agentId }, syncConfig, deps)
901
+ const cloudSkills = Array.isArray(manifest.skills) ? manifest.skills : []
902
+
903
+ await mkdir(skillsDir, { recursive: true })
904
+ let changed = false
905
+
906
+ // Remove local skill dirs that no longer exist in the cloud
907
+ const cloudSkillSlugs = new Set(cloudSkills.map(s => slugify(s.name, 'skill')))
908
+ const localDirs = await readdir(skillsDir, { withFileTypes: true }).catch(() => [])
909
+ for (const entry of localDirs) {
910
+ if (entry.isDirectory() && !cloudSkillSlugs.has(entry.name)) {
911
+ await rm(path.join(skillsDir, entry.name), { recursive: true, force: true })
912
+ changed = true
913
+ }
914
+ }
915
+
916
+ for (const cloudSkill of cloudSkills) {
917
+ const skillSlug = slugify(cloudSkill.name, 'skill')
918
+ const skillDir = path.join(skillsDir, skillSlug)
919
+ const cloudFiles = Array.isArray(cloudSkill.files) ? cloudSkill.files : []
920
+
921
+ // Check for any missing local file
922
+ const presentFlags = await Promise.all(
923
+ cloudFiles.map(f => readFile(path.join(skillDir, assertSafeRelativePath(f.name))).then(() => true).catch(() => false))
924
+ )
925
+ const hasMissing = presentFlags.some(p => !p)
926
+
927
+ if (hasMissing) {
928
+ // Full re-fetch: delete skill dir and pull every file fresh
929
+ await rm(skillDir, { recursive: true, force: true })
930
+ await mkdir(skillDir, { recursive: true })
931
+ for (const cloudFile of cloudFiles) {
932
+ const fetched = await callProjectMcp('skill_file_get', {
933
+ agent_id: agentId,
934
+ skill_name: cloudSkill.name,
935
+ file_path: cloudFile.name,
936
+ }, syncConfig, deps)
937
+ const target = path.join(skillDir, assertSafeRelativePath(cloudFile.name))
938
+ await mkdir(path.dirname(target), { recursive: true })
939
+ const content = fetched.encoding === 'base64'
940
+ ? Buffer.from(fetched.content || '', 'base64')
941
+ : String(fetched.content || '')
942
+ await writeFile(target, content)
943
+ }
944
+ changed = true
945
+ } else {
946
+ // All files present — diff by MD5
947
+ for (const cloudFile of cloudFiles) {
948
+ const localPath = path.join(skillDir, assertSafeRelativePath(cloudFile.name))
949
+ const localBuf = await readFile(localPath)
950
+ const localMd5 = createHash('md5').update(localBuf).digest('hex')
951
+ if (localMd5 !== cloudFile.md5) {
952
+ const fetched = await callProjectMcp('skill_file_get', {
953
+ agent_id: agentId,
954
+ skill_name: cloudSkill.name,
955
+ file_path: cloudFile.name,
956
+ }, syncConfig, deps)
957
+ const content = fetched.encoding === 'base64'
958
+ ? Buffer.from(fetched.content || '', 'base64')
959
+ : String(fetched.content || '')
960
+ await writeFile(localPath, content)
961
+ changed = true
962
+ }
963
+ }
964
+ }
965
+ }
966
+
967
+ return { changed, count: cloudSkills.length, skills_dir: skillsDir }
968
+ }
969
+
877
970
  async function materializeSkillsIfChanged({ task, workdir, flags, deps }) {
878
971
  const skills = Array.isArray(task.agent?.skills) ? task.agent.skills.map(normalizeSkillBundle) : []
879
972
  if (!skills.length) return { changed: false, count: 0 }
@@ -1016,11 +1109,10 @@ async function prepareRuntimeTask(task, flags, deps, config) {
1016
1109
  await mkdir(agentMemoryDir, { recursive: true })
1017
1110
 
1018
1111
  const database = await syncDatabaseIfNeeded({ task, workdir, config, flags, deps })
1019
- const skills = await materializeSkillsIfChanged({ task, workdir, flags, deps })
1020
1112
 
1021
- // Resolve and inject the project token so hosted MCP calls spawned by the
1022
- // runtime agent can authenticate without needing credentials on disk.
1113
+ // Resolve project token before skill sync so MCP auth is available.
1023
1114
  const projectToken = await projectSyncToken(projectTokenCandidatesForTask(task, flags), flags, deps, task.workspace?.swarm_token)
1115
+ const skills = await syncSkillsViaMcp({ task, workdir, flags, deps, projectToken, config })
1024
1116
  const env = {
1025
1117
  ...process.env,
1026
1118
  ...agentEnvironment(task),