best-geo-test 2.0.2 → 2.0.4-beta.1

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.
@@ -3566,14 +3566,14 @@
3566
3566
  "mode": "read",
3567
3567
  "guide": "publication",
3568
3568
  "requiresPlan": false,
3569
- "description": "当前能用的自媒体发布平台。文章平台和视频平台是两批,建发布任务前先按 contentType 取对应的那批——拿文章平台去发视频会被拒。返回里的 **limitConfig 是这个平台对文章的字数要求**(titleMaxLength 标题上限 / contentMinLength 正文下限 / contentMaxLength 正文上限,0 表示该项不限;为 null 表示这个平台不限字数)——**只对文章任务生效,视频任务不查字数**,视频平台这一项恒为 null。**remark 是投稿要求备注**(比如不收带外链的稿子)。不满足字数要求的文章建任务时会被拒,挑文章前先看这里。",
3569
+ "description": "当前能用的自媒体发布平台。文章平台、视频平台、图文平台是三批,建发布任务前先按 contentType 取对应的那批——拿文章平台去发视频或图文会被拒。返回里的 **limitConfig 是这个平台对内容的字数/张数要求**:文章平台是 titleMaxLength 标题上限 / contentMinLength 正文下限 / contentMaxLength 正文上限(0 表示该项不限;为 null 表示这个平台不限字数);**视频平台**看的是标题长度(titleMinLength / titleMaxLength)、描述长度(summaryMinLength / summaryMaxLength),视频没有正文,content 那两项不适用,建视频任务时按它校验视频;**图文平台**看的是标题长度(titleMinLength / titleMaxLength)、描述长度(summaryMinLength / summaryMaxLength,对应答案卡的发布描述),建图文任务时按它校验答案卡;**图片张数上限以 plan 阶段的报错为准**(各图文平台不同,不要只凭 imageMaxCount 判断)。**remark 是投稿要求备注**(比如不收带外链的稿子)。不满足要求的内容建任务时会被拒,挑内容前先看这里。",
3570
3570
  "inputSchema": {
3571
3571
  "type": "object",
3572
3572
  "properties": {
3573
3573
  "contentType": {
3574
3574
  "type": "string",
3575
- "enum": ["article", "video"],
3576
- "description": "只看发文章的还是只看发视频的。不传就是全部"
3575
+ "enum": ["article", "video", "image"],
3576
+ "description": "只看发文章的、发视频的还是发图文的(图文内容来自 AI 答案卡)。不传就是全部"
3577
3577
  }
3578
3578
  },
3579
3579
  "additionalProperties": false
@@ -3620,7 +3620,7 @@
3620
3620
  "mode": "read",
3621
3621
  "guide": "publication",
3622
3622
  "requiresPlan": false,
3623
- "description": "发布记录:每条内容在每个平台的实际下场。限定在当前 GEO 项目内。这是「发出去了没有、发到哪个链接、失败为什么」的唯一出处——用户问「小红书那篇发了吗」就查这里。status 同时给数值和 statusLabel,念给用户用后者。",
3623
+ "description": "发布记录:每条内容在每个平台的实际下场。限定在当前 GEO 项目内。这是「发出去了没有、发到哪个链接、失败为什么」的唯一出处——用户问「小红书那篇发了吗」就查这里。contentType 区分 article / video / image,图文记录没有 articleId,来源答案卡在 answerCardTaskId,title 是答案卡的发布标题。status 同时给数值和 statusLabel,念给用户用后者。",
3624
3624
  "inputSchema": {
3625
3625
  "type": "object",
3626
3626
  "properties": {
@@ -3657,7 +3657,7 @@
3657
3657
  "mode": "read",
3658
3658
  "guide": "publication",
3659
3659
  "requiresPlan": false,
3660
- "description": "单个媒体的实时状态与当前价格。**报价给用户前必须查这条**——列表是分页快照,遇上刚调过价就会和实际扣费对不上。**单位注意**:这里的价格字段是 points,单位是算力(1 元 = 100 算力);mediaPlatforms.list 的 price 和 plan 摘要里的金额单位都是元。给用户报价时统一换算成元,例如 points=5000 就是 50 元。",
3660
+ "description": "单个媒体的实时状态与当前价格。**报价给用户前必须查这条**——列表是分页快照,遇上刚调过价就会和实际扣费对不上。",
3661
3661
  "inputSchema": {
3662
3662
  "type": "object",
3663
3663
  "properties": {
@@ -3843,7 +3843,7 @@
3843
3843
  "mode": "write",
3844
3844
  "guide": "publication",
3845
3845
  "requiresPlan": true,
3846
- "description": "建视频发布任务。和 createArticle 同一套结构,只把 articleId 换成 videoId,平台要用 contentType=video 那批——拿文章平台发视频会被拒。createArticle 那条的字数要求**不适用于视频**:视频任务不查字数。",
3846
+ "description": "建视频发布任务。和 createArticle 同一套结构,只把 articleId 换成 videoId,平台要用 contentType=video 那批——拿文章平台发视频会被拒。**也要满足平台要求**:标题、描述长度按 contentType=video 那批平台自己的 limitConfig 校验(没有正文,不查正文字数),有的平台还要求有封面,不达标整批都建不了。数字人、智能剪辑生成的视频,发出去的标题和描述以生成任务里填的发布信息为准,不是素材本身的标题,所以用 videos.update 改标题对这类视频不起作用。plan 报「不满足平台要求」时把错误原样念给用户,让他定是改视频还是不发那个平台。",
3847
3847
  "inputSchema": {
3848
3848
  "type": "object",
3849
3849
  "properties": {
@@ -3885,6 +3885,58 @@
3885
3885
  "additionalProperties": false
3886
3886
  }
3887
3887
  },
3888
+ {
3889
+ "name": "publicationTasks.createImage",
3890
+ "mode": "write",
3891
+ "guide": "publication",
3892
+ "requiresPlan": true,
3893
+ "description": "建图文发布任务:把 AI 答案卡(answerCards)做好的图发到图文平台(抖音图文、视频号图文等)。和 createArticle 同一套结构,只把 articleId 换成 answerCardTaskId,平台要用 contentType=image 那批——拿文章或视频平台发图文会被拒。**执行后会真的发出去,CLI 撤不回来**。费用按发布记录条数算——一张答案卡发 3 个账号就是 3 条。**挑已生成完成(status=8)且发布标题(publishTitle)不为空的答案卡**,图要出齐(服务端只校验图是否出齐和标题,不校验 status,但没完成的卡多半图不全);缺标题先用 answerCards.savePublishInfo 补。标题、描述、图片张数按平台自己的 limitConfig 校验,同一张卡发不同平台,张数上限可能不同。**不用问 aigc**:网页端图文已经没有「AI 创作声明」,不传就是不打。",
3894
+ "inputSchema": {
3895
+ "type": "object",
3896
+ "properties": {
3897
+ "name": { "type": "string", "minLength": 1, "maxLength": 100, "description": "任务名,给用户认的" },
3898
+ "projectId": { "type": "integer", "minimum": 1, "description": "发哪个 GEO 项目的内容" },
3899
+ "aigc": { "type": "boolean", "description": "要不要打「AI 创作」声明。图文可以不传,默认 false" },
3900
+ "items": {
3901
+ "type": "array",
3902
+ "minItems": 1,
3903
+ "description": "每项是「一张答案卡 + 它要发到哪些图文平台的哪些账号」",
3904
+ "items": {
3905
+ "type": "object",
3906
+ "properties": {
3907
+ "answerCardTaskId": { "type": "integer", "minimum": 1, "description": "答案卡任务 id,来自 answerCards.list" },
3908
+ "platforms": {
3909
+ "type": "array",
3910
+ "minItems": 1,
3911
+ "items": {
3912
+ "type": "object",
3913
+ "properties": {
3914
+ "platform": { "type": "string", "description": "图文平台标识,填 publicationPlatforms.list(contentType=image)返回的 platform 值" },
3915
+ "publishAccountIds": {
3916
+ "type": "array",
3917
+ "minItems": 1,
3918
+ "items": { "type": "integer", "minimum": 1 },
3919
+ "description": "用哪些账号发。一个账号一条发布记录"
3920
+ },
3921
+ "publishTime": {
3922
+ "type": "string",
3923
+ "description": "定时发布时间,ISO 格式如 2026-09-01T09:00:00+08:00。**不传 = 立即发布**。不能填过去的时间"
3924
+ }
3925
+ },
3926
+ "required": ["platform", "publishAccountIds"],
3927
+ "additionalProperties": false
3928
+ }
3929
+ }
3930
+ },
3931
+ "required": ["answerCardTaskId", "platforms"],
3932
+ "additionalProperties": false
3933
+ }
3934
+ }
3935
+ },
3936
+ "required": ["name", "projectId", "items"],
3937
+ "additionalProperties": false
3938
+ }
3939
+ },
3888
3940
  {
3889
3941
  "name": "publicationTasks.delete",
3890
3942
  "mode": "write",
@@ -4442,7 +4494,7 @@
4442
4494
  "mode": "read",
4443
4495
  "guide": "answer-card",
4444
4496
  "requiresPlan": false,
4445
- "description": "答案卡任务详情:任务状态 + 逐页脚本 + 当前活动批次。status=1 分析中,脚本还没出来,先别急着高频轮询;status=2 脚本已就绪,才能看拆好的页、用 answerCards.savePages 改稿或用 answerCards.generate 确认生成。",
4497
+ "description": "答案卡任务详情:任务状态 + 逐页脚本 + 当前活动批次。status=1 分析中,脚本还没出来,先别急着高频轮询;status=2 脚本已就绪,才能看拆好的页、用 answerCards.savePages 改稿或用 answerCards.generate 确认生成。导入的答案卡没有批次,图在 pages[].importedImageUrl,不要调 answerCards.batch;它的视觉方式、内容多少、模型档位、页标题和页类型都是占位值,不要念给用户。",
4446
4498
  "inputSchema": {
4447
4499
  "type": "object",
4448
4500
  "properties": {
@@ -4817,6 +4869,58 @@
4817
4869
  "required": ["projectId", "taskId", "publishTitle"],
4818
4870
  "additionalProperties": false
4819
4871
  }
4872
+ },
4873
+ {
4874
+ "name": "answerCards.import",
4875
+ "mode": "write",
4876
+ "guide": "answer-card",
4877
+ "requiresPlan": true,
4878
+ "description": "把素材库里现成的图片导入成一张「已完成」的答案卡,每张图一页,顺序和重复都按 imageIds 给的来。**确认后扣一笔答案卡基础费**(金额见计划 notice)。图片要先在素材库里:本地图片用 images.create 上传拿到 id;也可以用本项目、本公司公共、或同组 GEO 项目素材库里的图。状态是「处理失败」「已停用」的图、以及不在本站存储上的外链图不能导入。导入的卡只能用 answerCards.savePublishInfo 改发布资料、用 publicationTasks.createImage 发布,**不能 savePages 改脚本,也不能 generate 重新出图**。",
4879
+ "inputSchema": {
4880
+ "type": "object",
4881
+ "properties": {
4882
+ "projectId": {
4883
+ "type": "integer",
4884
+ "minimum": 1,
4885
+ "description": "GEO 项目 id,来自 projects.list"
4886
+ },
4887
+ "imageIds": {
4888
+ "type": "array",
4889
+ "minItems": 1,
4890
+ "maxItems": 35,
4891
+ "items": {
4892
+ "type": "integer",
4893
+ "minimum": 1
4894
+ },
4895
+ "description": "素材库图片 id,顺序就是页序;同一张可以出现多次。发视频号图文最多 18 张,发布时会再按平台拦"
4896
+ },
4897
+ "coverIndex": {
4898
+ "type": "integer",
4899
+ "minimum": 1,
4900
+ "description": "封面是第几张(1 起),不传就是第 1 张。视频号图文固定用第 1 张"
4901
+ },
4902
+ "publishTitle": {
4903
+ "type": "string",
4904
+ "maxLength": 30,
4905
+ "description": "发布标题,必填"
4906
+ },
4907
+ "publishDescription": {
4908
+ "type": "string",
4909
+ "maxLength": 500
4910
+ },
4911
+ "publishTags": {
4912
+ "type": "array",
4913
+ "maxItems": 8,
4914
+ "items": {
4915
+ "type": "string",
4916
+ "maxLength": 30
4917
+ },
4918
+ "description": "话题标签,不允许空字符串"
4919
+ }
4920
+ },
4921
+ "required": ["projectId", "imageIds", "publishTitle"],
4922
+ "additionalProperties": false
4923
+ }
4820
4924
  }
4821
4925
  ]
4822
4926
  }
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var jt=Object.defineProperty;var Vt=(e,t,n)=>t in e?jt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var se=(e,t,n)=>Vt(e,typeof t!="symbol"?t+"":t,n);var ae="https://nbgeo.aimusiclj.com",Oe="best-geo-test",$e="best-geo-test",Ne="bg-test-cli-",Pe="2.0.2";var B=2,k=Pe,D=$e,p=Oe,Le="dev",J=Bt(Ft());function Ft(){var e;return(e=process.env.BEST_GEO_API_BASE)!=null?e:ae}var Q=Ne,De=300*1e3,Ue=30*1e3,Me=300*1e3;function Bt(e){return e.replace(/\/+$/,"")}var tt=require("node:child_process");var Ge=require("node:crypto"),X=require("node:os");var d=require("node:fs"),je=require("node:os"),A=require("node:path");var le=require("node:crypto");var _=class extends Error{constructor(n,r,o){super(o);se(this,"reason",n);se(this,"path",r);this.name="LocalConfigError"}},qt={schemaVersion:1,device:null,activeCredential:null,pendingCredential:null};function q(){var e;return(e=process.env.BEST_GEO_CONFIG_DIR)!=null?e:(0,A.join)((0,je.homedir)(),`.${p}`)}function O(){return(0,A.join)(q(),"config.json")}function C(){var n,r,o;let e=O(),t;try{if(!(0,d.existsSync)(e))return null;Gt(e),t=(0,d.readFileSync)(e,"utf8")}catch(i){throw i instanceof _?i:Ve(i,e)}try{let i=JSON.parse(t);if(typeof i!="object"||i==null||i.schemaVersion!==1)throw new Error("unexpected shape");return{schemaVersion:1,device:(n=i.device)!=null?n:null,activeCredential:(r=i.activeCredential)!=null?r:null,pendingCredential:(o=i.pendingCredential)!=null?o:null}}catch{throw new _("corrupted",e,`\u672C\u673A\u914D\u7F6E\u6587\u4EF6\u5185\u5BB9\u4E0D\u5B8C\u6574\u6216\u683C\u5F0F\u4E0D\u5BF9\uFF1A${e}`)}}function Kt(e){let t=q(),n=O(),r=(0,A.join)(t,`.config.json.${(0,le.randomBytes)(6).toString("hex")}.tmp`);try{(0,d.existsSync)(t)||(0,d.mkdirSync)(t,{recursive:!0,mode:448});let o=(0,d.openSync)(r,"wx",384);try{(0,d.writeSync)(o,`${JSON.stringify(e,null,2)}
2
+ "use strict";var jt=Object.defineProperty;var Vt=(e,t,n)=>t in e?jt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var se=(e,t,n)=>Vt(e,typeof t!="symbol"?t+"":t,n);var ae="https://nbgeo.aimusiclj.com",Oe="best-geo-test",$e="best-geo-test",Ne="bg-test-cli-",Pe="2.0.4-beta.1";var B=2,k=Pe,D=$e,p=Oe,Le="dev",J=Bt(Ft());function Ft(){var e;return(e=process.env.BEST_GEO_API_BASE)!=null?e:ae}var Q=Ne,De=300*1e3,Ue=30*1e3,Me=300*1e3;function Bt(e){return e.replace(/\/+$/,"")}var tt=require("node:child_process");var Ge=require("node:crypto"),X=require("node:os");var d=require("node:fs"),je=require("node:os"),A=require("node:path");var le=require("node:crypto");var _=class extends Error{constructor(n,r,o){super(o);se(this,"reason",n);se(this,"path",r);this.name="LocalConfigError"}},qt={schemaVersion:1,device:null,activeCredential:null,pendingCredential:null};function q(){var e;return(e=process.env.BEST_GEO_CONFIG_DIR)!=null?e:(0,A.join)((0,je.homedir)(),`.${p}`)}function O(){return(0,A.join)(q(),"config.json")}function C(){var n,r,o;let e=O(),t;try{if(!(0,d.existsSync)(e))return null;Gt(e),t=(0,d.readFileSync)(e,"utf8")}catch(i){throw i instanceof _?i:Ve(i,e)}try{let i=JSON.parse(t);if(typeof i!="object"||i==null||i.schemaVersion!==1)throw new Error("unexpected shape");return{schemaVersion:1,device:(n=i.device)!=null?n:null,activeCredential:(r=i.activeCredential)!=null?r:null,pendingCredential:(o=i.pendingCredential)!=null?o:null}}catch{throw new _("corrupted",e,`\u672C\u673A\u914D\u7F6E\u6587\u4EF6\u5185\u5BB9\u4E0D\u5B8C\u6574\u6216\u683C\u5F0F\u4E0D\u5BF9\uFF1A${e}`)}}function Kt(e){let t=q(),n=O(),r=(0,A.join)(t,`.config.json.${(0,le.randomBytes)(6).toString("hex")}.tmp`);try{(0,d.existsSync)(t)||(0,d.mkdirSync)(t,{recursive:!0,mode:448});let o=(0,d.openSync)(r,"wx",384);try{(0,d.writeSync)(o,`${JSON.stringify(e,null,2)}
3
3
  `),(0,d.fsyncSync)(o)}finally{(0,d.closeSync)(o)}(0,d.renameSync)(r,n)}catch(o){if((0,d.existsSync)(r))try{(0,d.unlinkSync)(r)}catch{}throw Ve(o,n)}}function T(e){var n;let t=e((n=C())!=null?n:{...qt});return Kt(t),t}function Gt(e){var r;if(process.platform==="win32")return;let t=(0,d.statSync)(e),n=(r=process.getuid)==null?void 0:r.call(process);if(n!=null&&t.uid!==n)throw new _("not_owner",e,`\u672C\u673A\u914D\u7F6E\u6587\u4EF6\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237\uFF1A${e}`);if((t.mode&63)!==0)throw new _("permissions_too_open",e,`\u672C\u673A\u914D\u7F6E\u6587\u4EF6\u6743\u9650\u8FC7\u5BBD\uFF08\u540C\u673A\u5176\u4ED6\u7528\u6237\u53EF\u8BFB\uFF09\uFF1A${e}`)}function Ve(e,t){let n=e==null?void 0:e.code;return n==="EACCES"||n==="EPERM"||n==="EROFS"?new _("denied",t,`\u6CA1\u6709\u6743\u9650\u8BFB\u5199\u672C\u673A\u914D\u7F6E\uFF1A${t}`):e instanceof Error?e:new Error(String(e))}var Fe=3600*1e3;function Be(){return(0,A.join)(q(),"pending-uploads")}var Ht=/^plan_[0-9a-zA-Z]+$/;function de(e){return Ht.test(e)?(0,A.join)(Be(),`${e}.json`):null}function W(e){var n;let t=de(e);if(t==null||!(0,d.existsSync)(t))return null;try{let r=JSON.parse((0,d.readFileSync)(t,"utf8"));return Date.now()-((n=r==null?void 0:r.createdAt)!=null?n:0)<Fe?r:null}catch{return null}}function qe(e,t){let n=de(e);if(n==null)throw new Error(`\u4E0D\u5408\u6CD5\u7684\u8BA1\u5212 ID:${e}`);let r=Be();(0,d.mkdirSync)(r,{recursive:!0,mode:448}),zt(r);let o=(0,A.join)(r,`.${e}.${(0,le.randomBytes)(6).toString("hex")}.tmp`);try{(0,d.writeFileSync)(o,JSON.stringify(t),{mode:384}),(0,d.renameSync)(o,n)}catch(i){if((0,d.existsSync)(o))try{(0,d.unlinkSync)(o)}catch{}throw i}}function Ke(e){let t=de(e);if(t!=null)try{(0,d.unlinkSync)(t)}catch{}}function zt(e){let t=(0,A.join)(q(),"pending-uploads.json");try{(0,d.existsSync)(t)&&(0,d.unlinkSync)(t)}catch{}let n;try{n=(0,d.readdirSync)(e)}catch{return}for(let r of n){if(r.endsWith(".tmp")){try{let i=(0,A.join)(e,r);Date.now()-(0,d.statSync)(i).mtimeMs>=Fe&&(0,d.unlinkSync)(i)}catch{}continue}if(!r.endsWith(".json"))continue;let o=r.slice(0,-5);if(W(o)==null)try{(0,d.unlinkSync)((0,A.join)(e,r))}catch{}}}function He(){var t,n;let e=(t=C())==null?void 0:t.device;return(n=e==null?void 0:e.deviceId)!=null&&n.startsWith(Q)?e.deviceId:null}function Y(){var r,o;let e={deviceName:(0,X.hostname)().slice(0,50),platform:`${(0,X.platform)()}-${process.arch}`},t=(r=C())==null?void 0:r.device;if((o=t==null?void 0:t.deviceId)!=null&&o.startsWith(Q))return t.deviceName===e.deviceName&&t.platform===e.platform?t:T(i=>({...i,device:{...t,...e}})).device;let n={deviceId:`${Q}${(0,Ge.randomUUID)()}`,...e};return T(i=>({...i,device:n})).device}function ze(){return`cli_${Date.now().toString(36)}${Math.random().toString(36).slice(2,8)}`}function E(e,t=ze()){return{ok:!0,code:"OK",requestId:t,data:e}}function l(e,t,n={}){var r;return{ok:!1,code:e,message:t,requestId:(r=n.requestId)!=null?r:ze(),...n.data==null?{}:{data:n.data},...n.nextAction==null?{}:{nextAction:n.nextAction}}}function a(e){process.stdout.write(`${JSON.stringify(e)}
4
4
  `),e.ok||(process.exitCode=1)}async function x(e,t={}){var i,s;let n=new URL(`${J}/v1/agent-cli${e}`);for(let[c,f]of Object.entries((i=t.query)!=null?i:{}))if(f!=null)for(let m of Array.isArray(f)?f:[f])n.searchParams.append(c,String(m));let r={"X-GEO-CLI-Version":k,"X-GEO-Protocol-Version":String(B),"X-GEO-CLI-Package":D};t.projectId!=null&&(r["X-Geo-Product-Id"]=String(t.projectId));let o=null;try{o=He()}catch{}o&&(r["X-Device-ID"]=o),t.apiKey&&(r.Authorization=`Bearer ${t.apiKey}`),t.body!=null&&(r["Content-Type"]="application/json");try{let c=await fetch(n,{method:(s=t.method)!=null?s:"GET",headers:r,body:t.body==null?void 0:JSON.stringify(t.body),signal:AbortSignal.timeout(t.isWrite?Me:Ue)}),f=await c.text();try{let m=JSON.parse(f);return Jt(m)?m:l("SERVER_ERROR",`\u670D\u52A1\u7AEF\u8FD4\u56DE\u4E86\u975E\u9884\u671F\u5185\u5BB9\uFF08HTTP ${c.status}\uFF09`,{data:{transportFailure:!0}})}catch{return l("SERVER_ERROR",`\u670D\u52A1\u7AEF\u8FD4\u56DE\u4E86\u975E\u9884\u671F\u5185\u5BB9\uFF08HTTP ${c.status}\uFF09`,{data:{transportFailure:!0}})}}catch(c){return c instanceof Error&&(c.name==="TimeoutError"||c.name==="AbortError")?l("SERVER_ERROR",t.isWrite?"\u670D\u52A1\u7AEF\u5904\u7406\u8D85\u65F6,\u8FD9\u6B21\u5199\u5165\u7ED3\u679C\u672A\u77E5":"\u670D\u52A1\u7AEF\u54CD\u5E94\u8D85\u65F6",{data:{transportFailure:!0},nextAction:t.isWrite?{type:"ask_user",message:"\u8D85\u65F6\u4E0D\u4EE3\u8868\u6CA1\u5199\u8FDB\u53BB \u2014\u2014 \u670D\u52A1\u7AEF\u53EF\u80FD\u8FD8\u5728\u5904\u7406\u3002**\u5148\u7528\u5BF9\u5E94\u7684\u67E5\u8BE2\u80FD\u529B\u786E\u8BA4\u4E00\u904D**,\u786E\u5B9E\u6CA1\u6709\u518D\u91CD\u65B0 plan;\u76F4\u63A5\u91CD\u8BD5\u4F1A\u5EFA\u51FA\u91CD\u590D\u7684\u4E00\u4EFD,\u800C\u4E14\u4F1A\u518D\u6263\u4E00\u6B21\u8D39\u3002"}:{type:"ask_user",message:"\u670D\u52A1\u7AEF\u54CD\u5E94\u592A\u6162\u3002\u7A0D\u540E\u91CD\u8BD5\u540C\u4E00\u6761\u547D\u4EE4\u5373\u53EF,\u67E5\u8BE2\u4E0D\u4F1A\u4EA7\u751F\u4EFB\u4F55\u5199\u5165"}}):l("SERVER_ERROR",`\u65E0\u6CD5\u8FDE\u63A5\u670D\u52A1\u7AEF\uFF1A${c instanceof Error?c.message:String(c)}`,{data:{transportFailure:!0},nextAction:{type:"ask_user",message:"\u8BF7\u786E\u8BA4\u7F51\u7EDC\u53EF\u7528\uFF0C\u4EE5\u53CA\u670D\u52A1\u7AEF\u5730\u5740\u914D\u7F6E\u6B63\u786E"}})}}function Jt(e){if(typeof e!="object"||e===null)return!1;let t=e;return typeof t.ok=="boolean"&&typeof t.code=="string"}function Qt(e){var t;return!e.ok&&((t=e.data)==null?void 0:t.transportFailure)===!0}function ce(e){return e.ok||Qt(e)?!1:["AUTH_EXPIRED","DEVICE_MISMATCH","ACCESS_DENIED","AUTH_REQUIRED","VALIDATION_FAILED"].includes(e.code)}function U(){var e,t,n;return(n=(t=(e=C())==null?void 0:e.activeCredential)==null?void 0:t.apiKey)!=null?n:null}function Z(){var e,t;return(t=(e=C())==null?void 0:e.activeCredential)!=null?t:null}function Je(){var e,t;return(t=(e=C())==null?void 0:e.pendingCredential)!=null?t:null}function Qe(e){T(t=>({...t,pendingCredential:{apiKey:e,startedAt:new Date().toISOString()}}))}function pe(){T(e=>({...e,pendingCredential:null}))}function ue(e){return T(t=>{let n=t.pendingCredential;return n?{...t,activeCredential:{apiKey:n.apiKey,...e},pendingCredential:null}:t}).activeCredential}function We(){T(e=>({...e,activeCredential:null,pendingCredential:null}))}var R=require("node:crypto"),Ze=require("node:http");function Xe(e){let t=`
5
5
  (function () {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "answer-card",
3
3
  "title": "AI 答案卡",
4
- "whenToUse": "用户想用一篇文章或一段自定义文案,快速生成一套图文并茂的多页答案卡(适合做信息图/知识卡片发布),或者想改一改已生成的脚本、查进度、拿成图地址时。",
4
+ "whenToUse": "用户想用一篇文章或一段自定义文案,快速生成一套图文并茂的多页答案卡(适合做信息图/知识卡片发布),或者想改一改已生成的脚本、查进度、拿成图地址时。也用于用户手上已经有一套做好的图片,想直接变成答案卡去发布(answerCards.import)。",
5
5
  "prerequisites": [
6
6
  "已完成 best-geo auth login",
7
7
  "已用 projects.list 确定 GEO 项目,拿到 projectId"
@@ -47,17 +47,23 @@
47
47
  "action": "定发布资料",
48
48
  "capability": "answerCards.savePublishInfo",
49
49
  "note": "标题/描述/话题标签/封面页,不扣费、不触发重新生成、也不会直接发布——纯粹保存资料。"
50
+ },
51
+ {
52
+ "step": 8,
53
+ "action": "(另一条路)导入现成图片",
54
+ "capability": "answerCards.import",
55
+ "note": "用户已经有做好的图时走这条,不经过分析和出图。先确认每张图都在素材库里:本地文件用 images.create 逐张上传拿 imageId,传完就能导入,不用等图片处理完。整条路有两类收费:每张图 images.create 一次上传费,answerCards.import 一次答案卡基础费。plan 本身不扣费,用户说了要导入就直接去 plan,不要在 plan 之前先停下来谈费用或自己去查价格——具体金额就在 plan 返回的 notice 里,随摘要一起告诉用户、用户同意就 apply。多张图时把每张 images.create 都先 plan 好,摘要和费用合在一条消息里让用户一次确认,再逐个 apply,不要一张一张问。导入后直接是已完成,可以 savePublishInfo 改发布资料,再用 publicationTasks.createImage 发布。"
50
56
  }
51
57
  ],
52
58
  "askUserWhen": [
53
59
  "用户没说清内容来源:给的是文章还是要自己写一段正文,两者只能选一个",
54
60
  "用户没说清视觉方式:不确定就用「AI 智能匹配」(visualMode=1),别替用户随便指定一个模板或编一段风格描述",
55
61
  "脚本已就绪(status=2):先把拆好的页面简要说给用户听,问要不要改,不要没确认就直接去 generate",
56
- "确认生成之前:generate 是真花钱的一步,必须把 plan 里的费用说明(出图费 + 基础费两笔)念清楚,等用户明确同意再 apply",
62
+ "确认生成或导入之前:answerCards.generate(出图费 + 基础费两笔)和 answerCards.import(一笔基础费)都收费,必须把 plan 返回的 notice 里的费用说明念清楚,等用户明确同意再 apply",
57
63
  "有失败页时:告知用户失败的页数和退款情况,问他要不要去网页处理,不要替他决定"
58
64
  ],
59
65
  "forbidden": [
60
- "不要替用户决定出不出图。answerCards.generate 是这条链路里唯一花钱的一步,没有得到用户明确同意不能 apply",
66
+ "不要替用户决定出不出图、导不导入。answerCards.generate 和 answerCards.import 都收费,apply 前必须把 notice 里的费用念给用户,没有得到用户明确同意不能 apply",
61
67
  "不要在 status=1(分析中)时反复调 answerCards.get 高频轮询,分析通常需要一些时间",
62
68
  "不要在 answerCards.generate 的计划确认之后再用 answerCards.savePages 改脚本。这份计划的报价是按当时的页数定的,页数一变就会被 apply 打回,得重新生成计划让用户重新确认",
63
69
  "不要自己编 templateId / modelCode。templateId 从 answerCards.templates 查,modelCode 从 textToImages.models 查,猜的值提交后会被服务端拒",
@@ -66,23 +72,25 @@
66
72
  "不要拿 savePages 之前的旧页 id 去提交下一次 savePages 或 generate。savePages 是删了重建页,提交成功后旧 id 全部作废,必须重新 answerCards.get 拿新 id",
67
73
  "不要因为批次 status 还没到终态就说任务失败了。4/5 排队中/出图中都还在正常跑,只有 6/7/8 才是终态",
68
74
  "现在没有重跑单页失败图的能力,不要替用户编一个「重试」操作,有失败页就引导他去网页处理",
69
- "不要跳过 plan 直接对写能力发 apply。answerCards.create / savePages / generate / savePublishInfo 都要先 plan 给用户看摘要"
75
+ "不要跳过 plan 直接对写能力发 apply。answerCards.create / savePages / generate / import / savePublishInfo 都要先 plan 给用户看摘要",
76
+ "不要对导入的答案卡(sourceType=3)调 answerCards.savePages 或 answerCards.generate,服务端在 plan 阶段就会拒绝;用户想改内容,只能换一组图片 id 重新导入一张新的(素材库里已有的图直接用 id,只有本地新图才需要先 images.create 上传)",
77
+ "不要用外链地址或别的 GEO 项目(非同组)的图去导入,会被拒;本地图片先用 images.create 上传"
70
78
  ],
71
79
  "errorRecovery": {
72
- "ACCESS_DENIED": "这条任务不在当前 GEO 项目里,用当前项目的列表能力重新查;要换项目先问用户。查别人的答案卡任务也报这个码——不区分「不存在」和「不是你的」。",
73
- "VALIDATION_FAILED": "参数不合法,按 capabilities 里的 inputSchema 修正后重试。message 里写明了是哪个字段;常见原因:sourceType/visualMode 对应的必填项没给全、content 不够 20 个有效字符、savePages 的补丁 pages/order/deleteIds 三项全空、用了 insert 加页或改了某页的 type/visualType(暂不支持)、order 没列全所有页 id(或用了 savePages 之前已作废的旧页 id)。",
74
- "PLAN_STALE": "确认期间这条任务(脚本或发布资料)被别人改过。重新 answerCards.get 读当前内容、重新 plan,把新摘要再给用户确认一次,不要拿旧 planId 重试。",
80
+ "ACCESS_DENIED": "这条任务不在当前 GEO 项目里,用当前项目的列表能力重新查;要换项目先问用户。查别人的答案卡任务也报这个码——不区分「不存在」和「不是你的」。导入时报这个码,是有图片不在可用范围:换用本项目、本公司公共或同组项目的图。",
81
+ "VALIDATION_FAILED": "参数不合法,按 capabilities 里的 inputSchema 修正后重试。message 里写明了是哪个字段;常见原因:sourceType/visualMode 对应的必填项没给全、content 不够 20 个有效字符、savePages 的补丁 pages/order/deleteIds 三项全空、用了 insert 加页或改了某页的 type/visualType(暂不支持)、order 没列全所有页 id(或用了 savePages 之前已作废的旧页 id)。import 报这个码时多半是:某张图在素材库里已停用或处理失败(换一张图)、图不是本站上传的(先 images.create 上传再用新 id)、coverIndex 超过了图片张数;对导入的答案卡调 savePages / generate 也会报这个码,这是不允许的操作,不要重试。",
82
+ "PLAN_STALE": "确认期间这条任务(脚本或发布资料)被别人改过。重新 answerCards.get 读当前内容、重新 plan,把新摘要再给用户确认一次,不要拿旧 planId 重试。导入时报这个码,是确认期间有图片被停用/删除/处理失败:重新生成计划,给用户确认后再执行。",
75
83
  "PLAN_EXPIRED": "计划超过 30 分钟没执行。重新 plan,把新摘要再给用户确认一次——旧 planId 已经不能用了。",
76
- "PLAN_COST_CHANGED": "generate 在 apply 时发现要出的页数和确认时不一样(多半是确认期间脚本被改过),或者单价变了。重新 plan,把新的页数和费用说明再念给用户确认一次,不要直接重试 apply。",
77
- "QUOTA_EXCEEDED": "账户余额(积分/算力)不足,generate 真扣费的这一步最容易撞到。把 message 原样转达给用户,让他确认账户余额或联系管理员充值,不要重试 apply——余额不够重试也不会成功。",
84
+ "PLAN_COST_CHANGED": "generate 在 apply 时发现要出的页数和确认时不一样(多半是确认期间脚本被改过),或者单价变了;import 报这个码是基础费价格变了。重新 plan,把新的页数和费用说明再念给用户确认一次,不要直接重试 apply。",
85
+ "QUOTA_EXCEEDED": "账户余额(积分/算力)不足,generate 真扣费的这一步最容易撞到,import 扣基础费时也可能撞到。把 message 原样转达给用户,让他确认账户余额或联系管理员充值,不要重试 apply——余额不够重试也不会成功。",
78
86
  "SERVER_ERROR": "服务端故障,稍后重试;反复出现请把 message 告知用户,不要自己判断原因。"
79
87
  },
80
- "completionCriteria": "查询类:已如实呈现任务/批次的真实状态,没跑完就说清它还在哪个阶段。写入类:answerCards.create/savePages/savePublishInfo 是不扣费的资料动作,用户看过 plan 摘要并同意后 apply 即可;answerCards.generate 必须让用户看清两笔费用并明确同意才能 apply,apply 成功后要告诉用户去哪查进度(answerCards.batch)。",
88
+ "completionCriteria": "查询类:已如实呈现任务/批次的真实状态,没跑完就说清它还在哪个阶段。写入类:answerCards.create/savePages/savePublishInfo 是不扣费的资料动作,用户看过 plan 摘要并同意后 apply 即可;answerCards.generate 必须让用户看清两笔费用并明确同意才能 apply,apply 成功后要告诉用户去哪查进度(answerCards.batch);answerCards.import 同样要让用户看清那笔基础费并明确同意后才能 apply,apply 成功后答案卡直接是已完成。",
81
89
  "writeFlow": {
82
90
  "title": "写数据:先 plan,用户同意后才 apply",
83
91
  "steps": [
84
92
  "执行 best-geo plan answerCards.<operation> --input '<JSON>',此时什么都没提交。",
85
- "把返回的 summary 原样念给用户听,answerCards.generate 的 notice 里的费用说明一条都不能漏。",
93
+ "把返回的 summary 原样念给用户听,answerCards.generate 和 answerCards.import 的 notice 里的费用说明一条都不能漏。",
86
94
  "等用户明确说同意。用户最初提的目标不算对这份摘要的确认。",
87
95
  "同意之后执行 best-geo apply <planId>。"
88
96
  ],
@@ -32,42 +32,48 @@
32
32
  },
33
33
  {
34
34
  "step": 5,
35
- "action": "建发布任务:publicationTasks.createArticle / createVideo",
35
+ "action": "建发布任务:publicationTasks.createArticle / createVideo / createImage",
36
36
  "capability": "publicationTasks.createArticle",
37
- "note": "**先 plan,把计划里逐条列的「哪篇发到哪个号、什么时候发」念给用户,等他确认再 apply**。执行之后稿子会真的发出去,CLI 撤不回来。aigc(要不要打 AI 创作声明)必须问用户,别替他决定。定时发布传 publishTime,不传就是立即发。费用按发布记录条数算——一篇发 3 个号就是 3 条,跟用户说清楚这个口径。**plan 阶段就会查字数**:报「不满足平台的字数要求」时,错误信息里点名了是哪篇文章投哪个平台、差多少字——把这句原样念给用户,让他定是改文章还是不发那个平台,别自己挑一个继续重试(这种失败重试多少次都过不去)。"
37
+ "note": "**先 plan,把计划里逐条列的「哪篇发到哪个号、什么时候发」念给用户,等他确认再 apply**。执行之后稿子会真的发出去,CLI 撤不回来。aigc(要不要打 AI 创作声明)发文章、视频时必须问用户,别替他决定;**图文不用问**,网页端图文已经没有这个声明,不传就是不打。定时发布传 publishTime,不传就是立即发。费用按发布记录条数算——一篇发 3 个号就是 3 条,跟用户说清楚这个口径。**plan 阶段就会查平台要求**(文章、视频、图文都查):报「不满足平台要求」时,错误信息里点名了是哪份内容投哪个平台、差在哪——把这句原样念给用户,让他定是改内容还是不发那个平台,别自己挑一个继续重试(这种失败重试多少次都过不去)。"
38
38
  },
39
39
  {
40
40
  "step": 6,
41
+ "action": "发图文:内容来自 AI 答案卡,用 publicationTasks.createImage",
42
+ "capability": "publicationTasks.createImage",
43
+ "note": "流程和发文章一样(先 plan、念逐条明细、用户确认再 apply),区别在三处:① 平台用 publicationPlatforms.list 带 contentType=image,图文平台和文章、视频平台是三批,混用会被拒;② 每项给的是 answerCardTaskId 而不是 articleId,**挑答案卡要选 status=8(已完成)且 publishTitle 不为空的**——还在生成、部分失败或没填发布标题的发不了,缺标题先用 answerCards.savePublishInfo 补;③ 图片张数、标题和描述长度由平台规则校验,张数上限以 plan 的报错为准,同一张卡发不同平台,张数上限可能不同,plan 报「不满足平台要求」时把错误原样念给用户,让他定改卡还是不发那个平台。发出去之后在 publicationRecords.list 里 contentType=image,title 是答案卡的发布标题,answerCardTaskId 是来源答案卡。"
44
+ },
45
+ {
46
+ "step": 7,
41
47
  "action": "任务建完后查进展,别反复重建",
42
48
  "capability": "publicationRecords.list",
43
49
  "note": "发布是异步的,apply 完不会马上有链接。等一会儿用 publicationRecords.list 看。每建一次任务就是一批新的发布,重建 = 重复发稿 + 重复扣费。"
44
50
  },
45
51
  {
46
- "step": 7,
52
+ "step": 8,
47
53
  "action": "要停下来用 publicationTasks.pause,改主意了用 delete",
48
54
  "capability": "publicationTasks.pause",
49
55
  "note": "两个都只对**还没发出去**的记录有效(删除会连暂停中的一起删掉)。已经发成功的稿子留在平台上,CLI 撤不回来——用户要撤那些,只能去各平台自己删,这一点必须说明白,别让他以为暂停就等于撤回。另外建任务时费用就按记录条数扣过了,删除不改变这笔扣费,用户点删除前得知道。**例外是外部导入的任务**(typeLabel=外部导入):它不能暂停或恢复;删除时会连同名下已发布的记录一起删掉,收录统计也会跟着减少,计划摘要会单独写出来,一定要念给用户。"
50
56
  },
51
57
  {
52
- "step": 8,
58
+ "step": 9,
53
59
  "action": "媒体投稿:先 mediaPlatforms.list 看有哪些媒体,报价前用 mediaPlatforms.get 查实时价",
54
60
  "capability": "mediaPlatforms.get",
55
61
  "note": "**报价必须用 get 那条**。列表是分页快照,遇上刚调过价就会和实际扣费对不上——用户按你报的数点了头,扣的却是另一个数。"
56
62
  },
57
63
  {
58
- "step": 9,
64
+ "step": 10,
59
65
  "action": "投稿:mediaPublications.create(一家)/ batchCreate(多家,最多 20)",
60
66
  "capability": "mediaPublications.batchCreate",
61
67
  "note": "**计划里逐家列出了媒体名和单价,要一家一家念给用户听,再给合计**——这是按明细收费的操作,用户确认的就是这份账单,只说「一共多少钱」不够。批量允许部分成功:某家失败不影响别家,失败的那家不扣费。一次只能投一篇文章,投多篇就一篇一篇来。"
62
68
  },
63
69
  {
64
- "step": 10,
70
+ "step": 11,
65
71
  "action": "查投稿进展用 mediaPublications.list,要整体情况用 mediaPublications.summary",
66
72
  "capability": "mediaPublications.summary",
67
73
  "note": "投稿动辄上百条。用户问「投出去的都怎么样了」直接用 summary,念 items 里的 statusLabel(「已拒稿 × 15」),别念 counts 里的数值。投稿有 failReason,被拒稿时把它念出来——这条链路和自媒体发布不同,是有失败原因的。**被拒稿的还要看 refundStatusLabel**:拒稿会原路退款,但退款偶尔会没退成(显示「待退款」,系统会自动补退)——用户问「钱退了吗」看的是这个字段,不是 status。"
68
74
  },
69
75
  {
70
- "step": 11,
76
+ "step": 12,
71
77
  "action": "用户在外面自己发过的文章,要算进收录统计:publicationTasks.importLinks",
72
78
  "capability": "publicationTasks.importLinks",
73
79
  "note": "这是**登记**,不是发布:不扣费,也不会往任何平台发东西。先问清这些链接发的是系统里已有的哪篇文章(用 articles.list 找到 articleId),不是的话问用户要标题(title)新建一篇外部文章——两个只给一个。plan 里逐条写明哪条能导、哪条跳过,**把跳过的和原因念给用户**,别只报「导入 N 条」。「网站暂不支持」的链接导进来也匹配不上收录,不要换个写法重试。导入后在 publicationTasks.list 里是一个「外部导入」任务,apply 结果里的 results 是逐条最终结果,以它为准告诉用户导进去了几条。"
@@ -77,7 +83,7 @@
77
83
  "分不清他要问的是自媒体发布还是媒体投稿",
78
84
  "他有多个 GEO 项目,而没说这次问的是哪个",
79
85
  "他说「发一下」但没说发哪篇、发到哪个平台、用哪个账号",
80
- "要不要打「AI 创作」声明(aigc)——这是他的合规选择,不能替他定",
86
+ "发文章、视频时要不要打「AI 创作」声明(aigc)——这是他的合规选择,不能替他定(图文不用问,默认不打)",
81
87
  "他说「明天发」这类相对时间,而你不确定具体是几点"
82
88
  ],
83
89
  "forbidden": [
@@ -91,7 +97,7 @@
91
97
  "不要把 status 数值念给用户,用 statusLabel",
92
98
  "不要说「暂停」或「删除任务」能撤回已经发出去的稿子——撤不了",
93
99
  "不要拿未审核或没封面的文章去发布/投稿,会被拒。挑文章时就用 status=1 + mustHaveCover=true 筛掉",
94
- "平台配了 limitConfig 时,不要把字数不达标的文章塞给那个平台——整批都建不了。字数改不改、还发不发那个平台,让用户定,别自己换一篇重试"
100
+ "平台配了 limitConfig 时,不要把字数不达标的文章、视频、答案卡塞给那个平台——整批都建不了。改不改内容、还发不发那个平台,让用户定,别自己换一份重试"
95
101
  ],
96
102
  "errorRecovery": [
97
103
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "best-geo-test",
3
- "version": "2.0.2",
3
+ "version": "2.0.4-beta.1",
4
4
  "description": "Best GEO CLI — 给本地 AI 助手调用的 GEO 命令行工具",
5
5
  "bin": {
6
6
  "best-geo-test": "dist/index.cjs"