dsh-one-click-polish 1.2.0 → 1.2.2

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.
Files changed (4) hide show
  1. package/README.md +57 -11
  2. package/client.js +83 -5
  3. package/index.js +64 -1
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -1,11 +1,20 @@
1
1
  # 一键润色 / 扩写 / 自动驾驶
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/dsh-one-click-polish?label=npm)](https://www.npmjs.com/package/dsh-one-click-polish)
4
+ [![license](https://img.shields.io/npm/l/dsh-one-click-polish)](LICENSE)
5
+
3
6
  给 DeepSeek Harness(dsh)桌面端的输入框加一组动作:**润色**、**扩写**、**自动驾驶**。所有模型调用都用**你当前会话正在用的那个模型**,结果直接写回输入框。
4
7
 
5
8
  - 润色:按三档深度重写草稿
6
9
  - 扩写:把一句话扩写成能直接开工的需求说明
7
10
  - 自动驾驶:让 AI 读对话、写下一步提示词、发给助手,一轮接一轮推进到你设定的目标
8
11
 
12
+ 一句安装:
13
+
14
+ ```
15
+ dsh-one-click-polish
16
+ ```
17
+
9
18
  装好之后,输入框左下角多一排按钮:
10
19
 
11
20
  ```
@@ -26,15 +35,40 @@
26
35
 
27
36
  ## 安装
28
37
 
29
- ### 界面里装(最省事)
38
+ 两种来源,功能完全一样:
39
+
40
+ | 来源 | 填什么 | 更新按钮 |
41
+ | --- | --- | --- |
42
+ | **npm** | `dsh-one-click-polish` | ✅ 有「可更新」 |
43
+ | GitHub | `https://github.com/HTRP1486/dsh-one-click-polish` | ❌ 只能看提交 |
44
+
45
+ ### 方式一:npm(推荐)
30
46
 
31
- 打开 dsh → 设置 → 插件 → 添加插件,在输入框里粘这个:
47
+ 打开 dsh → 设置 → 插件 → 添加插件,输入框里就填**包名**:
48
+
49
+ ```
50
+ dsh-one-click-polish
51
+ ```
52
+
53
+ 不用加 `npm:` 前缀,也不用加版本号(默认装 `latest`)。
54
+
55
+ 命令行等价写法:
56
+
57
+ ```bash
58
+ dsh plugin --profile <你的 profile 名> install dsh-one-click-polish
59
+ ```
60
+
61
+ **为什么推荐这个**:npm 上有版本号,dsh 能比对,所以插件列表里会出现「可更新」和「卸载」两个按钮。缺点是更新要走一遍发布流程。
62
+
63
+ ### 方式二:GitHub
64
+
65
+ 输入框里填仓库地址:
32
66
 
33
67
  ```
34
68
  https://github.com/HTRP1486/dsh-one-click-polish
35
69
  ```
36
70
 
37
- 下面这些写法等价,都行:
71
+ 等价写法都行:
38
72
 
39
73
  ```
40
74
  https://github.com/HTRP1486/dsh-one-click-polish
@@ -44,23 +78,35 @@ github:HTRP1486/dsh-one-click-polish
44
78
 
45
79
  **末尾不要加斜杠**。加了会报 `a URL must point at a git repository or a tarball` —— dsh 的判断规则是 `^https?://主机/用户/仓库(.git)?$`,最后一段不允许出现 `/`。
46
80
 
47
- ### 命令行装
48
-
49
- ```bash
50
- dsh plugin --profile <你的 profile 名> install github:HTRP1486/dsh-one-click-polish
51
- ```
52
-
53
- 不知道 profile 名的话,看你 `~/.dsh/profiles/` 下的目录名,常见的是 `desktop`。
81
+ **为什么用它**:跟最新提交走,不用等发版。但没有「可更新」按钮——想更新就是重装一次(`remove` 后 `install`)。
54
82
 
55
83
  ### 其他方式
56
84
 
57
85
  | 方式 | 做法 |
58
86
  | --- | --- |
59
- | 本地目录 | target 填仓库的**绝对路径**,如 `D:\Documents\dsh-one-click-polish`(相对路径会被拒绝) |
87
+ | 本地目录 | target 填仓库的**绝对路径**,如 `D:\Documents\dsh-one-click-polish`。改代码即时生效,适合开发 |
60
88
  | Release 包 | 从 [Releases](https://github.com/HTRP1486/dsh-one-click-polish/releases) 下 `.tgz`,target 填该文件的绝对路径 |
61
89
 
90
+ ### 三种方式怎么选
91
+
92
+ ```
93
+ 只想用 → npm
94
+ 要用最新代码 → GitHub
95
+ 自己在改它 → 本地目录(link)
96
+ ```
97
+
62
98
  装完**重启 dsh**。判断是否成功:输入框左下角出现 `润色 | 深度 | 扩写 | 自动`,设置里多出「润色 / 扩写」一页。
63
99
 
100
+ ### 版本号
101
+
102
+ 三个地方保持同步,每次发布一起改:
103
+
104
+ | 位置 | 当前 |
105
+ | --- | --- |
106
+ | `package.json` 的 `version` | `1.2.0` |
107
+ | npm 的 `latest` 标签 | `1.2.0` |
108
+ | GitHub 的 tag / Release | `v1.2.0` |
109
+
64
110
  ### 装不上时
65
111
 
66
112
  | 报错 | 原因 |
package/client.js CHANGED
@@ -41,6 +41,7 @@ window.__ModuleLoader__.load({
41
41
  const PROGRESS = ENDPOINT.replace(/\/polish$/, '/progress')
42
42
  const PILOT = ENDPOINT.replace(/\/polish$/, '/autopilot')
43
43
  const RUNS = ENDPOINT.replace(/\/polish$/, '/runs')
44
+ const VERSION = ENDPOINT.replace(/\/polish$/, '/version')
44
45
 
45
46
  /** The in-flight request, so the panel can cancel it. */
46
47
  let activeAbort = null
@@ -253,8 +254,8 @@ window.__ModuleLoader__.load({
253
254
  'auto.warn.body': '启动后 AI 会自动往这个会话里发送消息:读对话 → 写下一步提示词 → 发给助手,一轮接一轮。它会消耗模型额度,也可能做出你没想到的改动。随时可以中断或结束循环。',
254
255
  'auto.warn.cancel': '再想想',
255
256
  'auto.warn.confirm': '确认启动',
256
- 'auto.interrupt': '跳过这一轮',
257
- 'auto.interrupt.hint': '取消助手正在做的这一轮,循环继续,它会重新判断下一步。',
257
+ 'auto.interrupt': '跳过这一轮',
258
+ 'auto.interrupt.hint': '取消助手正在做的这一轮,循环继续,它会重新判断下一步。',
258
259
  'auto.stop.hint': '整个自动驾驶停下。之后可以从这里继续。',
259
260
  'auto.stop': '停止自动',
260
261
  'auto.running': '运行中',
@@ -281,6 +282,13 @@ window.__ModuleLoader__.load({
281
282
  'settings.idleWait': '等待助手完成上一轮的上限',
282
283
  'settings.idleWait.hint': '自动驾驶要发下一条时,如果助手还在处理,它会等。超过这个上限就先停下,点「继续」可以接着来。长会话需要更长的时间。',
283
284
  'settings.idleWait.unit': '分钟',
285
+ 'version.current': '当前版本',
286
+ 'version.latest': '已是最新',
287
+ 'version.newest': 'npm 上有',
288
+ 'version.checking': '检查更新中…',
289
+ 'version.unknown': '查不到最新版本',
290
+ 'version.how': '怎么更新',
291
+ 'version.recheck': '重新检查',
284
292
  'settings.explore': '检索预算',
285
293
  'settings.explore.hint': '标准/深度润色时会先查资料。这是查资料总共能用多久、最多查几轮。查完后再改写,两者互不占用。',
286
294
  'settings.explore.unit': '秒',
@@ -412,8 +420,8 @@ window.__ModuleLoader__.load({
412
420
  'auto.warn.body': 'The AI will send messages into this conversation by itself: read the history, write the next prompt, send it, round after round. It spends model quota and may change things you did not expect. You can interrupt or end the loop at any time.',
413
421
  'auto.warn.cancel': 'Not now',
414
422
  'auto.warn.confirm': 'Start',
415
- 'auto.interrupt': 'Skip this round',
416
- 'auto.interrupt.hint': 'Cancel the turn the assistant is running. The loop stays alive and re-plans the next step.',
423
+ 'auto.interrupt': 'Skip this round',
424
+ 'auto.interrupt.hint': 'Cancel the turn the assistant is running. The loop stays alive and re-plans the next step.',
417
425
  'auto.stop.hint': 'End the autopilot. You can continue from here afterwards.',
418
426
  'auto.stop': 'Stop autopilot',
419
427
  'auto.running': 'Running',
@@ -440,6 +448,13 @@ window.__ModuleLoader__.load({
440
448
  'settings.idleWait': 'How long to wait for the assistant',
441
449
  'settings.idleWait.hint': 'When the loop is ready to send but the assistant is still working, it waits. Past this limit it parks, and Continue picks it up again. Long conversations need more time.',
442
450
  'settings.idleWait.unit': 'minutes',
451
+ 'version.current': 'Version',
452
+ 'version.latest': 'Up to date',
453
+ 'version.newest': 'npm has',
454
+ 'version.checking': 'Checking for updates…',
455
+ 'version.unknown': 'Cannot reach npm',
456
+ 'version.how': 'How to update',
457
+ 'version.recheck': 'Check again',
443
458
  'settings.explore': 'Lookup budget',
444
459
  'settings.explore.hint': 'A standard or deep polish looks things up first. This is how long that may take in total, and how many rounds it may spend. The rewrite runs separately with its own budget.',
445
460
  'settings.explore.unit': 's',
@@ -564,6 +579,21 @@ window.__ModuleLoader__.load({
564
579
  .dsh-op-head{margin-bottom:18px}
565
580
  .dsh-op-h2{margin:0 0 4px;font-size:17px;font-weight:600;line-height:26px;letter-spacing:-.01em}
566
581
  .dsh-op-sub{margin:0;color:var(--dsw-alias-label-secondary);font-size:12.5px;line-height:20px}
582
+ /* Version line: this plugin's own update affordance. dsh's plugin page only
583
+ computes installTarget for bundles in its official catalogue, so a third-party
584
+ plugin has no update button there. */
585
+ .dsh-op-version{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:9px;
586
+ font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-secondary)}
587
+ .dsh-op-versionNow{font-variant-numeric:tabular-nums;
588
+ font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;
589
+ background:var(--dsw-alias-bg-base);border-radius:999px;padding:1px 8px;
590
+ color:var(--dsw-alias-label-primary)}
591
+ .dsh-op-versionNew{display:flex;flex-wrap:wrap;align-items:center;gap:8px;
592
+ font-size:11.5px;color:var(--dsw-alias-state-warn-primary)}
593
+ .dsh-op-versionBad{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));opacity:.8}
594
+ /* The busy-wait line: a live counter, so it is monospaced and does not reflow as
595
+ the seconds tick, and it holds a fixed height so the panel does not jump. */
596
+ .dsh-op-waiting{font-variant-numeric:tabular-nums;min-height:17px}
567
597
  .dsh-op-card{display:flex;flex-direction:column;gap:2px;padding:2px 16px;
568
598
  background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);
569
599
  border-radius:14px}
@@ -2266,6 +2296,8 @@ window.__ModuleLoader__.load({
2266
2296
  const [form, setForm] = useState(null) // what the user is editing
2267
2297
  const [efforts, setEfforts] = useState({ state: 'idle', list: [], defaultEffort: null })
2268
2298
  const [saveState, setSaveState] = useState({ state: 'idle', message: '' })
2299
+ // null while the first lookup is in flight; afterwards { current, latest, error }
2300
+ const [version, setVersion] = useState(null)
2269
2301
  const [runs, setRuns] = useState([])
2270
2302
  const [runsPick, setRunsPick] = useState(null)
2271
2303
  const [runEntries, setRunEntries] = useState([])
@@ -2317,6 +2349,24 @@ window.__ModuleLoader__.load({
2317
2349
 
2318
2350
  useEffect(() => { load() }, [load])
2319
2351
 
2352
+ /** Read this plugin's version against npm. Cached on the Host for 10 minutes. */
2353
+ const checkVersion = useCallback(async (force) => {
2354
+ try {
2355
+ const response = await fetch(force === true ? `${VERSION}?refresh=1` : VERSION)
2356
+ const payload = await response.json()
2357
+ if (payload?.ok !== true) throw new Error(payload?.error ?? 'HTTP error')
2358
+ setVersion({
2359
+ current: typeof payload.current === 'string' ? payload.current : '',
2360
+ latest: typeof payload.latest === 'string' ? payload.latest : null,
2361
+ error: typeof payload.error === 'string' ? payload.error : null,
2362
+ })
2363
+ } catch (failure) {
2364
+ setVersion({ current: '', latest: null, error: String(failure?.message ?? failure) })
2365
+ }
2366
+ }, [])
2367
+
2368
+ useEffect(() => { void checkVersion(false) }, [checkVersion])
2369
+
2320
2370
  /** Sessions that have an archived run, newest first. */
2321
2371
  const loadRuns = useCallback(async () => {
2322
2372
  try {
@@ -2462,7 +2512,35 @@ window.__ModuleLoader__.load({
2462
2512
  const pageBody = h('div', { className: 'dsh-op-page' },
2463
2513
  h('div', { className: 'dsh-op-head' },
2464
2514
  h('h2', { className: 'dsh-op-h2' }, t('settings.title')),
2465
- h('p', { className: 'dsh-op-sub' }, t('settings.sub'))),
2515
+ h('p', { className: 'dsh-op-sub' }, t('settings.sub')),
2516
+ // dsh's own plugin page can only offer 更新 for bundles in its official
2517
+ // catalogue, so a third-party plugin gets no update affordance there.
2518
+ // The version line puts that question here instead.
2519
+ h('div', { className: 'dsh-op-version' },
2520
+ h('span', { className: 'dsh-op-versionNow' },
2521
+ `${t('version.current')} ${version.current === '' ? '?' : version.current}`),
2522
+ version === null
2523
+ ? h('span', { className: 'dsh-op-hint dsh-op-hint--inline' }, t('version.checking'))
2524
+ : version.error !== null && version.error !== ''
2525
+ ? h('span', { className: 'dsh-op-versionBad' },
2526
+ `${t('version.unknown')} · ${version.error}`)
2527
+ : version.latest === null
2528
+ ? h('span', { className: 'dsh-op-hint dsh-op-hint--inline' }, t('version.unknown'))
2529
+ : version.latest !== version.current
2530
+ ? h('div', { className: 'dsh-op-versionNew' },
2531
+ h('span', null, `${t('version.newest')} ${version.latest}`),
2532
+ h('a', {
2533
+ className: 'dsh-op-link',
2534
+ href: 'https://www.npmjs.com/package/dsh-one-click-polish',
2535
+ target: '_blank',
2536
+ rel: 'noreferrer',
2537
+ }, t('version.how')),
2538
+ h('button', {
2539
+ type: 'button',
2540
+ className: 'dsh-op-link',
2541
+ onClick: () => { void checkVersion(true) },
2542
+ }, t('version.recheck')))
2543
+ : h('span', { className: 'dsh-op-hint dsh-op-hint--inline' }, t('version.latest')))),
2466
2544
  h('div', { className: 'dsh-op-card' },
2467
2545
  h('div', { className: 'dsh-op-cardTitle' }, t('settings.group.model')),
2468
2546
  h('div', { className: 'dsh-op-field' },
package/index.js CHANGED
@@ -23,7 +23,8 @@ export const name = 'one-click-polish'
23
23
  export const inject = ['llm', 'agents', 'webServer']
24
24
 
25
25
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
26
- import { join } from 'node:path'
26
+ import { dirname, join } from 'node:path'
27
+ import { fileURLToPath } from 'node:url'
27
28
 
28
29
  const PREFIX = '/plugins/dsh-one-click-polish'
29
30
  const DEFAULT_MAX_INPUT_CHARS = 12000
@@ -466,6 +467,61 @@ async function produceRewrite(ctx, settings, mode, strength, research, input, ag
466
467
  return text
467
468
  }
468
469
 
470
+ /**
471
+ * This plugin's own version, and the newest one on npm.
472
+ *
473
+ * dsh's plugin page only offers an update button for bundles in its official
474
+ * catalogue, because `installTarget` is computed from that catalogue. A third-party
475
+ * plugin therefore has no update affordance there at all, so the question "is there
476
+ * a newer version?" is answered here instead, on the plugin's own settings page.
477
+ */
478
+ const VERSION_TTL_MS = 10 * 60 * 1000
479
+ let versionCache = { at: 0, latest: null, error: null }
480
+
481
+ async function versionInfo() {
482
+ let current = ''
483
+ try {
484
+ // Resolve from this module rather than the process cwd: the Host may run with
485
+ // any working directory, and this file sits next to its own manifest.
486
+ const here = dirname(fileURLToPath(import.meta.url))
487
+ const manifest = JSON.parse(readFileSync(join(here, 'package.json'), 'utf8'))
488
+ current = typeof manifest.version === 'string' ? manifest.version : ''
489
+ } catch { /* a missing manifest just means no version to report */ }
490
+
491
+ if (versionCache.latest !== null && Date.now() - versionCache.at < VERSION_TTL_MS) {
492
+ return { current, latest: versionCache.latest, error: null, checked: versionCache.at }
493
+ }
494
+
495
+ // The registry is the only source of truth for "what is published"; a failed
496
+ // lookup must not look like "you are up to date".
497
+ let latest = null
498
+ let error = null
499
+ try {
500
+ const controller = new AbortController()
501
+ const timer = setTimeout(() => controller.abort(), 8000)
502
+ const response = await fetch('https://registry.npmjs.org/dsh-one-click-polish/latest', {
503
+ signal: controller.signal,
504
+ headers: { accept: 'application/json' },
505
+ })
506
+ clearTimeout(timer)
507
+ if (!response.ok) throw new Error(`HTTP ${response.status}`)
508
+ const payload = await response.json()
509
+ latest = typeof payload?.version === 'string' ? payload.version : null
510
+ } catch (failure) {
511
+ error = textOf(failure)
512
+ }
513
+ versionCache = { at: Date.now(), latest, error }
514
+ return { current, latest, error, checked: Date.now() }
515
+ }
516
+
517
+ async function versionRoute(req, res) {
518
+ // `refresh=1` drops the TTL cache: the panel offers a recheck, and a button that
519
+ // silently answers from cache would look broken.
520
+ const url = new URL(req.url ?? '/', 'http://localhost')
521
+ if (url.searchParams.get('refresh') === '1') versionCache.at = 0
522
+ sendJson(res, 200, { ok: true, ...(await versionInfo()) })
523
+ }
524
+
469
525
  async function statusRoute(ctx, req, res) {
470
526
  const url = new URL(req.url ?? '/', 'http://localhost')
471
527
  const sessionId = url.searchParams.get('sessionId') ?? undefined
@@ -2466,6 +2522,13 @@ export function apply(ctx, config) {
2466
2522
  })
2467
2523
 
2468
2524
 
2525
+ ctx.effect(() => server.register({
2526
+ kind: 'exact',
2527
+ path: `${PREFIX}/version`,
2528
+ handler: (req, res) => versionRoute(req, res)
2529
+ .catch(error => sendJson(res, 500, { ok: false, error: textOf(error) })),
2530
+ }), 'one-click-polish: version route')
2531
+
2469
2532
  ctx.effect(() => server.register({
2470
2533
  kind: 'exact',
2471
2534
  path: `${PREFIX}/status`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-one-click-polish",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "One-click polish, expand and autopilot for the DeepSeek Harness composer: rewrite the draft with the model the session is switched to, expand a one-line wish into a task brief, or let an AI drive a goal round after round.",
5
5
  "keywords": [
6
6
  "dsh",