dsh-prompt-customizer 0.2.6 → 0.3.0

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 DreamsTOF
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DreamsTOF
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) plugin that lets you control the **system prompt** and the **tool catalog** from a settings-panel UI.
6
6
 
7
- Plugin-injected prompt sections (from other plugins) can pollute your system prompt. This plugin lets you **block**, **replace**, **inject**, and **reorder** prompt sections by name, and **hide tools** from the model catalog — all live, without touching the other plugins.
7
+ Plugin-injected prompt sections can pollute your system prompt. This plugin lets you **block**, **replace**, **inject**, and **reorder** prompt sections by name, and **hide tools** from the model catalog — all live, without touching the other plugins.
8
+
9
+ ![Overview](4.png)
8
10
 
9
11
  ## Features
10
12
 
@@ -13,10 +15,18 @@ Plugin-injected prompt sections (from other plugins) can pollute your system pro
13
15
  - **Replace** a section's text (the original text is echoed for editing, including dynamically generated sections).
14
16
  - **Inject** brand-new sections.
15
17
  - **Reorder** sections with ↑/↓ arrows or **drag & drop** (HTML5). Order is stored as a virtual 0-based index, so there are never duplicate or fractional orders.
18
+
19
+ ![Prompt sections](2.png)
20
+
16
21
  - **Tools**
17
22
  - **Blacklist** (`exclude`): hide the listed tools.
18
23
  - **Whitelist** (`include`): keep only the listed tools (wins over exclude).
19
24
  - Only the model-facing catalog is affected — tools and routes keep working.
25
+
26
+ ![Tools](2-1.png)
27
+ ![Tools](2-2.png)
28
+ ![Tools](2-3.png)
29
+
20
30
  - **Presets**
21
31
  - **Save** the current customization as a preset (full snapshot).
22
32
  - **Apply** a preset: same-name sections are overridden, preset-only sections are added, and current sections outside the preset list are disabled by default.
@@ -24,6 +34,8 @@ Plugin-injected prompt sections (from other plugins) can pollute your system pro
24
34
  - Presets store **relative order** (each section records the section it follows), so they stay portable across prompts with different section sets.
25
35
  - Multiple presets can be stored locally; only one is active at a time.
26
36
 
37
+ ![Presets](3.png)
38
+
27
39
  ## Installation
28
40
 
29
41
  Install from npm (replace `web` with your profile name):
@@ -64,37 +76,6 @@ Toggle tools to hide them (blacklist), or switch to whitelist mode to keep only
64
76
  - **Export** — download the preset as JSON.
65
77
  - **Import** — load a preset JSON file (same-name presets are skipped).
66
78
 
67
- ## Configuration
68
-
69
- The plugin stores its config in the dsh settings document under the `prompt-customizer` namespace (`~/.dsh/settings.yaml`). You can edit it directly:
70
-
71
- ```yaml
72
- prompt-customizer:
73
- # Block these prompt sections by name.
74
- sections:
75
- - plugin:some-plugin
76
- # Replace a section's text by name (original order kept).
77
- replace:
78
- system: 'You are a helpful coding assistant.'
79
- # Inject / override sections (name + order + text).
80
- inject:
81
- - name: tool:read
82
- order: 0
83
- text: ''
84
- # Tool catalog filtering.
85
- tools:
86
- exclude: []
87
- include: []
88
- ```
89
-
90
- | Field | Type | Description |
91
- | --- | --- | --- |
92
- | `sections` | `string[]` | Prompt section names to block. |
93
- | `replace` | `Record<string, string>` | Replacement text per section name. |
94
- | `inject` | `{name, order, text}[]` | Sections to inject or override (order controls splice position). |
95
- | `tools.exclude` | `string[]` | Tool names to hide. |
96
- | `tools.include` | `string[]` | When non-empty, keep only these tools (wins over `exclude`). |
97
-
98
79
  ## Development
99
80
 
100
81
  ```bash
package/README.i18n.yaml CHANGED
@@ -1,2 +1,2 @@
1
- en: README.en.md
2
- zh-CN: README.md
1
+ en: README.en.md
2
+ zh-CN: README.md
package/README.md CHANGED
@@ -6,37 +6,44 @@
6
6
 
7
7
  其他插件注入的提示词段可能会污染你的系统提示词。本插件让你可以按名称**屏蔽**、**替换**、**注入**和**排序**提示词段,并从模型目录中**隐藏工具**——全部实时生效,无需改动其他插件。
8
8
 
9
+ ![提示词段编辑界面](4.png)
10
+
9
11
  ## 功能特性
10
12
 
11
- - **提示词段**
12
- - **屏蔽**指定名称的段(从组装后的系统提示词中移除)。
13
- - **替换**某段的文本(编辑时会回显原始文本,包括动态生成的段)。
14
- - **注入**全新的段。
15
- - 通过 **↑/↓ 箭头**或**拖拽**(HTML5)排序。顺序以虚拟的 0 起始下标存储,因此不会出现重复或小数 order。
16
- - **工具**
17
- - **黑名单**(`exclude`):隐藏列出的工具。
18
- - **白名单**(`include`):仅保留列出的工具(优先于 exclude)。
19
- - 只影响面向模型的目录,工具与路由仍正常工作。
20
- - **预设**
21
- - **保存**当前定制为预设(完整快照)。
22
- - **应用**预设:同名段被覆盖、预设独有段被添加、预设名单之外的当前段默认被屏蔽。
23
- - **导出 / 导入**预设为 JSON(完整快照)。
24
- - 预设存储**相对顺序**(每段记录它跟在哪个段之后),因此可跨不同段集合的提示词移植。
25
- - 本地可保存多个预设,同一时间只能激活一个。
13
+ - **提示词段管理**
14
+ - **屏蔽**指定名称的段(从组装后的系统提示词中移除)
15
+ - **替换**某段的文本(编辑时会回显原始文本,包括动态生成的段)
16
+ - **注入**全新的段
17
+ - 通过 **↑/↓ 箭头**或**拖拽**(HTML5)排序。顺序以虚拟的 0 起始下标存储,因此不会出现重复或小数 order
18
+
19
+ ![提示词段管理界面](2.png)
20
+
21
+ - **工具过滤**
22
+ - 隐藏列出的工具
23
+ - 只影响面向模型的目录,工具与路由仍正常工作
24
+
25
+ ![工具过滤界面](2-1.png)
26
+ ![工具过滤界面](2-2.png)
27
+ ![工具过滤界面](2-3.png)
28
+
29
+ - **预设系统**
30
+ - **保存**当前定制为预设(完整快照)
31
+ - **应用**预设:同名段被覆盖、预设独有段被添加、预设名单之外的当前段默认被屏蔽
32
+ - **导出 / 导入**预设为 JSON(完整快照)
33
+ - 预设存储**相对顺序**(每段记录它跟在哪个段之后),因此可跨不同段集合的提示词移植
34
+ - 本地可保存多个预设,同一时间只能激活一个
35
+
36
+ ![预设系统界面](3.png)
26
37
 
27
38
  ## 安装
28
39
 
29
- 从 npm 安装(将 `web` 换成你的 profile 名):
40
+ 从 npm 安装:
30
41
 
31
42
  ```bash
32
43
  dsh plugin --profile web add dsh-prompt-customizer
33
44
  ```
34
45
 
35
- 或从本地目录安装:
36
-
37
- ```bash
38
- dsh plugin --profile web add /path/to/dsh-prompt-customizer
39
- ```
46
+ <br />
40
47
 
41
48
  安装后,打开 dsh Web UI → **设置** → 侧边栏中的 **提示词定制**。
42
49
 
@@ -46,10 +53,10 @@ dsh plugin --profile web add /path/to/dsh-prompt-customizer
46
53
 
47
54
  每一行代表一个提示词段,包含:
48
55
 
49
- - **复选框**:屏蔽 / 取消屏蔽。
50
- - **替换**按钮:编辑该段文本(默认回显原始文本)。
51
- - **↑/↓ 箭头**和**拖拽手柄**:调整顺序。
52
- - `#N` 徽标显示该段的虚拟顺序(0 起始的位置)。
56
+ - **复选框**:屏蔽 / 取消屏蔽
57
+ - **替换**按钮:编辑该段文本(默认回显原始文本)
58
+ - **↑/↓ 箭头**和**拖拽手柄**:调整顺序
59
+ - `#N` 徽标显示该段的虚拟顺序(0 起始的位置)
53
60
 
54
61
  底部的 **注入新段** 区域可按名称、顺序和文本添加全新段。
55
62
 
@@ -59,10 +66,10 @@ dsh plugin --profile web add /path/to/dsh-prompt-customizer
59
66
 
60
67
  ### 预设 Tab
61
68
 
62
- - **保存当前为预设**:捕获当前的提示词 / 工具定制。
63
- - **应用**:激活某个预设(覆盖同名段、添加预设独有段、屏蔽预设名单之外的当前段)。
64
- - **导出**:将预设下载为 JSON。
65
- - **导入**:加载预设 JSON 文件(同名预设会被跳过)。
69
+ - **保存当前为预设**:捕获当前的提示词 / 工具定制
70
+ - **应用**:激活某个预设(覆盖同名段、添加预设独有段、屏蔽预设名单之外的当前段)
71
+ - **导出**:将预设下载为 JSON
72
+ - **导入**:加载预设 JSON 文件(同名预设会被跳过)
66
73
 
67
74
  ## 配置
68
75
 
@@ -75,25 +82,25 @@ prompt-customizer:
75
82
  - plugin:some-plugin
76
83
  # 按名称替换某段文本(保留原始顺序)。
77
84
  replace:
78
- system: 'You are a helpful coding assistant.'
85
+ system: "You are a helpful coding assistant."
79
86
  # 注入 / 覆盖段(名称 + 顺序 + 文本)。
80
87
  inject:
81
88
  - name: tool:read
82
89
  order: 0
83
- text: ''
90
+ text: ""
84
91
  # 工具目录过滤。
85
92
  tools:
86
93
  exclude: []
87
94
  include: []
88
95
  ```
89
96
 
90
- | 字段 | 类型 | 说明 |
91
- | --- | --- | --- |
92
- | `sections` | `string[]` | 要屏蔽的提示词段名称。 |
93
- | `replace` | `Record<string, string>` | 每个段名称对应的替换文本。 |
94
- | `inject` | `{name, order, text}[]` | 要注入或覆盖的段(order 控制拼接位置)。 |
95
- | `tools.exclude` | `string[]` | 要隐藏的工具名称。 |
96
- | `tools.include` | `string[]` | 非空时仅保留这些工具(优先于 `exclude`)。 |
97
+ | 字段 | 类型 | 说明 |
98
+ | --------------- | ------------------------ | ------------------------------------------ |
99
+ | `sections` | `string[]` | 要屏蔽的提示词段名称。 |
100
+ | `replace` | `Record<string, string>` | 每个段名称对应的替换文本。 |
101
+ | `inject` | `{name, order, text}[]` | 要注入或覆盖的段(order 控制拼接位置)。 |
102
+ | `tools.exclude` | `string[]` | 要隐藏的工具名称。 |
103
+ | `tools.include` | `string[]` | 非空时仅保留这些工具(优先于 `exclude`)。 |
97
104
 
98
105
  ## 开发
99
106
 
package/client/client.js CHANGED
@@ -24,6 +24,7 @@ window.__ModuleLoader__.load({
24
24
  system: "系统",
25
25
  replace: "编辑",
26
26
  clearReplace: "清除",
27
+ clearInput: "清空",
27
28
  injectNew: "注入新段",
28
29
  name: "名称",
29
30
  order: "顺序",
@@ -51,6 +52,7 @@ window.__ModuleLoader__.load({
51
52
  apply: "应用",
52
53
  export: "导出",
53
54
  delete: "删除",
55
+ restore: "还原",
54
56
  active: "使用中",
55
57
  preset: "预设"
56
58
  },
@@ -70,6 +72,7 @@ window.__ModuleLoader__.load({
70
72
  system: "System",
71
73
  replace: "Replace",
72
74
  clearReplace: "Clear",
75
+ clearInput: "Clear",
73
76
  injectNew: "Inject section",
74
77
  name: "Name",
75
78
  order: "Order",
@@ -97,6 +100,7 @@ window.__ModuleLoader__.load({
97
100
  apply: "Apply",
98
101
  export: "Export",
99
102
  delete: "Delete",
103
+ restore: "Restore",
100
104
  active: "Active",
101
105
  preset: "Preset"
102
106
  }
@@ -377,7 +381,10 @@ window.__ModuleLoader__.load({
377
381
  source: isCustom ? "custom" : "system"
378
382
  });
379
383
  }
380
- return [...map.values()].sort((a, b) => a.order - b.order);
384
+ return [...map.values()].sort((a, b) => a.order - b.order).map((sec, i) => ({
385
+ ...sec,
386
+ order: i
387
+ }));
381
388
  }
382
389
  /**
383
390
  * Resolve a preset's relative order into an absolute ordered list (0..n-1).
@@ -576,7 +583,7 @@ window.__ModuleLoader__.load({
576
583
  scope.set("replace", next);
577
584
  setEditing(null);
578
585
  };
579
- const clearReplace = (name) => {
586
+ const restoreReplace = (name) => {
580
587
  const next = { ...replace };
581
588
  delete next[name];
582
589
  scope.set("replace", next);
@@ -705,19 +712,26 @@ window.__ModuleLoader__.load({
705
712
  (0, react.createElement)("span", { style: s.code }, sec.name),
706
713
  (0, react.createElement)("span", { style: s.orderTag }, "#" + index),
707
714
  (0, react.createElement)("span", { style: sec.source === "custom" ? s.badgeCustom : s.badgeSystem }, sec.source === "custom" ? t("manual") : t("system")),
708
- sec.replaced ? (0, react.createElement)("span", { style: s.badgeReplaced }, t("replaced")) : null
715
+ replace[sec.name] ? (0, react.createElement)("span", { style: s.badgeReplaced }, t("replaced")) : null
709
716
  ]), isEditing ? (0, react.createElement)("div", { style: s.editBox }, [(0, react.createElement)("textarea", {
710
717
  style: s.editInput,
711
718
  value: draft,
712
719
  onChange: (e) => setDraft(e.target.value),
713
720
  rows: 3
714
- }), (0, react.createElement)("div", { style: s.injectRow }, [(0, react.createElement)("button", {
715
- style: s.mini,
716
- onClick: () => commitReplace(sec.name)
717
- }, t("add")), (0, react.createElement)("button", {
718
- style: s.mini,
719
- onClick: () => setEditing(null)
720
- }, t("clearReplace"))])]) : (0, react.createElement)("div", { style: s.preview }, String(sec.text ?? "").slice(0, 140) || (sec.source === "custom" ? t("empty") : t("dynamic")))]),
721
+ }), (0, react.createElement)("div", { style: s.injectRow }, [
722
+ (0, react.createElement)("button", {
723
+ style: s.mini,
724
+ onClick: () => commitReplace(sec.name)
725
+ }, t("save")),
726
+ (0, react.createElement)("button", {
727
+ style: s.mini,
728
+ onClick: () => setEditing(null)
729
+ }, t("clearInput")),
730
+ sec.source !== "custom" && replace[sec.name] ? (0, react.createElement)("button", {
731
+ style: s.mini,
732
+ onClick: () => restoreReplace(sec.name)
733
+ }, t("restore")) : null
734
+ ])]) : (0, react.createElement)("div", { style: s.preview }, String(replace[sec.name] ?? sec.text ?? "").slice(0, 140) || (sec.source === "custom" ? t("empty") : t("dynamic")))]),
721
735
  (0, react.createElement)("div", { style: s.arrowCol }, [(0, react.createElement)("button", {
722
736
  style: s.arrow,
723
737
  disabled: index === 0,
@@ -729,10 +743,14 @@ window.__ModuleLoader__.load({
729
743
  onClick: () => moveOrder(index, 1),
730
744
  title: t("moveDown")
731
745
  }, "↓")]),
732
- (0, react.createElement)("button", {
746
+ isEditing ? null : (0, react.createElement)("button", {
733
747
  style: s.mini,
734
- onClick: () => replace[sec.name] ? clearReplace(sec.name) : startReplace(sec.name, String(sec.text ?? ""))
735
- }, replace[sec.name] ? t("clearReplace") : t("replace")),
748
+ onClick: () => startReplace(sec.name, String(sec.text ?? ""))
749
+ }, t("replace")),
750
+ !isEditing && sec.source !== "custom" && replace[sec.name] ? (0, react.createElement)("button", {
751
+ style: s.mini,
752
+ onClick: () => restoreReplace(sec.name)
753
+ }, t("restore")) : null,
736
754
  sec.source === "custom" ? (0, react.createElement)("button", {
737
755
  style: s.mini,
738
756
  onClick: () => removeCustom(sec.name),
@@ -966,7 +984,7 @@ window.__ModuleLoader__.load({
966
984
  * after every plugin's filter, and the tools the current preset will inject
967
985
  * (whitelist-style, after include/exclude). Neither view edits config. */
968
986
  const PREVIEW_URL = "/api/prompt-customizer/preview";
969
- function PreviewTab({ scope, t }) {
987
+ function PreviewTab({ scope, t, active, refreshId }) {
970
988
  const [sub, setSub] = (0, react.useState)("prompt");
971
989
  const [data, setData] = (0, react.useState)(null);
972
990
  const [error, setError] = (0, react.useState)(null);
@@ -975,7 +993,7 @@ window.__ModuleLoader__.load({
975
993
  if (inFlight.current) return;
976
994
  inFlight.current = true;
977
995
  setData(null);
978
- fetch(PREVIEW_URL).then((r) => r.json()).then((body) => {
996
+ fetch(`${PREVIEW_URL}?t=${Date.now()}`).then((r) => r.json()).then((body) => {
979
997
  if (body && body.ok === false) throw new Error(body.error ?? "preview failed");
980
998
  setData(body);
981
999
  setError(null);
@@ -985,6 +1003,9 @@ window.__ModuleLoader__.load({
985
1003
  };
986
1004
  (0, react.useEffect)(load, []);
987
1005
  (0, react.useEffect)(() => scope.subscribe(load), [scope]);
1006
+ (0, react.useEffect)(() => {
1007
+ if (active) load();
1008
+ }, [active, refreshId]);
988
1009
  return (0, react.createElement)("div", { style: s.list }, [
989
1010
  (0, react.createElement)("div", { style: s.bar }, [(0, react.createElement)("button", {
990
1011
  style: sub === "prompt" ? s.tabActive : s.tab,
@@ -1042,11 +1063,13 @@ window.__ModuleLoader__.load({
1042
1063
  const [inv, setInv] = (0, react.useState)(null);
1043
1064
  const [tab, setTab] = (0, react.useState)("sections");
1044
1065
  const [error, setError] = (0, react.useState)(null);
1066
+ const [refreshId, setRefreshId] = (0, react.useState)(0);
1045
1067
  (0, react.useEffect)(() => scope.subscribe(() => setSnap(scope.getSnapshot())), [scope]);
1046
1068
  const refresh = () => {
1047
1069
  fetch(INVENTORY_URL).then((r) => r.json()).then((data) => {
1048
1070
  setInv(data);
1049
1071
  setError(null);
1072
+ setRefreshId((n) => n + 1);
1050
1073
  }).catch((e) => setError(String(e instanceof Error ? e.message : e)));
1051
1074
  };
1052
1075
  (0, react.useEffect)(refresh, []);
@@ -1101,7 +1124,9 @@ window.__ModuleLoader__.load({
1101
1124
  t
1102
1125
  }) : (0, react.createElement)(PreviewTab, {
1103
1126
  scope,
1104
- t
1127
+ t,
1128
+ active: tab === "preview",
1129
+ refreshId
1105
1130
  })
1106
1131
  ]);
1107
1132
  }