@yixinkj/priority-buyer-alert-cli 0.1.52 → 0.1.56
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/changelog.json +66 -0
- package/index.js +44 -5
- package/launcher-core.js +68 -2
- package/package.json +2 -1
- package/skill/SKILL.md +13 -2
- package/skills.json +2 -2
package/changelog.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "随包发布的更新日志。启动器用它决定「这次更新要不要打断调用方」,并把 summary 打给模型看(只写 stderr,不要求转述给终端用户)。",
|
|
3
|
+
"//impact": "output = 只影响怎么读结果,本次命令仍然有效,照常执行;invocation = 影响怎么发命令,调用方手上那条命令可能已经失效,必须停下重读。缺省和写坏一律按 invocation 处理。发版时填不出 summary,多半说明这次压根不该动 SKILL.md 正文。",
|
|
4
|
+
"entries": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.1.56",
|
|
7
|
+
"impact": "output",
|
|
8
|
+
"summary": "买家在询价卡之后又发了图片或文件时,不会再把图片当成询价卡去读,需求内容不再丢失"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.55",
|
|
12
|
+
"impact": "output",
|
|
13
|
+
"summary": "卡片读不出内容时,首响清单里会带一段结构诊断,便于定位是哪一类卡片没支持"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"version": "0.1.54",
|
|
17
|
+
"impact": "output",
|
|
18
|
+
"summary": "定制询价卡不再把「款式和数量」这个栏目名当成买家要的数量;那张卡上的数字是产品起订量,不是买家自己填的量"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.1.53",
|
|
22
|
+
"impact": "output",
|
|
23
|
+
"summary": "版本更新不再每次打断:只有会影响命令怎么发的改动才要求重跑,其余照常执行并提示重读"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"version": "0.1.52",
|
|
27
|
+
"impact": "output",
|
|
28
|
+
"summary": "定制询价卡现在能读出数量、目标单价和定制规格;同一浏览器 Profile 开着多个消息中心页面时不再中断扫描"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"version": "0.1.51",
|
|
32
|
+
"impact": "output",
|
|
33
|
+
"summary": "卡片读不出内容时会带回结构诊断,便于定位是哪一类卡片不支持"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"version": "0.1.50",
|
|
37
|
+
"impact": "output",
|
|
38
|
+
"summary": "推送内容改成纯文本加粗,去掉表格和标题,飞书钉钉里不再显示成一堆符号"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"version": "0.1.49",
|
|
42
|
+
"impact": "output",
|
|
43
|
+
"summary": "业务员回复之后不会再被误报成没人回。以前询盘列表的时间没及时更新,预警会继续提醒几个小时"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"version": "0.1.48",
|
|
47
|
+
"impact": "output",
|
|
48
|
+
"summary": "清单为空时也会留下产物文件;跑完自动清掉中间文件,不再占磁盘"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"version": "0.1.47",
|
|
52
|
+
"impact": "output",
|
|
53
|
+
"summary": "摘要新增本轮清单条数。原来那个待办数不等于清单长度,容易被读成「本轮什么都没推」"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"version": "0.1.46",
|
|
57
|
+
"impact": "output",
|
|
58
|
+
"summary": "不再按消息条数给会话分档。真实询盘普遍超过原来的阈值,导致开场白几乎从不生成"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"version": "0.1.45",
|
|
62
|
+
"impact": "invocation",
|
|
63
|
+
"summary": "新询盘首响扫描的无人值守心跳现在必须带 --mark,否则同一批询盘每轮都会重复推送"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
package/index.js
CHANGED
|
@@ -48,6 +48,9 @@ const REPOSITORY_SKILL_PATH = path.join(
|
|
|
48
48
|
const BUNDLED_SKILL_PATH = fs.existsSync(PACKED_SKILL_PATH)
|
|
49
49
|
? PACKED_SKILL_PATH
|
|
50
50
|
: REPOSITORY_SKILL_PATH;
|
|
51
|
+
// 更新日志随包发布,启动器据此判断「这次更新要不要打断调用方」。
|
|
52
|
+
// 缺文件、读不出、区间为空时一律按最保守的 invocation 处理,见 core.readChangelog。
|
|
53
|
+
const CHANGELOG_PATH = path.join(PACKAGE_ROOT, 'changelog.json');
|
|
51
54
|
const SKILL_VERSION_MARKER = `.${SKILL_ID}-version.json`;
|
|
52
55
|
const SKILL_ROOT = path.join(os.homedir(), '.yixin', SKILL_ID);
|
|
53
56
|
const RUNTIME_ROOT = path.join(SKILL_ROOT, 'runtime');
|
|
@@ -194,7 +197,10 @@ const resolveSkillGate = (skillPath, { homeDir = os.homedir(), syncOptions = {}
|
|
|
194
197
|
* @param {object} [options] 测试注入的内置文件路径。
|
|
195
198
|
* @returns {object} 同步结论与版本信息。
|
|
196
199
|
*/
|
|
197
|
-
function syncSkillInstallation(
|
|
200
|
+
function syncSkillInstallation(
|
|
201
|
+
skillPath,
|
|
202
|
+
{ bundledSkillPath = BUNDLED_SKILL_PATH, changelogPath = CHANGELOG_PATH } = {}
|
|
203
|
+
) {
|
|
198
204
|
const resolvedSkillPath = core.resolveSkillMdPath(skillPath);
|
|
199
205
|
const { content: bundledSkill, metadata: bundledMetadata } = core.loadBundledSkill(bundledSkillPath);
|
|
200
206
|
// 内核只检查包自身完整性(文件在不在、name/description 有没有),身份判定留在本仓。
|
|
@@ -281,15 +287,42 @@ function syncSkillInstallation(skillPath, { bundledSkillPath = BUNDLED_SKILL_PAT
|
|
|
281
287
|
}
|
|
282
288
|
});
|
|
283
289
|
|
|
290
|
+
// 判据是**正文**变没变,不是文件变没变:frontmatter 的版本号每次发布必然变,
|
|
291
|
+
// 拿整份文件比对等于「每次发布都打断一次」。真机一周 4 次更新,其中 2 次正文一个字
|
|
292
|
+
// 没动,却照样把调用方挡回去重跑,换来零信息。
|
|
293
|
+
const bodyChanged = core.skillBody(currentSkill) !== core.skillBody(bundledSkill);
|
|
294
|
+
const changelog = core.readChangelog(changelogPath, currentVersion, SKILL_VERSION);
|
|
295
|
+
// 正文没变 → 无条件放行。正文变了 → 由日志里的 impact 决定:
|
|
296
|
+
// 只影响「怎么读结果」就放行(调用方读到提示后自己重读 SKILL.md 再解读产物),
|
|
297
|
+
// 影响「怎么发命令」就必须停——那条命令是读着旧说明构造的,可能已经失效。
|
|
298
|
+
const blocking = bodyChanged && changelog.impact !== 'output';
|
|
284
299
|
return {
|
|
285
|
-
action:
|
|
300
|
+
action: blocking ? 'updated' : 'synced',
|
|
286
301
|
skillPath: resolvedSkillPath,
|
|
287
302
|
currentVersion,
|
|
288
303
|
targetVersion: SKILL_VERSION,
|
|
289
|
-
registryPath: registryPreview.registryPath
|
|
304
|
+
registryPath: registryPreview.registryPath,
|
|
305
|
+
bodyChanged,
|
|
306
|
+
changelog
|
|
290
307
|
};
|
|
291
308
|
}
|
|
292
309
|
|
|
310
|
+
/// 把「已更新、请重读」这件事说给调用方听,但**不打断本次执行**。
|
|
311
|
+
///
|
|
312
|
+
/// 只写 stderr:stdout 是产物 JSON,混进去会让调用方解析失败。也刻意不要求转述给终端
|
|
313
|
+
/// 用户——重读 SKILL.md 是模型自己的事,业务员不需要每次更新都被告知一遍。
|
|
314
|
+
function reportSkillSynced(sync) {
|
|
315
|
+
if (!sync || !sync.bodyChanged) return;
|
|
316
|
+
const entries = (sync.changelog && sync.changelog.entries) || [];
|
|
317
|
+
console.error(
|
|
318
|
+
`[${SKILL_ID}] 已更新到 ${sync.targetVersion}。本次结果请按新版说明解读,`
|
|
319
|
+
+ `下一步动作前重新读一次 SKILL.md。`
|
|
320
|
+
);
|
|
321
|
+
for (const entry of entries) {
|
|
322
|
+
if (entry && entry.summary) console.error(`[${SKILL_ID}] ${entry.summary}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
293
326
|
/** 帮助文案是本仓的命令面,不进共享内核。 */
|
|
294
327
|
function printHelp() {
|
|
295
328
|
process.stdout.write(`${SKILL_DISPLAY_NAME} ${SKILL_VERSION}(Runtime ${RUNTIME_VERSION})
|
|
@@ -379,16 +412,22 @@ async function main() {
|
|
|
379
412
|
}
|
|
380
413
|
emitStatus(
|
|
381
414
|
'skill_updated',
|
|
382
|
-
`${SKILL_DISPLAY_NAME}已更新至 ${SKILL_VERSION}
|
|
415
|
+
`${SKILL_DISPLAY_NAME}已更新至 ${SKILL_VERSION}。本次改动会影响命令怎么发,`
|
|
416
|
+
+ `请重新读一次 SKILL.md 再按新版执行;本次不执行扫描。`,
|
|
383
417
|
{
|
|
384
418
|
skill_path: gate.sync.skillPath,
|
|
385
419
|
previous_version: gate.sync.currentVersion || null,
|
|
386
420
|
skills_registry: gate.sync.registryPath || null,
|
|
387
|
-
restart_required: true
|
|
421
|
+
restart_required: true,
|
|
422
|
+
changes: ((gate.sync.changelog && gate.sync.changelog.entries) || [])
|
|
423
|
+
.map((entry) => entry.summary)
|
|
424
|
+
.filter(Boolean)
|
|
388
425
|
}
|
|
389
426
|
);
|
|
390
427
|
return;
|
|
391
428
|
}
|
|
429
|
+
// 放行的更新只提示、不打断:正文没变,或者改动只影响怎么读结果。
|
|
430
|
+
reportSkillSynced(gate.sync);
|
|
392
431
|
|
|
393
432
|
const runtimeOverride = process.env.PRIORITY_BUYER_ALERT_RUNTIME_BINARY;
|
|
394
433
|
if (runtimeOverride) {
|
package/launcher-core.js
CHANGED
|
@@ -629,7 +629,7 @@ function resolveSkillGate(skillPath, { syncSkill, discoverSkillPaths, discovered
|
|
|
629
629
|
}
|
|
630
630
|
if (skillPath) {
|
|
631
631
|
const sync = syncSkill(skillPath);
|
|
632
|
-
return { allowRun: sync.action
|
|
632
|
+
return { allowRun: isRunnableSyncAction(sync.action), sync };
|
|
633
633
|
}
|
|
634
634
|
const candidates = discoverSkillPaths();
|
|
635
635
|
if (candidates.length !== 1) {
|
|
@@ -643,12 +643,76 @@ function resolveSkillGate(skillPath, { syncSkill, discoverSkillPaths, discovered
|
|
|
643
643
|
if (discoveredPathPolicy === 'require_argument') {
|
|
644
644
|
return {
|
|
645
645
|
allowRun: false,
|
|
646
|
-
sync: {
|
|
646
|
+
sync: {
|
|
647
|
+
...sync,
|
|
648
|
+
action: 'skill_path_required',
|
|
649
|
+
candidates,
|
|
650
|
+
synced: !isRunnableSyncAction(sync.action)
|
|
651
|
+
}
|
|
647
652
|
};
|
|
648
653
|
}
|
|
649
654
|
return { allowRun: false, sync: { ...sync, action: 'restart_required' } };
|
|
650
655
|
}
|
|
651
656
|
|
|
657
|
+
/// 同步之后仍然可以继续执行本次命令吗。
|
|
658
|
+
///
|
|
659
|
+
/// `current` 是什么都没变;`synced` 是变了但**不影响本次这条命令**——版本号单独升、
|
|
660
|
+
/// 或者改动只影响「怎么读结果」。两者都放行,调用方读到提示后自己重读 SKILL.md 即可。
|
|
661
|
+
///
|
|
662
|
+
/// 真机数据:一周里 4 次更新有 2 次正文一个字没动,只是 frontmatter 的版本号跟着升了,
|
|
663
|
+
/// 那 2 次全都把调用方挡回去重跑一遍,换来零信息。
|
|
664
|
+
///
|
|
665
|
+
/// 写成函数而不是模块级 Set,是为了不动内核那条「模块级只允许一个策略常量」的守卫——
|
|
666
|
+
/// 那条守卫是 SKILL_ID 事故留下的,不该为了少写两个字就把它放宽。
|
|
667
|
+
function isRunnableSyncAction(action) {
|
|
668
|
+
return action === 'current' || action === 'synced';
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/// 剥掉 frontmatter 里的 `version:` 行,用来判断**正文**变没变。
|
|
672
|
+
///
|
|
673
|
+
/// 版本号每次发布必然变,拿整份文件比对等于「每次发布都算正文变了」。而真正决定要不要
|
|
674
|
+
/// 打断调用方的,是那些指导它怎么做的文字有没有变。
|
|
675
|
+
function skillBody(content) {
|
|
676
|
+
return String(content ?? '').replace(/^version:.*$/m, '');
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/// 取 `(fromVersion, toVersion]` 区间内的更新日志条目。
|
|
680
|
+
///
|
|
681
|
+
/// `impact` 决定要不要打断本次调用,只有两个取值:
|
|
682
|
+
/// - `output` —— 只影响「怎么读结果」。本次命令仍然有效,照常执行,事后重读即可。
|
|
683
|
+
/// - `invocation` —— 影响「怎么发命令」。调用方是读着旧说明构造的这条命令,它可能已经
|
|
684
|
+
/// 失效,必须停下重来。
|
|
685
|
+
///
|
|
686
|
+
/// **缺省和读不出来一律按 `invocation`。** 真机上栽过一次:SKILL.md 从「`--mark` 只配
|
|
687
|
+
/// `--start`」改成「首响心跳也必须带 `--mark`」,按旧说明构造的命令没有 `--mark`,
|
|
688
|
+
/// 放行执行的结果是水位不记账、同一批新询盘每轮重复推送——不报错,只是结果是错的。
|
|
689
|
+
/// 默认成放行的话,漏标一次就是这种静默出错,而且没人会发现。
|
|
690
|
+
function readChangelog(changelogPath, fromVersion, toVersion) {
|
|
691
|
+
const fallback = { impact: 'invocation', entries: [] };
|
|
692
|
+
if (!changelogPath || !fs.existsSync(changelogPath)) return fallback;
|
|
693
|
+
let data;
|
|
694
|
+
try {
|
|
695
|
+
data = JSON.parse(fs.readFileSync(changelogPath, 'utf8'));
|
|
696
|
+
} catch {
|
|
697
|
+
return fallback;
|
|
698
|
+
}
|
|
699
|
+
const all = Array.isArray(data) ? data : Array.isArray(data.entries) ? data.entries : null;
|
|
700
|
+
if (!all) return fallback;
|
|
701
|
+
const entries = all.filter((entry) => {
|
|
702
|
+
const version = entry && typeof entry.version === 'string' ? entry.version : '';
|
|
703
|
+
if (!version) return false;
|
|
704
|
+
// 左开右闭:本地已经是 from 那一版,它的改动不必再说一遍。
|
|
705
|
+
return (
|
|
706
|
+
(!fromVersion || compareVersions(version, fromVersion) > 0) &&
|
|
707
|
+
compareVersions(version, toVersion) <= 0
|
|
708
|
+
);
|
|
709
|
+
});
|
|
710
|
+
if (!entries.length) return fallback;
|
|
711
|
+
// 区间里只要有一条影响调用方式,整段就必须按 invocation 处理。
|
|
712
|
+
const impact = entries.some((entry) => entry.impact !== 'output') ? 'invocation' : 'output';
|
|
713
|
+
return { impact, entries };
|
|
714
|
+
}
|
|
715
|
+
|
|
652
716
|
/* ------------------------------------------------------------------ *
|
|
653
717
|
* 六、平台识别与下载源
|
|
654
718
|
* ------------------------------------------------------------------ */
|
|
@@ -1439,6 +1503,8 @@ export {
|
|
|
1439
1503
|
reportPrunedRuntimeVersions,
|
|
1440
1504
|
resolveReleaseBaseUrls,
|
|
1441
1505
|
resolveSkillGate,
|
|
1506
|
+
readChangelog,
|
|
1507
|
+
skillBody,
|
|
1442
1508
|
resolveSkillMdPath,
|
|
1443
1509
|
restoreFile,
|
|
1444
1510
|
restoreTree,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yixinkj/priority-buyer-alert-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56",
|
|
4
4
|
"description": "重点买家未回复预警独立启动器,只读扫描询盘并提醒,不发送任何消息。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"index.js",
|
|
16
16
|
"launcher-core.js",
|
|
17
|
+
"changelog.json",
|
|
17
18
|
"skills.json",
|
|
18
19
|
"skill"
|
|
19
20
|
],
|
package/skill/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: 哨兵预警-0812
|
|
3
3
|
description: 重点买家未回复预警。只读扫描阿里国际站询盘列表,挑出重点买家(金品店铺为 L2 及以上真实档;出口通店铺只有 L1+ 一个值,它是 L1–L4 的合集,必然包含 L1),判断买家最后一句是否超过 SLA 仍没有真人业务员回复,并输出中文预警清单;机器人自动接待不算人工回复。Use when 用户要求检查重点买家有没有超时未回复、查询盘超时提醒、看 L3/L4 有没有快凉的,或要求运行重点买家预警。
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.56"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# 重点买家预警
|
|
@@ -26,6 +26,13 @@ npx -y @yixinkj/priority-buyer-alert-cli@latest \
|
|
|
26
26
|
|
|
27
27
|
启动成功后立即记录返回的 `run_id`,此后只用同一个 `run_id` 轮询,不要再次执行 `--start`。
|
|
28
28
|
|
|
29
|
+
**关于版本更新的两种提示,处理方式不同:**
|
|
30
|
+
|
|
31
|
+
- stderr 出现「已更新到 x.y.z,本次结果请按新版说明解读」——**本次命令照常执行完了,结果有效**。
|
|
32
|
+
下一步动作之前重新读一次 SKILL.md 即可,不要重跑这条命令。
|
|
33
|
+
- 返回 `skill_updated`——本次**没有执行**。这类更新会影响命令怎么发,手上这条可能已经失效,
|
|
34
|
+
必须重新读 SKILL.md 再按新版重跑。
|
|
35
|
+
|
|
29
36
|
预警扫描参数(与 `--start` 一起使用):
|
|
30
37
|
|
|
31
38
|
- `--minutes 30`:SLA,超过多少分钟没有人工回复算超时。默认 30,严格大于才触发。
|
|
@@ -220,7 +227,11 @@ npx -y @yixinkj/priority-buyer-alert-cli@latest \
|
|
|
220
227
|
猜他要什么。
|
|
221
228
|
|
|
222
229
|
首响清单里除了这句摘要,还有结构化的 `last_buyer_card`:`quantity`、`product_moq`、
|
|
223
|
-
`product_unit_price`、`detail_requirement
|
|
230
|
+
`product_unit_price`、`detail_requirement`(买家自己写的需求原文)。
|
|
231
|
+
|
|
232
|
+
**`quantity` 为空不等于买家没说数量。**定制询价卡上通常只有产品的最低起订量,没有买家
|
|
233
|
+
自己填的数量,这时 `quantity` 是空的、数字在 `product_moq` 里。转述时要说成「这款起订量
|
|
234
|
+
500」,不要说成「他要 500 个」——那是两回事,说错了买家一眼看得出你没看卡。**把清单文件直接交给
|
|
224
235
|
首响 Skill**,它用 `--from-scan` 就能拿到这些,不必为同样的数据再开一次详情页——
|
|
225
236
|
两边调的本来就是同一个接口。没有卡片时该字段为空,首响 Skill 会自己回落去抓。
|
|
226
237
|
|
package/skills.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"runtimeVersion": "0.1.
|
|
2
|
+
"runtimeVersion": "0.1.45",
|
|
3
3
|
"platforms": {
|
|
4
4
|
"darwin-arm64": {
|
|
5
5
|
"id": "mac-arm64",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"skill": {
|
|
25
25
|
"name": "哨兵预警-0812",
|
|
26
26
|
"id": "priority-buyer-alert",
|
|
27
|
-
"version": "0.1.
|
|
27
|
+
"version": "0.1.56",
|
|
28
28
|
"bin": "priority-buyer-alert-cli"
|
|
29
29
|
}
|
|
30
30
|
}
|