prompt-skill-armory 0.9.2 → 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.2-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.2
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.2) [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.2-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.2
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.2) [一键更新]
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.2",
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": {
@@ -521,13 +521,14 @@ window.__ModuleLoader__.load({
521
521
  return [];
522
522
  }
523
523
  }
524
- async function exportConversations(ids) {
524
+ async function exportConversations(ids, projectKey) {
525
525
  try {
526
526
  const b = await (await fetch("/api/armory/export", {
527
527
  method: "POST",
528
528
  headers: { "content-type": "application/json" },
529
529
  body: JSON.stringify({
530
530
  sessionIds: ids,
531
+ projectKey,
531
532
  includeAttachments: false,
532
533
  includeWorkspace: false
533
534
  })
@@ -538,15 +539,22 @@ window.__ModuleLoader__.load({
538
539
  }
539
540
  }
540
541
  async function downloadExport(name) {
541
- const blob = await (await fetch(`/api/armory/export/${name}`)).blob();
542
- const url = URL.createObjectURL(blob);
543
- const a = document.createElement("a");
544
- a.href = url;
545
- a.download = name;
546
- document.body.appendChild(a);
547
- a.click();
548
- a.remove();
549
- 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
+ }
550
558
  }
551
559
  async function deleteConversations(ids) {
552
560
  try {
@@ -555,7 +563,11 @@ window.__ModuleLoader__.load({
555
563
  headers: { "content-type": "application/json" },
556
564
  body: JSON.stringify({ sessionIds: ids })
557
565
  })).json();
558
- 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
+ };
559
571
  } catch {
560
572
  return null;
561
573
  }
@@ -946,7 +958,7 @@ window.__ModuleLoader__.load({
946
958
  });
947
959
  }
948
960
  /** Bump with every release; keep in sync with package.json version + CHANGELOG. */
949
- const ARMORY_VERSION = "0.9.2";
961
+ const ARMORY_VERSION = "0.9.7";
950
962
  /** Compact duration: 45.2s / 2m42s / 1h05m. */
951
963
  function fmtDuration(ms) {
952
964
  const s = ms / 1e3;
@@ -990,13 +1002,29 @@ window.__ModuleLoader__.load({
990
1002
  children: "暂无趋势数据"
991
1003
  });
992
1004
  const maxSteps = Math.max(...byDay.map((d) => d.steps), 1);
993
- 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);
994
1007
  const x = (i) => PAD.l + (n === 1 ? iw / 2 : i / (n - 1) * iw);
995
1008
  const ySteps = (v) => PAD.t + ih - v / maxSteps * ih;
996
- 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
+ };
997
1013
  const pathSteps = byDay.map((d, i) => `${i === 0 ? "M" : "L"}${x(i).toFixed(1)},${ySteps(d.steps).toFixed(1)}`).join(" ");
998
1014
  const pathOut = byDay.map((d, i) => `${i === 0 ? "M" : "L"}${x(i).toFixed(1)},${yTok(d.outputTokens).toFixed(1)}`).join(" ");
999
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
+ };
1000
1028
  const grid = [
1001
1029
  0,
1002
1030
  1,
@@ -1038,12 +1066,15 @@ window.__ModuleLoader__.load({
1038
1066
  fill: "#8b949e",
1039
1067
  children: g.label
1040
1068
  })] }, i)),
1041
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1042
- x: W - PAD.r + 6,
1043
- y: PAD.t + 8,
1044
- fontSize: "9",
1045
- fill: "#3fb950",
1046
- 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);
1047
1078
  }),
1048
1079
  byDay.map((d, i) => {
1049
1080
  if (n > 7 && i % Math.ceil(n / 7) !== 0 && i !== n - 1) return null;
@@ -1110,21 +1141,40 @@ window.__ModuleLoader__.load({
1110
1141
  stroke: "#0d1117",
1111
1142
  strokeWidth: "1"
1112
1143
  }),
1113
- n <= 4 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1114
- x: x(i),
1115
- y: ySteps(d.steps) - 6,
1116
- textAnchor: "middle",
1117
- fontSize: "8.5",
1118
- fill: "#d29922",
1119
- children: d.steps
1120
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1121
- x: x(i),
1122
- y: yTok(d.outputTokens) - 6,
1123
- textAnchor: "middle",
1124
- fontSize: "8.5",
1125
- fill: "#3fb950",
1126
- children: fmtTokens(d.outputTokens)
1127
- })] })
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
+ ] })
1128
1178
  ] }, i))
1129
1179
  ]
1130
1180
  }),
@@ -1278,6 +1328,7 @@ window.__ModuleLoader__.load({
1278
1328
  const [chatRows, setChatRows] = (0, react.useState)([]);
1279
1329
  const [chatSelected, setChatSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
1280
1330
  const [chatTarget, setChatTarget] = (0, react.useState)("");
1331
+ const [chatProject, setChatProject] = (0, react.useState)("");
1281
1332
  const [chatBusy, setChatBusy] = (0, react.useState)(false);
1282
1333
  const [chatMsg, setChatMsg] = (0, react.useState)("");
1283
1334
  const [latestVer, setLatestVer] = (0, react.useState)("");
@@ -1285,13 +1336,30 @@ window.__ModuleLoader__.load({
1285
1336
  const [updateMsg, setUpdateMsg] = (0, react.useState)("");
1286
1337
  const [stats, setStats] = (0, react.useState)(null);
1287
1338
  const [statsRange, setStatsRange] = (0, react.useState)("all");
1339
+ const [statsUpdatedAt, setStatsUpdatedAt] = (0, react.useState)(0);
1288
1340
  const reloadStats = async () => {
1289
- setStats(await fetchStats(statsRange));
1341
+ const s = await fetchStats(statsRange);
1342
+ if (s !== null) {
1343
+ setStats(s);
1344
+ setStatsUpdatedAt(Date.now());
1345
+ }
1290
1346
  };
1291
1347
  const changeStatsRange = (r) => {
1292
1348
  setStatsRange(r);
1293
- fetchStats(r).then(setStats);
1349
+ fetchStats(r).then((s) => {
1350
+ if (s !== null) {
1351
+ setStats(s);
1352
+ setStatsUpdatedAt(Date.now());
1353
+ }
1354
+ });
1294
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]);
1295
1363
  (0, react.useEffect)(() => {
1296
1364
  checkLatestVersion().then((v) => {
1297
1365
  if (v !== "") setLatestVer(v);
@@ -1311,18 +1379,59 @@ window.__ModuleLoader__.load({
1311
1379
  return n;
1312
1380
  });
1313
1381
  };
1382
+ const chatProjects = (0, react.useMemo)(() => {
1383
+ const byKey = /* @__PURE__ */ new Map();
1384
+ for (const row of chatRows) {
1385
+ const name = row.cwd ? row.cwd.split(/[\\/]/).filter(Boolean).pop() ?? row.projectKey : row.projectKey;
1386
+ if (!byKey.has(row.projectKey)) byKey.set(row.projectKey, name);
1387
+ }
1388
+ return [...byKey.entries()].map(([key, name]) => ({
1389
+ key,
1390
+ name
1391
+ })).sort((a, b) => a.name.localeCompare(b.name));
1392
+ }, [chatRows]);
1393
+ (0, react.useEffect)(() => {
1394
+ if (chatProject === "" && chatProjects.length > 0) setChatProject(chatProjects[0].key);
1395
+ else if (chatProject !== "" && !chatProjects.some((p) => p.key === chatProject)) setChatProject(chatProjects[0]?.key ?? "");
1396
+ }, [chatProjects, chatProject]);
1314
1397
  const doExportChat = async () => {
1315
1398
  setChatBusy(true);
1316
1399
  setChatMsg("");
1317
- const name = await exportConversations([...chatSelected]);
1318
- 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 {
1319
1409
  setChatMsg("导出失败");
1410
+ } finally {
1320
1411
  setChatBusy(false);
1412
+ }
1413
+ };
1414
+ const doExportProject = async () => {
1415
+ const key = chatProject.trim();
1416
+ if (key === "") {
1417
+ setChatMsg("请先选择项目工作区");
1321
1418
  return;
1322
1419
  }
1323
- await downloadExport(name);
1324
- setChatMsg(`已导出 ${name}`);
1325
- setChatBusy(false);
1420
+ setChatBusy(true);
1421
+ setChatMsg("");
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 {
1431
+ setChatMsg("导出失败");
1432
+ } finally {
1433
+ setChatBusy(false);
1434
+ }
1326
1435
  };
1327
1436
  const doUpdate = async () => {
1328
1437
  setUpdating(true);
@@ -1335,13 +1444,18 @@ window.__ModuleLoader__.load({
1335
1444
  if (file === void 0) return;
1336
1445
  setChatBusy(true);
1337
1446
  setChatMsg("");
1338
- const n = await importConversations(file, chatTarget.trim() || void 0);
1339
- setChatMsg(n === null ? "导入失败" : `已导入 ${n} 个对话,重启客户端后生效`);
1340
- if (n !== null) {
1341
- refreshSessions();
1342
- 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);
1343
1458
  }
1344
- setChatBusy(false);
1345
1459
  };
1346
1460
  const doDeleteChat = async () => {
1347
1461
  const ids = [...chatSelected];
@@ -1349,17 +1463,22 @@ window.__ModuleLoader__.load({
1349
1463
  if (!window.confirm(`确定删除选中的 ${ids.length} 个对话吗?此操作不可恢复。`)) return;
1350
1464
  setChatBusy(true);
1351
1465
  setChatMsg("");
1352
- const n = await deleteConversations(ids);
1353
- 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 {
1354
1478
  setChatMsg("删除失败");
1479
+ } finally {
1355
1480
  setChatBusy(false);
1356
- return;
1357
1481
  }
1358
- setChatSelected(/* @__PURE__ */ new Set());
1359
- setChatMsg(`已删除 ${n} 个对话`);
1360
- refreshSessions();
1361
- await reloadChat();
1362
- setChatBusy(false);
1363
1482
  };
1364
1483
  (0, react.useEffect)(() => {
1365
1484
  const snap = backgroundClient.getSnapshot();
@@ -2468,7 +2587,7 @@ window.__ModuleLoader__.load({
2468
2587
  ...CSS.desc,
2469
2588
  lineHeight: "1.5"
2470
2589
  },
2471
- children: "勾选要导出的对话,打成一个 zip;在另一台机器选该 zip 导入即可还原(含附件与工作区)。"
2590
+ children: "可导出选中的对话,也可下拉选择某个项目工作区整体导出;导入后项目工作区名会尽量与导出端保持一致。"
2472
2591
  }),
2473
2592
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2474
2593
  style: CSS.actions,
@@ -2483,6 +2602,36 @@ window.__ModuleLoader__.load({
2483
2602
  ")"
2484
2603
  ]
2485
2604
  }),
2605
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2606
+ style: CSS.actionBtn,
2607
+ disabled: chatBusy || chatProject === "",
2608
+ onClick: () => void doExportProject(),
2609
+ title: "导出该项目工作区下的全部对话,导入后可保留项目名",
2610
+ children: "导出整个项目"
2611
+ }),
2612
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2613
+ style: {
2614
+ ...CSS.input,
2615
+ maxWidth: "220px",
2616
+ textOverflow: "ellipsis"
2617
+ },
2618
+ value: chatProject,
2619
+ onChange: (e) => setChatProject(e.target.value),
2620
+ disabled: chatProjects.length === 0,
2621
+ title: "选择要整体导出的项目工作区",
2622
+ children: [chatProjects.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2623
+ value: "",
2624
+ children: "(无项目)"
2625
+ }), chatProjects.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
2626
+ value: p.key,
2627
+ children: [
2628
+ p.name,
2629
+ "(",
2630
+ chatRows.filter((r) => r.projectKey === p.key).length,
2631
+ " 个对话)"
2632
+ ]
2633
+ }, p.key))]
2634
+ }),
2486
2635
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2487
2636
  style: {
2488
2637
  ...CSS.actionBtn,
@@ -2559,8 +2708,13 @@ window.__ModuleLoader__.load({
2559
2708
  style: {
2560
2709
  ...CSS.badge,
2561
2710
  ...CSS.badgeDisabled,
2562
- flex: "none"
2711
+ flex: "none",
2712
+ maxWidth: "150px",
2713
+ overflow: "hidden",
2714
+ textOverflow: "ellipsis",
2715
+ whiteSpace: "nowrap"
2563
2716
  },
2717
+ title: row.cwd || row.projectKey,
2564
2718
  children: row.cwd ? row.cwd.split(/[\\/]/).filter(Boolean).pop() : row.projectKey
2565
2719
  })]
2566
2720
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2592,12 +2746,19 @@ window.__ModuleLoader__.load({
2592
2746
  gap: "8px",
2593
2747
  flexWrap: "wrap"
2594
2748
  },
2595
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2749
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2596
2750
  style: {
2597
2751
  fontSize: "13px",
2598
2752
  fontWeight: 600
2599
2753
  },
2600
- 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
+ })]
2601
2762
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2602
2763
  style: {
2603
2764
  display: "flex",