dsh-offhours 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  DeepSeek Harness 社区插件:把看板里的待办,在**低谷/夜间**自动做掉。
4
4
 
5
5
  - 只做一件事:`todo` 卡 → 每卡一个独立 git worktree → headless worker 干活并自证 → 证据写回卡 → 合并回收。
6
- - 不需要 cron/systemd:插件进程内按 interval 自己扫;也可以手动 `offhours run --once`。
6
+ - 不需要 cron/systemd:插件进程内按 interval 自己扫;也可以手动 `npx dsh-offhours run --once`。
7
7
  - 你的数据只在本地:看板是 `KANBAN.json`,状态/日志在 `stateDir`,不上传。
8
8
 
9
9
  ## 安装
@@ -14,26 +14,32 @@ dsh plugin --profile web add dsh-offhours
14
14
 
15
15
  装完重启一次 `dsh web`(或让 HMR 生效),左侧出现「夜间跑批」面板。
16
16
 
17
+ > 为什么下面的命令写 `npx dsh-offhours`:安装插件不会把 CLI 加进全局 PATH;`npx` 会直接运行刚装好的同一个入口(首次会从 npm 取包)。如果你已经全局安装,可以简写成 `offhours`。
18
+
17
19
  ## 三步开始
18
20
 
19
21
  ```bash
20
22
  # 1) 在你要跑批的 git 仓库里初始化(写配置 + 建/关联特性 + 生成看板)
21
23
  cd ~/code/myapp
22
- offhours init --repo . --name myapp
24
+ npx dsh-offhours init --repo . --name myapp
23
25
 
24
26
  # 2) 体检(依赖、路径、worker 权限)
25
- offhours doctor
27
+ npx dsh-offhours doctor
26
28
 
27
29
  # 3) 建一张卡(自动盖当前会话;没有 session 的卡跑批不会捡)
28
- offhours card add "给登录页补错误提示" \
30
+ npx dsh-offhours card add "给登录页补错误提示" \
29
31
  --summary "目标:登录失败有提示。判据:npm test 通过。不做什么:不改后端。"
30
32
  ```
31
33
 
32
- 之后低谷时段自动跑;也可以用面板「立即跑」或 `offhours run --once`。
34
+ 之后低谷时段自动跑;也可以用面板「立即跑」或 `npx dsh-offhours run --once`。
35
+
36
+ 如果打开面板看到「先完成一次性初始化」,说明配置还没生成:在项目仓库执行上面的 `init`,刷新面板即可;不需要重装插件。
37
+
38
+ 装完/升级插件后记得重启一次 `dsh web`,面板才会加载新代码。
33
39
 
34
40
  ## 配置
35
41
 
36
- `~/.dsh/offhours/config.json`(`offhours init` 生成):
42
+ `~/.dsh/offhours/config.json`(`npx dsh-offhours init` 生成):
37
43
 
38
44
  ```json
39
45
  {
@@ -62,13 +68,15 @@ offhours card add "给登录页补错误提示" \
62
68
 
63
69
  | 命令 | 作用 |
64
70
  |---|---|
65
- | `offhours init --repo .` | 初始化配置/看板/特性 |
66
- | `offhours doctor` | 依赖与权限体检(失败退出非 0) |
67
- | `offhours card add "<标题>" --summary "<目标/判据/边界>"` | 建卡并盖章当前会话 |
68
- | `offhours card list` | 列卡 |
69
- | `offhours run --once` / `--dry-run` | 手动跑一轮 / 只看会挑哪张 |
70
- | `offhours status` | 看板计数、心跳、最近状态(JSON) |
71
- | `offhours boards` | 看板列表 |
71
+ | `npx dsh-offhours init --repo .` | 初始化配置/看板/特性 |
72
+ | `npx dsh-offhours doctor` | 依赖与权限体检(失败退出非 0) |
73
+ | `npx dsh-offhours card add "<标题>" --summary "<目标/判据/边界>"` | 建卡并盖章当前会话 |
74
+ | `npx dsh-offhours card list` | 列卡 |
75
+ | `npx dsh-offhours run --once` / `--dry-run` | 手动跑一轮 / 只看会挑哪张 |
76
+ | `npx dsh-offhours status` | 看板计数、心跳、最近状态(JSON) |
77
+ | `npx dsh-offhours boards` | 看板列表 |
78
+
79
+ > 不想走 npx:装完后 CLI 也在 profile 的 `node_modules/.bin/offhours`(例如 `~/.dsh/profiles/web/node_modules/.bin/offhours`),可以直接用绝对路径调用。
72
80
 
73
81
  ## 依赖
74
82
 
package/bin/offhours.mjs CHANGED
@@ -20,15 +20,16 @@ import {
20
20
 
21
21
  const HELP = `dsh-offhours —— 夜间/低谷自动跑批(配一个 git 仓库就能用)
22
22
 
23
- 用法:
24
- offhours init --repo <git仓库路径> [--name 名称] [--board KANBAN.json] [--feature 特性名]
25
- offhours doctor
26
- offhours card add "<标题>" --summary "<目标/判据>" [--tags auto] [--board id]
27
- offhours card list [--board id]
28
- offhours run [--once] [--dry-run] [--force] [--board id]
29
- offhours status [--board id]
30
- offhours boards
23
+ 装完插件后不需要全局安装 CLI,用 npx 调用即可:
24
+ npx dsh-offhours init --repo <git仓库路径> [--name 名称] [--board KANBAN.json] [--feature 特性名]
25
+ npx dsh-offhours doctor
26
+ npx dsh-offhours card add "<标题>" --summary "<目标/判据>" [--tags auto] [--board id]
27
+ npx dsh-offhours card list [--board id]
28
+ npx dsh-offhours run [--once] [--dry-run] [--force] [--board id]
29
+ npx dsh-offhours status [--board id]
30
+ npx dsh-offhours boards
31
31
 
32
+ 如果你已经全局安装了本包,也可以把 npx dsh-offhours 换成 offhours。
32
33
  全局:--config <path>(默认 ~/.dsh/offhours/config.json)
33
34
 
34
35
  约束:worker 需要 dsh、python3、bash、git、jq、flock;zstd 可选(成本统计)。
@@ -53,14 +54,14 @@ function parse(argv) {
53
54
  }
54
55
 
55
56
  function die(msg, code = 1) {
56
- console.error(`offhours: ${msg}`)
57
+ console.error(`dsh-offhours: ${msg}`)
57
58
  process.exit(code)
58
59
  }
59
60
 
60
61
  function loadOrDie(flags) {
61
62
  const { config, path, exists } = loadConfig(flags.config)
62
- if (!exists) die(`没有配置:${path}\n先运行:offhours init --repo <git仓库路径>`)
63
- if (!config.boards.length) die(`配置里没有看板:${path}\n先运行:offhours init --repo <git仓库路径>`)
63
+ if (!exists) die(`没有配置:${path}\n先运行:npx dsh-offhours init --repo <你的 git 仓库>`)
64
+ if (!config.boards.length) die(`配置里没有看板:${path}\n先运行:npx dsh-offhours init --repo <你的 git 仓库>`)
64
65
  return { cfg: config, path }
65
66
  }
66
67
 
@@ -104,8 +105,13 @@ function cmdInit(flags) {
104
105
  writeFileSync(boardFile, '{\n "version": 1,\n "cards": []\n}\n', 'utf8')
105
106
  }
106
107
  mkdirSync(cfg.featuresRoot, { recursive: true })
108
+ // 合并已有登记表:init 一个新仓库不能把用户之前登记的仓库冲掉。
109
+ let registry = {}
110
+ try { registry = JSON.parse(readFileSync(join(cfg.featuresRoot, '.repos.json'), 'utf8')) || {} } catch { /* 首次初始化 */ }
111
+ if (!registry || typeof registry !== 'object' || Array.isArray(registry)) registry = {}
112
+ registry[name] = { path: repo, baseBranch: base }
107
113
  writeFileSync(join(cfg.featuresRoot, '.repos.json'),
108
- JSON.stringify({ [name]: { path: repo, baseBranch: base } }, null, 2) + '\n', 'utf8')
114
+ JSON.stringify(registry, null, 2) + '\n', 'utf8')
109
115
  saveConfig(cfgPath, cfg)
110
116
 
111
117
  const env = {
@@ -118,6 +124,13 @@ function cmdInit(flags) {
118
124
  let r = spawnSync(fb, args, { env, encoding: 'utf8' })
119
125
  if (r.status !== 0) r = spawnSync(fb, ['ensure', feature, '--repos', name], { env, encoding: 'utf8' })
120
126
  const featOut = String((r.stdout || r.stderr || '').trim()).split('\n').slice(-2).join('\n')
127
+ // feature CLI 可能把登记表改写成字符串;补回 baseBranch,且不丢已有仓库。
128
+ try {
129
+ let reg2 = JSON.parse(readFileSync(join(cfg.featuresRoot, '.repos.json'), 'utf8')) || {}
130
+ if (!reg2 || typeof reg2 !== 'object' || Array.isArray(reg2)) reg2 = {}
131
+ reg2[name] = { path: repo, baseBranch: base }
132
+ writeFileSync(join(cfg.featuresRoot, '.repos.json'), JSON.stringify(reg2, null, 2) + '\n', 'utf8')
133
+ } catch { /* 保留 feature 的写法 */ }
121
134
 
122
135
  console.log(`✓ 配置:${cfgPath}`)
123
136
  console.log(`✓ 仓库:${name} → ${repo}(基准分支 ${base})`)
@@ -126,13 +139,17 @@ function cmdInit(flags) {
126
139
  if (featOut) console.log(featOut)
127
140
  console.log('')
128
141
  console.log('下一步:')
129
- console.log(` offhours doctor`)
130
- console.log(` offhours card add "第一张卡" --summary "目标/判据/不做什么"`)
131
- console.log(` offhours run --once`)
142
+ console.log(' npx dsh-offhours doctor')
143
+ console.log(' npx dsh-offhours card add "第一张卡" --summary "目标/判据/不做什么"')
144
+ console.log(' npx dsh-offhours run --once')
132
145
  }
133
146
 
134
147
  function cmdDoctor(flags) {
135
- const { config: cfg, path } = loadConfig(flags.config)
148
+ const { config: cfg, path, exists } = loadConfig(flags.config)
149
+ if (!exists) {
150
+ console.log(`未配置:${path}`)
151
+ console.log('先运行:npx dsh-offhours init --repo <你的 git 仓库>')
152
+ }
136
153
  const rows = []
137
154
  const fail = []
138
155
  const hard = (name, ok, detail = '') => {
package/lib/client.js CHANGED
@@ -1001,6 +1001,7 @@ window.__ModuleLoader__.load({
1001
1001
  const reviews = Array.isArray(data.reviews) ? data.reviews : []
1002
1002
  const running = Array.isArray(data.running) ? data.running : []
1003
1003
  const recent = data.recent || {}
1004
+ const setup = data.setup || {}
1004
1005
  const problems = (Array.isArray(data.problems) ? data.problems : []).filter(
1005
1006
  // 「定时器当前是 active」是正常状态,不该显示成提示(旧版宿主会多带一个换行)
1006
1007
  (p) => !/当前是\s*active\b/.test(String(p)),
@@ -1065,7 +1066,7 @@ window.__ModuleLoader__.load({
1065
1066
  h('span', { style: { fontSize: 12, color: T.textSub } }, t('boardLabel')),
1066
1067
  ...(Array.isArray(snap.data && snap.data.boards) && snap.data.boards.length
1067
1068
  ? snap.data.boards
1068
- : [{ key: 'ops', label: 'ops', path: '/root/ops/KANBAN.json' }]
1069
+ : []
1069
1070
  ).map((b) =>
1070
1071
  h(
1071
1072
  Button,
@@ -1086,6 +1087,33 @@ window.__ModuleLoader__.load({
1086
1087
  h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 4 } }, chips),
1087
1088
  ),
1088
1089
 
1090
+ setup.configured === false
1091
+ ? h(
1092
+ 'div',
1093
+ {
1094
+ style: {
1095
+ ...cardStyle, borderColor: T.brand, fontSize: 12, lineHeight: 1.65,
1096
+ display: 'flex', flexDirection: 'column', gap: 6,
1097
+ },
1098
+ },
1099
+ h('div', { style: { fontWeight: 700, fontSize: 13, color: T.text } }, t('setupTitle')),
1100
+ h('div', { style: { color: T.textSub } }, t('setupBody')),
1101
+ h(
1102
+ 'code',
1103
+ {
1104
+ style: {
1105
+ fontSize: 12, padding: '7px 9px', borderRadius: 8, background: T.fill,
1106
+ border: `1px solid ${T.borderStrong}`, wordBreak: 'break-all',
1107
+ },
1108
+ },
1109
+ setup.initCommand || 'npx dsh-offhours init --repo .',
1110
+ ),
1111
+ h('div', { style: { color: T.textSub } }, t('setupAfter')),
1112
+ setup.configPath
1113
+ ? h('div', { style: { color: T.textSub, wordBreak: 'break-all' } }, `${t('setupConfigPath')} ${setup.configPath}`)
1114
+ : null,
1115
+ )
1116
+ : null,
1089
1117
  snap.error
1090
1118
  ? h('div', { style: { ...cardStyle, borderColor: T.danger, color: T.danger, fontSize: 12 } }, snap.error)
1091
1119
  : null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-offhours",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "DSH 社区插件:把看板待办在低谷/夜间自动做掉(挑卡 → 独占 worktree → worker → 证据 → 合并回收),只依赖配置里的 git 仓库。",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -11,7 +11,8 @@
11
11
  "./package.json": "./package.json"
12
12
  },
13
13
  "bin": {
14
- "offhours": "./bin/offhours.mjs"
14
+ "offhours": "./bin/offhours.mjs",
15
+ "dsh-offhours": "./bin/offhours.mjs"
15
16
  },
16
17
  "files": [
17
18
  "bin/offhours.mjs",
@@ -69,10 +70,10 @@
69
70
  ],
70
71
  "repository": {
71
72
  "type": "git",
72
- "url": "git+https://github.com/jingyichushi/dsh-offhours.git"
73
+ "url": "git+https://github.com/shuke52tt/dsh-offhours.git"
73
74
  },
74
- "homepage": "https://github.com/jingyichushi/dsh-offhours#readme",
75
+ "homepage": "https://github.com/shuke52tt/dsh-offhours#readme",
75
76
  "bugs": {
76
- "url": "https://github.com/jingyichushi/dsh-offhours/issues"
77
+ "url": "https://github.com/shuke52tt/dsh-offhours/issues"
77
78
  }
78
79
  }
package/panel/client.js CHANGED
@@ -1001,6 +1001,7 @@ window.__ModuleLoader__.load({
1001
1001
  const reviews = Array.isArray(data.reviews) ? data.reviews : []
1002
1002
  const running = Array.isArray(data.running) ? data.running : []
1003
1003
  const recent = data.recent || {}
1004
+ const setup = data.setup || {}
1004
1005
  const problems = (Array.isArray(data.problems) ? data.problems : []).filter(
1005
1006
  // 「定时器当前是 active」是正常状态,不该显示成提示(旧版宿主会多带一个换行)
1006
1007
  (p) => !/当前是\s*active\b/.test(String(p)),
@@ -1065,7 +1066,7 @@ window.__ModuleLoader__.load({
1065
1066
  h('span', { style: { fontSize: 12, color: T.textSub } }, t('boardLabel')),
1066
1067
  ...(Array.isArray(snap.data && snap.data.boards) && snap.data.boards.length
1067
1068
  ? snap.data.boards
1068
- : [{ key: 'ops', label: 'ops', path: '/root/ops/KANBAN.json' }]
1069
+ : []
1069
1070
  ).map((b) =>
1070
1071
  h(
1071
1072
  Button,
@@ -1086,6 +1087,33 @@ window.__ModuleLoader__.load({
1086
1087
  h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 4 } }, chips),
1087
1088
  ),
1088
1089
 
1090
+ setup.configured === false
1091
+ ? h(
1092
+ 'div',
1093
+ {
1094
+ style: {
1095
+ ...cardStyle, borderColor: T.brand, fontSize: 12, lineHeight: 1.65,
1096
+ display: 'flex', flexDirection: 'column', gap: 6,
1097
+ },
1098
+ },
1099
+ h('div', { style: { fontWeight: 700, fontSize: 13, color: T.text } }, t('setupTitle')),
1100
+ h('div', { style: { color: T.textSub } }, t('setupBody')),
1101
+ h(
1102
+ 'code',
1103
+ {
1104
+ style: {
1105
+ fontSize: 12, padding: '7px 9px', borderRadius: 8, background: T.fill,
1106
+ border: `1px solid ${T.borderStrong}`, wordBreak: 'break-all',
1107
+ },
1108
+ },
1109
+ setup.initCommand || 'npx dsh-offhours init --repo .',
1110
+ ),
1111
+ h('div', { style: { color: T.textSub } }, t('setupAfter')),
1112
+ setup.configPath
1113
+ ? h('div', { style: { color: T.textSub, wordBreak: 'break-all' } }, `${t('setupConfigPath')} ${setup.configPath}`)
1114
+ : null,
1115
+ )
1116
+ : null,
1089
1117
  snap.error
1090
1118
  ? h('div', { style: { ...cardStyle, borderColor: T.danger, color: T.danger, fontSize: 12 } }, snap.error)
1091
1119
  : null,
package/panel/index.js CHANGED
@@ -322,6 +322,7 @@ async function buildStatus(boardArg) {
322
322
  if (Number.isFinite(n)) windowLeft = n
323
323
  } catch { /* 忽略:面板降级为不显示余量 */ }
324
324
  const COOLDOWN_MIN = cooldownMinFor(board)
325
+ const configured = !!(CFG._path && existsSync(CFG._path) && Array.isArray(CFG.boards) && CFG.boards.length > 0)
325
326
  const out = {
326
327
  ok: true,
327
328
  now: fmtLocal(now),
@@ -341,6 +342,13 @@ async function buildStatus(boardArg) {
341
342
  running: [],
342
343
  recent: { log: [], receipt: null, runs: [] },
343
344
  problems: [],
345
+ setup: {
346
+ configured,
347
+ configPath: CFG._path || '',
348
+ initCommand: 'npx dsh-offhours init --repo .',
349
+ doctorCommand: 'npx dsh-offhours doctor',
350
+ restartHint: '安装或更新插件后重启 dsh,面板才会加载新代码。',
351
+ },
344
352
  }
345
353
 
346
354
  // SCHEDULER-REPORT:按配置的调度方式如实报告(systemd / 插件内 interval)。
@@ -379,7 +387,7 @@ async function buildStatus(boardArg) {
379
387
  }
380
388
  } catch { out.problems.push('systemctl 查询失败(定时器状态未知)') }
381
389
  }
382
- if (CFG.scheduler === 'plugin' && out.timer.active === 'unknown') {
390
+ if (configured && CFG.scheduler === 'plugin' && out.timer.active === 'unknown') {
383
391
  out.problems.push('插件内调度器没有心跳(确认 dsh 进程在跑,或 schedule.enabled=false)')
384
392
  }
385
393
 
@@ -544,6 +552,15 @@ async function buildStatus(boardArg) {
544
552
  out.recent.runs = []
545
553
  }
546
554
 
555
+ if (!configured) {
556
+ // 未配置时只展示 setup 卡片;不要把空 path 触发的“看板读不到/冷却未知”当错误吓用户。
557
+ out.problems = []
558
+ out.todos = []
559
+ out.reviews = []
560
+ out.running = []
561
+ out.recent = { log: [], receipt: null, runs: [] }
562
+ out.timer.raw = 'unconfigured'
563
+ }
547
564
  return out
548
565
  }
549
566
 
@@ -77,6 +77,10 @@
77
77
  "liveEmpty": "No output yet",
78
78
  "liveFollow": "auto-follow",
79
79
  "runningNowText": "Running now:",
80
- "openWorkerSession": "Open worker session"
80
+ "openWorkerSession": "Open worker session",
81
+ "setupTitle": "One-time setup",
82
+ "setupBody": "Run this inside the git repository you want to batch:",
83
+ "setupAfter": "Then reload or restart dsh. The board appears here; work only starts off-peak while the machine is idle.",
84
+ "setupConfigPath": "Config file:"
81
85
  }
82
86
  }
@@ -77,6 +77,10 @@
77
77
  "liveEmpty": "还没有输出(等它开始吐字)",
78
78
  "liveFollow": "自动跟随最新",
79
79
  "runningNowText": "正在跑批:",
80
- "openWorkerSession": "打开处理会话(对话/轨迹)"
80
+ "openWorkerSession": "打开处理会话(对话/轨迹)",
81
+ "setupTitle": "先完成一次性初始化",
82
+ "setupBody": "在要跑批的 git 仓库里运行:",
83
+ "setupAfter": "然后刷新或重启 dsh;左侧会出现看板,只有低谷且机器空闲时才会自动跑。",
84
+ "setupConfigPath": "配置文件:"
81
85
  }
82
86
  }
@@ -17,10 +17,11 @@ description: "夜间/低谷自动跑批(dsh-offhours):把看板待办在
17
17
  ## 建卡(唯一正确入口)
18
18
 
19
19
  ```bash
20
- offhours card add "<标题>" --summary "<目标 + 判据(命令) + 涉及面 + 不做什么>"
20
+ npx dsh-offhours card add "<标题>" --summary "<目标 + 判据(命令) + 涉及面 + 不做什么>"
21
21
  ```
22
22
 
23
23
  - 必须在 dsh 会话里执行:它会用 `DSH_SESSION_ID` 盖章 `sourceSessionId`;
24
+ - CLI 没进全局 PATH:统一用 `npx dsh-offhours ...`(或 profile 里的 `node_modules/.bin/offhours`)。
24
25
  **没有 sourceSessionId 的卡跑批永远不会捡**(不是 bug,是隔离要求)。
25
26
  - 用 dsh-kanban 的 `board_add` 建的卡不带 session,只适合人工看板,不适合跑批。
26
27
  - 判据必须是命令,不是形容词;一张卡 = 一个可独立合并的成果。
@@ -28,10 +29,10 @@ offhours card add "<标题>" --summary "<目标 + 判据(命令) + 涉及面 +
28
29
  ## 查状态 / 手动跑
29
30
 
30
31
  ```bash
31
- offhours doctor # 依赖、配置、权限体检(先跑这个)
32
- offhours status # 看板计数 + 心跳 + 最近状态(JSON)
33
- offhours run --once # 手动跑一轮(生产调度器之外)
34
- offhours run --dry-run # 只看会挑哪张,不启动 worker
32
+ npx dsh-offhours doctor # 依赖、配置、权限体检(先跑这个)
33
+ npx dsh-offhours status # 看板计数 + 心跳 + 最近状态(JSON)
34
+ npx dsh-offhours run --once # 手动跑一轮(生产调度器之外)
35
+ npx dsh-offhours run --dry-run # 只看会挑哪张,不启动 worker
35
36
  ```
36
37
 
37
38
  面板:dsh Web 左侧「夜间跑批」;`/offhours` 命令也可查状态。