prompt-skill-armory 0.9.3 → 0.9.7

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/README.en.md CHANGED
@@ -12,13 +12,13 @@
12
12
  |--|--|--|--|
13
13
  | <img src="docs/preview/preview-6.png" width="220"/> | <img src="docs/preview/preview-7.png" width="220"/> | | |
14
14
 
15
- ![version](https://img.shields.io/badge/version-0.9.3-00ff9c)
15
+ ![version](https://img.shields.io/badge/version-0.9.7-00ff9c)
16
16
  ![license](https://img.shields.io/badge/license-MIT-blue)
17
17
  ![platform](https://img.shields.io/badge/platform-web%20%2B%20desktop-00ff9c)
18
18
  ![CI](https://github.com/Qian-Ning/prompt-skill-armory/actions/workflows/ci.yml/badge.svg)
19
19
 
20
20
  ```
21
- ✦ Armory v0.9.3
21
+ ✦ Armory v0.9.7
22
22
  ┌─────────┬─────────┬─────┬────────────┬──────┬──────┐
23
23
  │ Prompts │ Skills │ MCP │ Wallpaper │ Chat │ Stats│
24
24
  │ global │ merged │ hub │ wallpaper │ sync │ usage│
@@ -111,7 +111,7 @@ Achieved via profile composition (the installer mounts the panel into each profi
111
111
 
112
112
  Every time the panel opens it queries npm for the latest `prompt-skill-armory` version; when a newer version exists a banner appears:
113
113
 
114
- > New version vX.Y.Z (current v0.9.3) [Update now]
114
+ > New version vX.Y.Z (current v0.9.7) [Update now]
115
115
 
116
116
  Clicking "Update now" makes the Host re-run the official installer in place → "Update complete, please restart the client".
117
117
 
package/README.md CHANGED
@@ -12,13 +12,13 @@
12
12
  |--|--|--|--|
13
13
  | <img src="docs/preview/preview-6.png" width="220"/> | <img src="docs/preview/preview-7.png" width="220"/> | | |
14
14
 
15
- ![version](https://img.shields.io/badge/version-0.9.3-00ff9c)
15
+ ![version](https://img.shields.io/badge/version-0.9.7-00ff9c)
16
16
  ![license](https://img.shields.io/badge/license-MIT-blue)
17
17
  ![platform](https://img.shields.io/badge/platform-web%20%2B%20desktop-00ff9c)
18
18
  ![CI](https://github.com/Qian-Ning/prompt-skill-armory/actions/workflows/ci.yml/badge.svg)
19
19
 
20
20
  ```
21
- ✦ Armory v0.9.3
21
+ ✦ Armory v0.9.7
22
22
  ┌─────────┬─────────┬─────┬────────────┬──────┬──────┐
23
23
  │ 提示词 │ 技能 │ MCP │ Wallpaper │ 对话 │ 统计 │
24
24
  │ 全局注入 │ 合并管理 │ 连接 │ 壁纸·特效 │ 导入 │ 用量 │
@@ -114,7 +114,7 @@ pnpm dsh web
114
114
 
115
115
  插件面板每次打开会自动查询 npm 上 `prompt-skill-armory` 的最新版本;发现新版本时顶部显示横幅:
116
116
 
117
- > 有新版本 vX.Y.Z(当前 v0.9.3) [一键更新]
117
+ > 有新版本 vX.Y.Z(当前 v0.9.7) [一键更新]
118
118
 
119
119
  点击「一键更新」→ Host 端运行官方安装器原地重装 → 提示「更新完成,请重启客户端生效」。
120
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-skill-armory",
3
- "version": "0.9.3",
3
+ "version": "0.9.7",
4
4
  "description": "One-command installer for Prompt-SkillArmory (prompts + skills manager) into the DeepSeek Harness (web + desktop)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -539,15 +539,22 @@ window.__ModuleLoader__.load({
539
539
  }
540
540
  }
541
541
  async function downloadExport(name) {
542
- const blob = await (await fetch(`/api/armory/export/${name}`)).blob();
543
- const url = URL.createObjectURL(blob);
544
- const a = document.createElement("a");
545
- a.href = url;
546
- a.download = name;
547
- document.body.appendChild(a);
548
- a.click();
549
- a.remove();
550
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
542
+ try {
543
+ const r = await fetch(`/api/armory/export/${name}`);
544
+ if (!r.ok) return false;
545
+ const blob = await r.blob();
546
+ const url = URL.createObjectURL(blob);
547
+ const a = document.createElement("a");
548
+ a.href = url;
549
+ a.download = name;
550
+ document.body.appendChild(a);
551
+ a.click();
552
+ a.remove();
553
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
554
+ return true;
555
+ } catch {
556
+ return false;
557
+ }
551
558
  }
552
559
  async function deleteConversations(ids) {
553
560
  try {
@@ -556,7 +563,11 @@ window.__ModuleLoader__.load({
556
563
  headers: { "content-type": "application/json" },
557
564
  body: JSON.stringify({ sessionIds: ids })
558
565
  })).json();
559
- return b.ok ? b.deleted ?? 0 : null;
566
+ if (!b.ok) return null;
567
+ return {
568
+ deleted: b.deleted ?? 0,
569
+ failed: Array.isArray(b.failed) ? b.failed : []
570
+ };
560
571
  } catch {
561
572
  return null;
562
573
  }
@@ -947,7 +958,7 @@ window.__ModuleLoader__.load({
947
958
  });
948
959
  }
949
960
  /** Bump with every release; keep in sync with package.json version + CHANGELOG. */
950
- const ARMORY_VERSION = "0.9.3";
961
+ const ARMORY_VERSION = "0.9.7";
951
962
  /** Compact duration: 45.2s / 2m42s / 1h05m. */
952
963
  function fmtDuration(ms) {
953
964
  const s = ms / 1e3;
@@ -991,13 +1002,29 @@ window.__ModuleLoader__.load({
991
1002
  children: "暂无趋势数据"
992
1003
  });
993
1004
  const maxSteps = Math.max(...byDay.map((d) => d.steps), 1);
994
- const maxTokens = Math.max(...byDay.map((d) => d.outputTokens), 1);
1005
+ const maxTokens = Math.max(...byDay.map((d) => d.outputTokens), ...byDay.map((d) => d.inputTokens), ...byDay.map((d) => d.cacheReadTokens), ...byDay.map((d) => d.cacheWriteTokens), 1);
1006
+ const logBase = Math.max(Math.log10(maxTokens), 1);
995
1007
  const x = (i) => PAD.l + (n === 1 ? iw / 2 : i / (n - 1) * iw);
996
1008
  const ySteps = (v) => PAD.t + ih - v / maxSteps * ih;
997
- const yTok = (v) => PAD.t + ih - v / maxTokens * ih;
1009
+ const yTok = (v) => {
1010
+ const vv = Math.max(v, 1);
1011
+ return PAD.t + ih - Math.log10(vv) / logBase * ih;
1012
+ };
998
1013
  const pathSteps = byDay.map((d, i) => `${i === 0 ? "M" : "L"}${x(i).toFixed(1)},${ySteps(d.steps).toFixed(1)}`).join(" ");
999
1014
  const pathOut = byDay.map((d, i) => `${i === 0 ? "M" : "L"}${x(i).toFixed(1)},${yTok(d.outputTokens).toFixed(1)}`).join(" ");
1000
1015
  const pathIn = byDay.map((d, i) => `${i === 0 ? "M" : "L"}${x(i).toFixed(1)},${yTok(d.inputTokens).toFixed(1)}`).join(" ");
1016
+ const tokTicks = [
1017
+ 0,
1018
+ .25,
1019
+ .5,
1020
+ .75,
1021
+ 1
1022
+ ].map((f) => Math.pow(10, f * logBase));
1023
+ const niceToken = (v) => {
1024
+ if (v >= 1e6) return `${(v / 1e6).toFixed(v % 1e6 === 0 ? 0 : 1)}M`;
1025
+ if (v >= 1e3) return `${(v / 1e3).toFixed(v % 1e3 === 0 ? 0 : 1)}k`;
1026
+ return String(Math.round(v));
1027
+ };
1001
1028
  const grid = [
1002
1029
  0,
1003
1030
  1,
@@ -1039,12 +1066,15 @@ window.__ModuleLoader__.load({
1039
1066
  fill: "#8b949e",
1040
1067
  children: g.label
1041
1068
  })] }, i)),
1042
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1043
- x: W - PAD.r + 6,
1044
- y: PAD.t + 8,
1045
- fontSize: "9",
1046
- fill: "#3fb950",
1047
- children: fmtTokens(maxTokens)
1069
+ tokTicks.map((v, i) => {
1070
+ const yy = yTok(v);
1071
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1072
+ x: W - PAD.r + 6,
1073
+ y: yy + 3,
1074
+ fontSize: "9",
1075
+ fill: i === 4 ? "#3fb950" : "#8b949e",
1076
+ children: niceToken(v)
1077
+ }, i);
1048
1078
  }),
1049
1079
  byDay.map((d, i) => {
1050
1080
  if (n > 7 && i % Math.ceil(n / 7) !== 0 && i !== n - 1) return null;
@@ -1111,21 +1141,40 @@ window.__ModuleLoader__.load({
1111
1141
  stroke: "#0d1117",
1112
1142
  strokeWidth: "1"
1113
1143
  }),
1114
- n <= 4 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1115
- x: x(i),
1116
- y: ySteps(d.steps) - 6,
1117
- textAnchor: "middle",
1118
- fontSize: "8.5",
1119
- fill: "#d29922",
1120
- children: d.steps
1121
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1122
- x: x(i),
1123
- y: yTok(d.outputTokens) - 6,
1124
- textAnchor: "middle",
1125
- fontSize: "8.5",
1126
- fill: "#3fb950",
1127
- children: fmtTokens(d.outputTokens)
1128
- })] })
1144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1145
+ cx: x(i),
1146
+ cy: yTok(d.inputTokens),
1147
+ r: hover === i ? 4 : 3,
1148
+ fill: "#58a6ff",
1149
+ stroke: "#0d1117",
1150
+ strokeWidth: "1"
1151
+ }),
1152
+ n <= 4 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1153
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1154
+ x: x(i),
1155
+ y: ySteps(d.steps) - 6,
1156
+ textAnchor: "middle",
1157
+ fontSize: "8.5",
1158
+ fill: "#d29922",
1159
+ children: d.steps
1160
+ }),
1161
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1162
+ x: x(i),
1163
+ y: yTok(d.outputTokens) - 6,
1164
+ textAnchor: "middle",
1165
+ fontSize: "8.5",
1166
+ fill: "#3fb950",
1167
+ children: fmtTokens(d.outputTokens)
1168
+ }),
1169
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1170
+ x: x(i),
1171
+ y: yTok(d.inputTokens) - 6,
1172
+ textAnchor: "middle",
1173
+ fontSize: "8.5",
1174
+ fill: "#58a6ff",
1175
+ children: fmtTokens(d.inputTokens)
1176
+ })
1177
+ ] })
1129
1178
  ] }, i))
1130
1179
  ]
1131
1180
  }),
@@ -1287,13 +1336,30 @@ window.__ModuleLoader__.load({
1287
1336
  const [updateMsg, setUpdateMsg] = (0, react.useState)("");
1288
1337
  const [stats, setStats] = (0, react.useState)(null);
1289
1338
  const [statsRange, setStatsRange] = (0, react.useState)("all");
1339
+ const [statsUpdatedAt, setStatsUpdatedAt] = (0, react.useState)(0);
1290
1340
  const reloadStats = async () => {
1291
- setStats(await fetchStats(statsRange));
1341
+ const s = await fetchStats(statsRange);
1342
+ if (s !== null) {
1343
+ setStats(s);
1344
+ setStatsUpdatedAt(Date.now());
1345
+ }
1292
1346
  };
1293
1347
  const changeStatsRange = (r) => {
1294
1348
  setStatsRange(r);
1295
- fetchStats(r).then(setStats);
1349
+ fetchStats(r).then((s) => {
1350
+ if (s !== null) {
1351
+ setStats(s);
1352
+ setStatsUpdatedAt(Date.now());
1353
+ }
1354
+ });
1296
1355
  };
1356
+ (0, react.useEffect)(() => {
1357
+ if (activeTab !== "stats") return;
1358
+ const timer = setInterval(() => {
1359
+ reloadStats();
1360
+ }, 3e4);
1361
+ return () => clearInterval(timer);
1362
+ }, [activeTab, statsRange]);
1297
1363
  (0, react.useEffect)(() => {
1298
1364
  checkLatestVersion().then((v) => {
1299
1365
  if (v !== "") setLatestVer(v);
@@ -1331,15 +1397,19 @@ window.__ModuleLoader__.load({
1331
1397
  const doExportChat = async () => {
1332
1398
  setChatBusy(true);
1333
1399
  setChatMsg("");
1334
- const name = await exportConversations([...chatSelected]);
1335
- if (name === null) {
1400
+ try {
1401
+ const name = await exportConversations([...chatSelected]);
1402
+ if (name === null) {
1403
+ setChatMsg("导出失败");
1404
+ return;
1405
+ }
1406
+ const ok = await downloadExport(name);
1407
+ setChatMsg(ok ? `已导出 ${name}` : "导出失败:下载未完成");
1408
+ } catch {
1336
1409
  setChatMsg("导出失败");
1410
+ } finally {
1337
1411
  setChatBusy(false);
1338
- return;
1339
1412
  }
1340
- await downloadExport(name);
1341
- setChatMsg(`已导出 ${name}`);
1342
- setChatBusy(false);
1343
1413
  };
1344
1414
  const doExportProject = async () => {
1345
1415
  const key = chatProject.trim();
@@ -1349,15 +1419,19 @@ window.__ModuleLoader__.load({
1349
1419
  }
1350
1420
  setChatBusy(true);
1351
1421
  setChatMsg("");
1352
- const name = await exportConversations([], key);
1353
- if (name === null) {
1422
+ try {
1423
+ const name = await exportConversations([], key);
1424
+ if (name === null) {
1425
+ setChatMsg("导出失败");
1426
+ return;
1427
+ }
1428
+ const ok = await downloadExport(name);
1429
+ setChatMsg(ok ? `已导出整个项目 ${key} → ${name}` : "导出失败:下载未完成");
1430
+ } catch {
1354
1431
  setChatMsg("导出失败");
1432
+ } finally {
1355
1433
  setChatBusy(false);
1356
- return;
1357
1434
  }
1358
- await downloadExport(name);
1359
- setChatMsg(`已导出整个项目 ${key} → ${name}`);
1360
- setChatBusy(false);
1361
1435
  };
1362
1436
  const doUpdate = async () => {
1363
1437
  setUpdating(true);
@@ -1370,13 +1444,18 @@ window.__ModuleLoader__.load({
1370
1444
  if (file === void 0) return;
1371
1445
  setChatBusy(true);
1372
1446
  setChatMsg("");
1373
- const n = await importConversations(file, chatTarget.trim() || void 0);
1374
- setChatMsg(n === null ? "导入失败" : `已导入 ${n} 个对话,重启客户端后生效`);
1375
- if (n !== null) {
1376
- refreshSessions();
1377
- await reloadChat();
1447
+ try {
1448
+ const n = await importConversations(file, chatTarget.trim() || void 0);
1449
+ setChatMsg(n === null ? "导入失败" : `已导入 ${n} 个对话,重启客户端后生效`);
1450
+ if (n !== null) {
1451
+ refreshSessions();
1452
+ await reloadChat();
1453
+ }
1454
+ } catch {
1455
+ setChatMsg("导入失败");
1456
+ } finally {
1457
+ setChatBusy(false);
1378
1458
  }
1379
- setChatBusy(false);
1380
1459
  };
1381
1460
  const doDeleteChat = async () => {
1382
1461
  const ids = [...chatSelected];
@@ -1384,17 +1463,22 @@ window.__ModuleLoader__.load({
1384
1463
  if (!window.confirm(`确定删除选中的 ${ids.length} 个对话吗?此操作不可恢复。`)) return;
1385
1464
  setChatBusy(true);
1386
1465
  setChatMsg("");
1387
- const n = await deleteConversations(ids);
1388
- if (n === null) {
1466
+ try {
1467
+ const r = await deleteConversations(ids);
1468
+ if (r === null) {
1469
+ setChatMsg("删除失败");
1470
+ return;
1471
+ }
1472
+ setChatSelected(/* @__PURE__ */ new Set());
1473
+ const failMsg = r.failed.length > 0 ? `;${r.failed.length} 个删除失败(文件可能被占用,可稍后重试)` : "";
1474
+ setChatMsg(`已删除 ${r.deleted} 个对话${failMsg}`);
1475
+ refreshSessions();
1476
+ await reloadChat();
1477
+ } catch {
1389
1478
  setChatMsg("删除失败");
1479
+ } finally {
1390
1480
  setChatBusy(false);
1391
- return;
1392
1481
  }
1393
- setChatSelected(/* @__PURE__ */ new Set());
1394
- setChatMsg(`已删除 ${n} 个对话`);
1395
- refreshSessions();
1396
- await reloadChat();
1397
- setChatBusy(false);
1398
1482
  };
1399
1483
  (0, react.useEffect)(() => {
1400
1484
  const snap = backgroundClient.getSnapshot();
@@ -2526,10 +2610,14 @@ window.__ModuleLoader__.load({
2526
2610
  children: "导出整个项目"
2527
2611
  }),
2528
2612
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2529
- style: CSS.input,
2613
+ style: {
2614
+ ...CSS.input,
2615
+ maxWidth: "220px",
2616
+ textOverflow: "ellipsis"
2617
+ },
2530
2618
  value: chatProject,
2531
2619
  onChange: (e) => setChatProject(e.target.value),
2532
- disabled: chatBusy || chatProjects.length === 0,
2620
+ disabled: chatProjects.length === 0,
2533
2621
  title: "选择要整体导出的项目工作区",
2534
2622
  children: [chatProjects.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2535
2623
  value: "",
@@ -2620,8 +2708,13 @@ window.__ModuleLoader__.load({
2620
2708
  style: {
2621
2709
  ...CSS.badge,
2622
2710
  ...CSS.badgeDisabled,
2623
- flex: "none"
2711
+ flex: "none",
2712
+ maxWidth: "150px",
2713
+ overflow: "hidden",
2714
+ textOverflow: "ellipsis",
2715
+ whiteSpace: "nowrap"
2624
2716
  },
2717
+ title: row.cwd || row.projectKey,
2625
2718
  children: row.cwd ? row.cwd.split(/[\\/]/).filter(Boolean).pop() : row.projectKey
2626
2719
  })]
2627
2720
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2653,12 +2746,19 @@ window.__ModuleLoader__.load({
2653
2746
  gap: "8px",
2654
2747
  flexWrap: "wrap"
2655
2748
  },
2656
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2749
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2657
2750
  style: {
2658
2751
  fontSize: "13px",
2659
2752
  fontWeight: 600
2660
2753
  },
2661
- children: "使用统计"
2754
+ children: ["使用统计", statsUpdatedAt > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2755
+ style: {
2756
+ ...CSS.hint,
2757
+ marginLeft: "8px",
2758
+ fontWeight: 400
2759
+ },
2760
+ children: ["自动刷新 · 上次更新 ", new Date(statsUpdatedAt).toLocaleTimeString()]
2761
+ })]
2662
2762
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2663
2763
  style: {
2664
2764
  display: "flex",