@qic-9821/dsh-plugin-sync 0.1.1 → 0.1.3

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
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,8 +4,16 @@
4
4
 
5
5
  npm:[`@qic-9821/dsh-plugin-sync`](https://www.npmjs.com/package/@qic-9821/dsh-plugin-sync) · 仓库:<https://github.com/QIC-9821/dsh-plugin-sync> · MIT
6
6
 
7
+ ## 演示
8
+
9
+ 独立面板(`http://127.0.0.1:<端口>/plugin-sync`):状态、对端、同步方式、权限、计划日志一张页面上全有。
10
+
7
11
  ![独立面板](docs/panel.png)
8
12
 
13
+ 同一套设置在系统设置里也有一页(`设置 → 插件同步`),改完即保存,权限可以一项一项单独关:
14
+
15
+ ![设置页](docs/settings-sync.png)
16
+
9
17
  配套:`设置 → 插件同步`(系统设置菜单栏里的独立一页)、`设置 → 插件 → 同步`(Plugins 段内的一页)、独立面板 `http://127.0.0.1:<端口>/plugin-sync`、两个 Agent 工具 `plugin_sync` / `plugin_sync_config`。
10
18
 
11
19
  ## 安装
@@ -40,6 +48,8 @@ dsh plugin --profile <profile> add @qic-9821/dsh-plugin-sync
40
48
  | 允许对端改动本机 | `capRemoteApply` | 关掉后对端连读都读不到 |
41
49
  | 同步密钥字段 | `capSecrets` | 默认关;`appSecret` / `token` / `*key*` 一律写成 `[redacted]` |
42
50
 
51
+ 默认只开「插件清单与启停」和「允许对端改动本机」;配置、安装、复制二进制、密钥四项要自己在设置页打开,打开后下一次自动同步就会真的落地对应操作。
52
+
43
53
  安装、升级、卸载、启停全部走宿主的 `pluginManager` 服务(`installBundle` / `updateBundle` / `removeBundle` / `setBundleEnabled` / `setPluginEnabled`),不自己拼 pnpm 命令行、不手写 profile 的 `package.json`,失败由宿主回滚它自己的改动。
44
54
 
45
55
  ## 怎么连
@@ -55,9 +65,11 @@ dsh plugin --profile <profile> add @qic-9821/dsh-plugin-sync
55
65
 
56
66
  - `autoStart`:DSH 启动后约 25 秒跑一轮(等宿主把插件挂完)。
57
67
  - `autoRestart`:进程重启后再补一轮(约 2 分钟),处理对端还在启动的情况;服务器重启后的同步就是这条。
58
- - `intervalMinutes`:固定间隔(0 = 关)。
68
+ - `intervalMinutes`:固定间隔(0 = 关)。只在 DSH 运行期间生效,每 N 分钟跑一轮完整同步;和「启动时」「重启后」两轮互不影响。
59
69
  - 手动:面板/设置页按钮,或工具 `plugin_sync` `action="run"`。
60
70
 
71
+ 总开关 `enabled` 关掉后,以上任何自动同步与手动同步都不执行(仍可在面板上「只看计划」)。
72
+
61
73
  ## 冲突策略
62
74
 
63
75
  - `local`:以发起方为准(默认)
package/client.js CHANGED
@@ -94,6 +94,9 @@ window.__ModuleLoader__.load({
94
94
  }),
95
95
  h('span', { key: 'text' }, [
96
96
  h('b', { key: 'name' }, props.label),
97
+ props.dirty
98
+ ? h('span', { key: 'dirty', style: { marginLeft: 6, fontSize: 11, color: 'var(--dsh-accent, #3b82f6)' } }, '未保存')
99
+ : null,
97
100
  props.desc ? h('div', { key: 'desc', style: MUTED }, props.desc) : null,
98
101
  ]),
99
102
  ],
@@ -125,8 +128,18 @@ window.__ModuleLoader__.load({
125
128
  }, [load])
126
129
 
127
130
  const config = state?.config || {}
128
- const value = useCallback((key) => (draft[key] !== undefined ? draft[key] : config[key]), [draft, config])
131
+ const caps = config.capabilities || {}
132
+ // draft wins, then the nested capabilities map, then the flat key
133
+ const value = useCallback(
134
+ (key) => (draft[key] !== undefined ? draft[key] : caps[key] !== undefined ? caps[key] : config[key]),
135
+ [draft, caps, config],
136
+ )
137
+ const dirty = useCallback((key) => draft[key] !== undefined, [draft])
129
138
  const setValue = useCallback((key, next) => setDraft((current) => ({ ...current, [key]: next })), [])
139
+ const capsOn = useMemo(
140
+ () => (state?.capabilityCatalog || []).filter((cap) => Boolean(value(cap.key))).length,
141
+ [state, value],
142
+ )
130
143
 
131
144
  const save = useCallback(async () => {
132
145
  if (Object.keys(draft).length === 0) return
@@ -190,9 +203,10 @@ window.__ModuleLoader__.load({
190
203
  ? `${formatTime(status.lastRun.at)} · ${status.lastRun.ok ? '成功' : `失败:${status.lastRun.error || ''}`}`
191
204
  : '还没跑过',
192
205
  ],
206
+ ['设置保存于', state?.updatedAt ? formatTime(state.updatedAt) : '还没保存过'],
193
207
  ['当前', status.running ? '同步中…' : '空闲'],
194
208
  ]
195
- }, [status])
209
+ }, [status, state?.updatedAt])
196
210
 
197
211
  if (!state) {
198
212
  return h('div', { style: { padding: 16, fontSize: 13 } }, error ? `读取失败:${error}` : '读取中…')
@@ -260,7 +274,8 @@ window.__ModuleLoader__.load({
260
274
  h(Toggle, {
261
275
  key: 'enable',
262
276
  label: '总开关',
263
- desc: '关掉后「启动时同步」和手动同步都不动作',
277
+ desc: '关掉后任何自动与手动同步都不执行(仍可「只看计划」)',
278
+ dirty: dirty('enabled'),
264
279
  checked: Boolean(value('enabled')),
265
280
  onChange: (next) => setValue('enabled', next),
266
281
  }),
@@ -268,6 +283,7 @@ window.__ModuleLoader__.load({
268
283
  key: 'startup',
269
284
  label: '软件启动时同步',
270
285
  desc: 'DSH 启动后约 25 秒跑一轮(等宿主把插件都挂上)',
286
+ dirty: dirty('autoStart'),
271
287
  checked: Boolean(value('autoStart')),
272
288
  onChange: (next) => setValue('autoStart', next),
273
289
  }),
@@ -275,22 +291,38 @@ window.__ModuleLoader__.load({
275
291
  key: 'restart',
276
292
  label: '服务器重启后同步',
277
293
  desc: '进程重启之后再补一轮,处理对端还在启动的情况',
294
+ dirty: dirty('autoRestart'),
278
295
  checked: Boolean(value('autoRestart')),
279
296
  onChange: (next) => setValue('autoRestart', next),
280
297
  }),
281
- h(Field, { key: 'interval', label: '每隔多少分钟同步(0 = 不按间隔)' },
282
- input(value('intervalMinutes'), '0', (next) => setValue('intervalMinutes', Number(next) || 0))),
283
- h(Field, { key: 'delay', label: '启动后延迟秒数' },
298
+ h(Toggle, {
299
+ key: 'interval',
300
+ label: '定时同步',
301
+ desc: 'DSH 运行期间每 N 分钟跑一轮;关掉就是「不按间隔」,和上面两轮互不影响',
302
+ dirty: dirty('intervalMinutes'),
303
+ checked: Number(value('intervalMinutes')) > 0,
304
+ onChange: (next) =>
305
+ setValue('intervalMinutes', next ? Number(value('intervalMinutes')) || 15 : 0),
306
+ }),
307
+ Number(value('intervalMinutes')) > 0
308
+ ? h(
309
+ Field,
310
+ { key: 'intervalValue', label: '间隔分钟数(1–1440)' },
311
+ input(value('intervalMinutes'), '15', (next) => setValue('intervalMinutes', Math.max(0, Number(next) || 0))),
312
+ )
313
+ : null,
314
+ h(Field, { key: 'delay', label: '启动后延迟秒数(只影响「软件启动时同步」)' },
284
315
  input(value('startupDelaySec'), '25', (next) => setValue('startupDelaySec', Number(next) || 0))),
285
316
  ]),
286
317
  ]),
287
318
 
288
- h(Card, { key: 'caps', title: '权限(每一项可以单独关)' },
319
+ h(Card, { key: 'caps', title: `权限(已开启 ${capsOn} / ${(state.capabilityCatalog || []).length},每一项可以单独关)` },
289
320
  (state.capabilityCatalog || []).map((cap) =>
290
321
  h(Toggle, {
291
322
  key: cap.key,
292
323
  label: cap.label,
293
324
  desc: cap.desc,
325
+ dirty: dirty(cap.key),
294
326
  checked: Boolean(value(cap.key)),
295
327
  onChange: (next) => setValue(cap.key, next),
296
328
  }),
@@ -299,7 +331,13 @@ window.__ModuleLoader__.load({
299
331
 
300
332
  h('div', { key: 'save', style: { display: 'flex', gap: 8, alignItems: 'center' } }, [
301
333
  h('button', { key: 'save', style: BUTTON_PRIMARY, disabled: Boolean(busy) || Object.keys(draft).length === 0, onClick: () => void save() }, '保存设置'),
302
- h('span', { key: 'hint', style: MUTED }, Object.keys(draft).length === 0 ? '没有未保存的改动' : `${Object.keys(draft).length} 项待保存`),
334
+ h(
335
+ 'span',
336
+ { key: 'hint', style: MUTED },
337
+ Object.keys(draft).length === 0
338
+ ? `已保存 · ${state?.updatedAt ? formatTime(state.updatedAt) : '(本次打开后还没改过)'}`
339
+ : `${Object.keys(draft).length} 项待保存`,
340
+ ),
303
341
  ]),
304
342
 
305
343
  out ? h('pre', { key: 'out', style: { marginTop: 12, whiteSpace: 'pre-wrap', fontSize: 11.5, maxHeight: 260, overflow: 'auto', background: 'rgba(127,127,127,.1)', padding: 10, borderRadius: 8 } }, out) : null,
package/lib/http.js CHANGED
@@ -148,6 +148,8 @@ export async function statePayload(engine, cfg) {
148
148
  capabilities: Object.fromEntries(
149
149
  Object.keys(CAPABILITY_LABELS).map((key) => [key, Boolean(cfg[key])]),
150
150
  ),
151
+ // flat mirror of the capabilities, so consumers can read `capPlugins` directly
152
+ ...Object.fromEntries(Object.keys(CAPABILITY_LABELS).map((key) => [key, Boolean(cfg[key])])),
151
153
  include: cfg.include,
152
154
  exclude: cfg.exclude,
153
155
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qic-9821/dsh-plugin-sync",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Keep the DSH plugin set (inventory, configs, enable state, package installs, local bundle binaries) in sync between two machines, with per-capability switches, schedule controls and a settings/panel UI.",
5
5
  "license": "MIT",
6
6
  "author": "QIC-9821",
@@ -23,6 +23,9 @@
23
23
  "cordis.patch.yml",
24
24
  "LICENSE"
25
25
  ],
26
+ "scripts": {
27
+ "test": "node --test test/*.test.js"
28
+ },
26
29
  "keywords": [
27
30
  "dsh",
28
31
  "deepseek-harness",
@@ -53,8 +56,5 @@
53
56
  "@deepseek-ai/dsh-client-ui-settings"
54
57
  ]
55
58
  }
56
- },
57
- "scripts": {
58
- "test": "node --test test/*.test.js"
59
59
  }
60
- }
60
+ }