dsh-project-deck 0.0.0-stage → 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.
@@ -0,0 +1,4 @@
1
+ # dsh-project-deck bundle patch: one insert row activating the personal-agent panel plugin.
2
+ - insert:
3
+ - id: dsh-project-deck
4
+ name: 'dsh-project-deck'
package/lib/client.js ADDED
@@ -0,0 +1,366 @@
1
+ // dsh-project-deck client half: 在 DSH 界面里嵌入项目甲板 Project Deck(deck 面板)。
2
+ // 面板本体是独立进程(~/.project-deck/bin/panel.py,http://127.0.0.1:9199),
3
+ // 这里用 iframe 内嵌,避免在插件里重复实现一遍 UI。
4
+ window.__ModuleLoader__.load({
5
+ id: 'dsh-project-deck',
6
+ factory: (require) => {
7
+ const module = { exports: {} }
8
+ const exports = module.exports
9
+ const React = require('react')
10
+ const h = React.createElement
11
+ const PANEL = 'http://127.0.0.1:9199'
12
+
13
+ // 兼容读取可选服务(uiWorkspace 在 0.1.6+,旧版回退 sessions)
14
+ function optionalService(ctx, name) {
15
+ try {
16
+ const value = ctx && ctx.get ? ctx.get(name) : undefined
17
+ if (value != null) return value
18
+ } catch { /* get 异常时继续属性访问 */ }
19
+ try { return (ctx && ctx[name]) || null } catch { return null }
20
+ }
21
+
22
+ // 自定义深链:#deck-session=<id>(DSH 本身没有 URL scheme / 会话路由,
23
+ // 所以由插件读 hash 后自己跳;链接对普通浏览器也可用)
24
+ function readDeepLink() {
25
+ try {
26
+ const hash = String(window.location.hash || '')
27
+ const m = hash.match(/deck-session=([^&]+)/)
28
+ if (m) return { kind: 'session', value: decodeURIComponent(m[1]) }
29
+ const p = hash.match(/deck-project=([^&]+)/)
30
+ if (p) return { kind: 'project', value: decodeURIComponent(p[1]) }
31
+ return null
32
+ } catch { return null }
33
+ }
34
+
35
+ function clearDeepLink() {
36
+ try {
37
+ if (/deck-session=/.test(window.location.hash)) {
38
+ history.replaceState(null, '', window.location.pathname + window.location.search)
39
+ }
40
+ } catch { /* 忽略 */ }
41
+ }
42
+
43
+ function sessionKnown(ctx, sessionId) {
44
+ const sessions = optionalService(ctx, 'sessions')
45
+ if (!sessions) return false
46
+ try {
47
+ const snap = sessions.list && sessions.list.getSnapshot ? sessions.list.getSnapshot() : null
48
+ if (snap && snap.byId && snap.byId[sessionId]) return true
49
+ if (sessions.binding && sessions.binding(sessionId)) return true
50
+ } catch { /* 忽略 */ }
51
+ return false
52
+ }
53
+
54
+ function findWorkspaceByPath(ctx, root) {
55
+ const ws = optionalService(ctx, 'workspaces')
56
+ try {
57
+ const snap = ws && ws.list ? ws.list.getSnapshot() : null
58
+ const items = (snap && snap.items) || []
59
+ return items.find((w) => w.path === root) || null
60
+ } catch { return null }
61
+ }
62
+
63
+ function openProjectWith(ctx, payload) {
64
+ const uw = optionalService(ctx, 'uiWorkspace')
65
+ if (!uw) return Promise.resolve(false)
66
+ if (payload.sessionId && sessionKnown(ctx, payload.sessionId)) {
67
+ return Promise.resolve(openSessionWith(ctx, payload.sessionId))
68
+ }
69
+ // 没有会话:找到 workspace(没有就创建),再开会话
70
+ const existing = findWorkspaceByPath(ctx, payload.root)
71
+ const ensureWorkspace = existing
72
+ ? Promise.resolve(existing)
73
+ : (optionalService(ctx, 'workspaces') && optionalService(ctx, 'workspaces').create
74
+ ? optionalService(ctx, 'workspaces').create({ path: payload.root }).catch(() => null)
75
+ : Promise.resolve(null))
76
+ return ensureWorkspace.then((ws) => {
77
+ const wid = ws && ws.workspaceId
78
+ if (!wid || typeof uw.openWorkspace !== 'function') return false
79
+ return uw.openWorkspace(wid).then(() => true).catch(() => false)
80
+ })
81
+ }
82
+
83
+ function openSessionWith(ctx, sessionId) {
84
+ const uw = optionalService(ctx, 'uiWorkspace')
85
+ if (uw && typeof uw.openSession === 'function') {
86
+ try { uw.openSession(sessionId); return true } catch (e) { console.warn('[dsh-project-deck] uiWorkspace.openSession 失败', e) }
87
+ }
88
+ const sessions = optionalService(ctx, 'sessions')
89
+ if (sessions && typeof sessions.open === 'function') {
90
+ try { sessions.open(sessionId); return true } catch (e) { console.warn('[dsh-project-deck] sessions.open 失败', e) }
91
+ }
92
+ console.warn('[dsh-project-deck] 没有可用的会话导航服务(uiWorkspace / sessions 都不可用)')
93
+ return false
94
+ }
95
+
96
+ const css = `
97
+ .deck-fab{position:fixed;right:18px;bottom:112px;left:auto;z-index:120;position:fixed;pointer-events:auto;touch-action:none;cursor:grab;user-select:none;display:flex;align-items:center;justify-content:center;
98
+ width:46px;height:46px;border-radius:50%;border:1px solid rgba(53,208,186,.45);cursor:pointer;
99
+ background:linear-gradient(180deg,#12303a,#0f1e1d);color:#7ce7da;font-size:20px;line-height:1;
100
+ box-shadow:0 10px 24px rgba(0,0,0,.35),0 2px 6px rgba(0,0,0,.2);transition:filter .12s,transform .12s;
101
+ font-family:system-ui,-apple-system,"PingFang SC",sans-serif;padding:0}
102
+ .deck-fab:hover{filter:brightness(1.12);transform:translateY(-1px)}
103
+ .deck-fab:active{cursor:grabbing}
104
+ .deck-fab svg{display:block}
105
+ .deck-fab .deck-badge{position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;border-radius:9px;
106
+ background:#ef5b5b;color:#fff;font-size:10px;line-height:16px;text-align:center;font-weight:700;box-shadow:0 0 0 2px #0b0f14}
107
+ .deck-fab.ai-on{box-shadow:0 0 0 2px rgba(53,208,186,.55),0 10px 24px rgba(0,0,0,.35)}
108
+ .deck-fab:active{transform:translateY(0)}
109
+ .deck-mask{position:fixed;inset:0;z-index:130;background:rgba(4,8,12,.62);display:flex;align-items:center;justify-content:center;padding:24px;pointer-events:auto}
110
+ .deck-box{width:min(1180px,94vw);height:min(860px,88vh);background:#0b0f14;border:1px solid #1f2a37;border-radius:14px;
111
+ display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 60px rgba(0,0,0,.45)}
112
+ .deck-head{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid #1f2a37;
113
+ background:#0f151d;color:#e6edf3;font:600 13px/1.4 system-ui,-apple-system,"PingFang SC",sans-serif}
114
+ .deck-head .dot{width:8px;height:8px;border-radius:50%;background:#3ddc97;box-shadow:0 0 8px rgba(61,220,151,.7)}
115
+ .deck-head .sp{flex:1}
116
+ .deck-head a,.deck-head button{color:#8b98a5;background:none;border:1px solid #1f2a37;border-radius:7px;
117
+ padding:4px 10px;font-size:12px;cursor:pointer;text-decoration:none;font-family:inherit}
118
+ .deck-head a:hover,.deck-head button:hover{color:#e6edf3;border-color:#2bb8a5}
119
+ .deck-frame{flex:1;width:100%;border:0;background:#0b0f14}
120
+ .deck-tip{padding:8px 14px;color:#6f7d8a;font:11px/1.5 system-ui,-apple-system,"PingFang SC",sans-serif;border-top:1px solid #1a232d}
121
+ `
122
+
123
+ if (typeof document !== 'undefined' && !document.getElementById('dsh-project-deck-style')) {
124
+ const style = document.createElement('style')
125
+ style.id = 'dsh-project-deck-style'
126
+ style.textContent = css
127
+ document.head.appendChild(style)
128
+ }
129
+
130
+ function Panel(props) {
131
+ const appCtx = props && props.appCtx
132
+ const [open, setOpen] = React.useState(false)
133
+ const [loaded, setLoaded] = React.useState(false)
134
+ const frameRef = React.useRef(null)
135
+
136
+ // 悬浮按钮可拖到页面任意位置(位置持久化)
137
+ const [pos, setPos] = React.useState(() => {
138
+ try { const v = JSON.parse(localStorage.getItem('deck-fab-pos') || 'null'); return v && typeof v.x === 'number' ? v : null } catch { return null }
139
+ })
140
+ const [summary, setSummary] = React.useState({ auto: 0, assist: 0, pending: 0 })
141
+ React.useEffect(() => {
142
+ let alive = true
143
+ const load = () => {
144
+ fetch('http://127.0.0.1:9199/api/state', { cache: 'no-store' })
145
+ .then((r) => r.json())
146
+ .then((d) => {
147
+ if (!alive) return
148
+ const ps = d.projects || []
149
+ setSummary({
150
+ auto: ps.filter((p) => p.autonomy && p.autonomy.mode === 'auto').length,
151
+ assist: ps.filter((p) => p.autonomy && p.autonomy.mode === 'assist').length,
152
+ pending: (d.approvals || []).length,
153
+ })
154
+ })
155
+ .catch(() => {})
156
+ }
157
+ load()
158
+ const t = setInterval(load, 60000)
159
+ return () => { alive = false; clearInterval(t) }
160
+ }, [])
161
+
162
+ const dragState = React.useRef(null)
163
+ const movedRef = React.useRef(false)
164
+
165
+ const clamp = (x, y) => ({
166
+ x: Math.max(6, Math.min(x, window.innerWidth - 54)),
167
+ y: Math.max(6, Math.min(y, window.innerHeight - 54)),
168
+ })
169
+
170
+ // 用 window 级监听,避免指针移出按钮就丢事件(之前只能按住按钮原地挪)
171
+ React.useEffect(() => {
172
+ const onMove = (e) => {
173
+ const d = dragState.current
174
+ if (!d) return
175
+ if (!d.moved && (Math.abs(e.clientX - d.sx) > 3 || Math.abs(e.clientY - d.sy) > 3)) d.moved = true
176
+ setPos(clamp(e.clientX - d.dx, e.clientY - d.dy))
177
+ }
178
+ const onUp = () => {
179
+ const d = dragState.current
180
+ if (!d) return
181
+ dragState.current = null
182
+ movedRef.current = d.moved
183
+ setPos((cur) => { if (cur) { try { localStorage.setItem('deck-fab-pos', JSON.stringify(cur)) } catch {} } return cur })
184
+ }
185
+ window.addEventListener('pointermove', onMove, true)
186
+ window.addEventListener('pointerup', onUp, true)
187
+ window.addEventListener('mousemove', onMove, true)
188
+ window.addEventListener('mouseup', onUp, true)
189
+ return () => {
190
+ window.removeEventListener('pointermove', onMove, true)
191
+ window.removeEventListener('pointerup', onUp, true)
192
+ window.removeEventListener('mousemove', onMove, true)
193
+ window.removeEventListener('mouseup', onUp, true)
194
+ }
195
+ }, [])
196
+
197
+ const fabStyle = pos ? { left: pos.x + 'px', top: pos.y + 'px', right: 'auto', bottom: 'auto' } : undefined
198
+
199
+ const onDragStart = (e) => {
200
+ const r = e.currentTarget.getBoundingClientRect()
201
+ dragState.current = { dx: e.clientX - r.left, dy: e.clientY - r.top, sx: e.clientX, sy: e.clientY, moved: false }
202
+ movedRef.current = false
203
+ if (!pos) setPos({ x: r.left, y: r.top })
204
+ if (e.preventDefault) e.preventDefault()
205
+ }
206
+
207
+ // 面板里也能改图标位置(拖拽失效时的兜底)
208
+ React.useEffect(() => {
209
+ const onPos = (e) => {
210
+ if (e.origin !== PANEL) return
211
+ const d = e.data || {}
212
+ if (d.type !== 'deck:set-fab-pos') return
213
+ const W = window.innerWidth, H = window.innerHeight
214
+ const spots = {
215
+ 'right-bottom': { x: W - 60, y: H - 160 },
216
+ 'left-bottom': { x: 16, y: H - 160 },
217
+ 'right-top': { x: W - 60, y: 84 },
218
+ 'center-right': { x: W - 60, y: Math.round(H / 2) },
219
+ default: { x: W - 60, y: H - 160 },
220
+ }
221
+ const next = spots[d.pos] || spots.default
222
+ setPos(clamp(next.x, next.y))
223
+ try { localStorage.setItem('deck-fab-pos', JSON.stringify(clamp(next.x, next.y))) } catch {}
224
+ }
225
+ window.addEventListener('message', onPos)
226
+ return () => window.removeEventListener('message', onPos)
227
+ }, [])
228
+
229
+ React.useEffect(() => {
230
+ if (!open) return undefined
231
+ const onKey = (e) => { if (e.key === 'Escape') setOpen(false) }
232
+ window.addEventListener('keydown', onKey)
233
+ return () => window.removeEventListener('keydown', onKey)
234
+ }, [open])
235
+
236
+ const openPanel = () => { setLoaded(false); setOpen(true) }
237
+
238
+ return h(React.Fragment, null,
239
+ h('button', {
240
+ className: 'deck-fab' + (summary.auto > 0 ? ' ai-on' : ''),
241
+ style: fabStyle,
242
+ title: '项目甲板 · AI 项目管理与自动推进(可拖动 v4)'
243
+ + (summary.auto ? `|auto ${summary.auto}` : '')
244
+ + (summary.pending ? `|待你拍板 ${summary.pending}` : ''),
245
+ onPointerDown: onDragStart,
246
+ onMouseDown: onDragStart,
247
+ onClick: () => { if (!movedRef.current) openPanel(); movedRef.current = false },
248
+ onDragStart: (e) => e.preventDefault(),
249
+ },
250
+ h('svg', { width: 26, height: 26, viewBox: '0 0 24 24', fill: 'none', stroke: '#7ce7da', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round' },
251
+ // 左:AI 机器人
252
+ h('rect', { x: 1.6, y: 8.2, width: 8.2, height: 7.2, rx: 2.2 }),
253
+ h('path', { d: 'M5.7 8.2V6.2' }),
254
+ h('circle', { cx: 5.7, cy: 5, r: 1.05, fill: '#7ce7da', stroke: 'none' }),
255
+ h('circle', { cx: 4.1, cy: 11.5, r: 0.9, fill: '#7ce7da', stroke: 'none' }),
256
+ h('circle', { cx: 7.3, cy: 11.5, r: 0.9, fill: '#7ce7da', stroke: 'none' }),
257
+ h('path', { d: 'M4.5 13.7h2.4' }),
258
+ // 右:待办清单(两条已完成)
259
+ h('rect', { x: 11.8, y: 4.6, width: 10.4, height: 14.8, rx: 2.6 }),
260
+ h('path', { d: 'M13.9 9.4l1.1 1.1 1.9-2.1' }),
261
+ h('path', { d: 'M18.1 9.9h2.3' }),
262
+ h('path', { d: 'M13.9 14l1.1 1.1 1.9-2.1' }),
263
+ h('path', { d: 'M18.1 14.5h2.3' }),
264
+ ),
265
+ summary.pending > 0 ? h('span', { className: 'deck-badge' }, String(summary.pending)) : null,
266
+ ),
267
+ open
268
+ ? h('div', { className: 'deck-mask', onClick: (e) => { if (e.target === e.currentTarget) setOpen(false) } },
269
+ h('div', { className: 'deck-box' },
270
+ h('div', { className: 'deck-head' },
271
+ h('span', { className: 'dot' }),
272
+ h('span', null, '项目甲板 Project Deck'),
273
+ h('span', { className: 'sp' }),
274
+ h('a', { href: PANEL, target: '_blank', rel: 'noreferrer' }, '新窗口'),
275
+ h('button', { onClick: () => setOpen(false) }, '关闭'),
276
+ ),
277
+ h('iframe', {
278
+ ref: frameRef,
279
+ className: 'deck-frame',
280
+ src: PANEL,
281
+ title: 'deck panel',
282
+ onLoad: () => setLoaded(true),
283
+ }),
284
+ h('div', { className: 'deck-tip' },
285
+ loaded
286
+ ? '面板由 ~/.project-deck/bin/panel.py 提供(127.0.0.1:9199)'
287
+ : '正在连接面板… 若长时间空白,请在终端运行 deck status 检查服务是否在跑',
288
+ ),
289
+ ),
290
+ )
291
+ : null,
292
+ )
293
+ }
294
+
295
+ // 崩溃兜底:任何渲染错误都不让图标凭空消失
296
+ class Boundary extends React.Component {
297
+ constructor(props) { super(props); this.state = { err: null } }
298
+ static getDerivedStateFromError(err) { return { err } }
299
+ render() {
300
+ if (this.state.err) {
301
+ return h('div', {
302
+ title: String(this.state.err && this.state.err.message || this.state.err),
303
+ style: { position: 'fixed', right: 12, bottom: 100, zIndex: 121, background: '#2a1518',
304
+ color: '#ffb3b3', padding: '5px 9px', borderRadius: 8, fontSize: 11,
305
+ border: '1px solid #4a2226', cursor: 'pointer', pointerEvents: 'auto' },
306
+ onClick: () => this.setState({ err: null }),
307
+ }, '项目甲板加载失败(点我重试)')
308
+ }
309
+ return this.props.children
310
+ }
311
+ }
312
+
313
+ exports.name = 'dsh-project-deck'
314
+ exports.inject = ['slots']
315
+ exports.apply = function apply(ctx) {
316
+ const slots = ctx.get('slots')
317
+ if (!slots) {
318
+ console.error('[dsh-project-deck] slots 服务不可用')
319
+ return
320
+ }
321
+ // 深链:支持 #deck-session=<id>(会话)与 #deck-project=<路径>(项目→开会话)。
322
+ // 刚加载时会话列表还没拉回来,直接 open 会被丢掉,所以要等就绪后重试。
323
+ let deepLinkTimer = null
324
+ const finishDeepLink = (label) => {
325
+ clearDeepLink()
326
+ console.log('[dsh-project-deck] deep link opened', label)
327
+ }
328
+ const tryDeepLink = (target, attempt) => {
329
+ const cur = readDeepLink()
330
+ if (!cur || cur.kind !== target.kind || cur.value !== target.value) return
331
+ if (target.kind === 'session') {
332
+ if (sessionKnown(ctx, target.value)) {
333
+ if (openSessionWith(ctx, target.value)) { finishDeepLink(target.value); return }
334
+ }
335
+ } else {
336
+ openProjectWith(ctx, { root: target.value, sessionId: null }).then((ok) => {
337
+ if (ok) finishDeepLink(target.value)
338
+ else if (attempt < 50) deepLinkTimer = setTimeout(() => tryDeepLink(target, attempt + 1), 400)
339
+ else console.warn('[dsh-project-deck] deep link 放弃:workspace 创建/打开失败', target.value)
340
+ })
341
+ return
342
+ }
343
+ if (attempt >= 50) {
344
+ console.warn('[dsh-project-deck] deep link 放弃:会话未出现在列表里', target.value)
345
+ return
346
+ }
347
+ deepLinkTimer = setTimeout(() => tryDeepLink(target, attempt + 1), 400)
348
+ }
349
+ const onDeepLink = () => {
350
+ const target = readDeepLink()
351
+ if (!target) return
352
+ if (deepLinkTimer) clearTimeout(deepLinkTimer)
353
+ tryDeepLink(target, 0)
354
+ }
355
+ onDeepLink()
356
+ window.addEventListener('hashchange', onDeepLink)
357
+
358
+ slots.inject('shell.overlay', () => slots.register(
359
+ { name: 'shell.overlay', id: 'deck-panel', order: 6, label: '项目甲板' },
360
+ (props) => h(Boundary, null, h(Panel, Object.assign({}, props, { appCtx: ctx }))),
361
+ ))
362
+ console.log('[dsh-project-deck] client ready')
363
+ }
364
+ return module.exports
365
+ },
366
+ })
package/lib/index.js ADDED
@@ -0,0 +1,98 @@
1
+ // dsh-project-deck host half: 把本机的 deck CLI 暴露成模型工具。
2
+ // 面板本体是独立进程(~/.project-deck/bin/panel.py, http://127.0.0.1:9199);
3
+ // 客户端半边(client.js)以 iframe 嵌入,不依赖本文件的 HTTP 路由。
4
+ import { execFile } from 'node:child_process'
5
+ import { existsSync } from 'node:fs'
6
+ import { homedir } from 'node:os'
7
+ import { join } from 'node:path'
8
+
9
+ export const name = 'dsh-project-deck'
10
+ export const inject = ['tools']
11
+
12
+ const PAGENT = join(homedir(), '.person-agent', 'bin', 'deck')
13
+
14
+ function run(args, timeout = 60000) {
15
+ return new Promise((resolve) => {
16
+ if (!existsSync(PAGENT)) {
17
+ resolve('❌ 找不到 deck CLI:' + PAGENT + '(项目甲板 尚未初始化)')
18
+ return
19
+ }
20
+ execFile(PAGENT, args, {
21
+ timeout,
22
+ maxBuffer: 4 * 1024 * 1024,
23
+ env: { ...process.env, PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin' },
24
+ }, (err, stdout, stderr) => {
25
+ const out = String(stdout || '').trim()
26
+ const errText = String(stderr || '').trim()
27
+ if (err && !out) {
28
+ resolve('❌ deck ' + args.join(' ') + ' 失败:' + (errText || err.message))
29
+ return
30
+ }
31
+ resolve(out + (errText ? '\n' + errText : ''))
32
+ })
33
+ })
34
+ }
35
+
36
+ function toolText(name, description, argsBuilder) {
37
+ return {
38
+ name,
39
+ description,
40
+ parameters: { type: 'object', properties: {} },
41
+ output: {
42
+ schema: { type: 'string' },
43
+ render(_args, value) {
44
+ return [{ type: 'text', text: String(value || '') }]
45
+ },
46
+ },
47
+ async execute() {
48
+ return run(argsBuilder())
49
+ },
50
+ }
51
+ }
52
+
53
+ export function apply(ctx) {
54
+ const tools = ctx.get('tools')
55
+ if (!tools) {
56
+ console.error('[dsh-project-deck] tools 服务不可用,跳过工具注册')
57
+ return
58
+ }
59
+
60
+ const registrations = [
61
+ toolText(
62
+ 'deck_status',
63
+ '查看项目甲板 运行状态:项目数、周期播报开关与间隔、守护进程、微信通道、自动发现上次扫描时间。',
64
+ () => ['status'],
65
+ ),
66
+ toolText(
67
+ 'deck_list',
68
+ '列出项目注册表里的全部项目(id、名称、类型、状态、优先级、根目录)。',
69
+ () => ['list'],
70
+ ),
71
+ toolText(
72
+ 'deck_scan',
73
+ '扫描本机(以及已上报的其它节点)发现新项目候选,返回评分与理由;达到阈值的项目会在守护进程扫描时自动登记。',
74
+ () => ['scan'],
75
+ ),
76
+ toolText(
77
+ 'deck_nodes',
78
+ '查看多节点扫描的节点列表:平台、最近上报时间、项目数、在线状态。',
79
+ () => ['nodes'],
80
+ ),
81
+ toolText(
82
+ 'deck_report',
83
+ '立即生成项目播报并推送到微信(文字 + 可选的看板图)。会真实发送消息,仅在用户明确要求时调用。',
84
+ () => ['report'],
85
+ ),
86
+ ]
87
+
88
+ const registered = []
89
+ for (const reg of registrations) {
90
+ try {
91
+ tools.register(reg)
92
+ registered.push(reg.name)
93
+ } catch (e) {
94
+ console.error('[dsh-project-deck] 注册工具失败', reg.name, (e && e.message) || e)
95
+ }
96
+ }
97
+ console.log('[dsh-project-deck] host ready, tools:', registered.join(', '))
98
+ }
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "dsh-project-deck",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.2",
4
+ "description": "DeepSeek Harness plugin: a project deck for your repos — registry, dashboard, scheduled WeChat reports, auto-discovery, multi-node scanning.",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "default": "./lib/index.js"
10
+ },
11
+ "./client": {
12
+ "default": "./lib/client.js"
13
+ },
14
+ "./cordis.patch.yml": "./cordis.patch.yml",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "dsh": {
18
+ "bundle": {
19
+ "patch": "./cordis.patch.yml"
20
+ },
21
+ "client": {
22
+ "platform": "web",
23
+ "immediately": true
24
+ }
25
+ },
26
+ "files": [
27
+ "lib",
28
+ "cordis.patch.yml",
29
+ "backend",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "keywords": [
34
+ "dsh",
35
+ "deepseek-harness",
36
+ "plugin",
37
+ "project-management",
38
+ "wechat",
39
+ "dashboard"
40
+ ],
41
+ "license": "MIT",
42
+ "engines": {
43
+ "node": ">=22.15"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/peng456/dsh-project-deck.git"
48
+ },
49
+ "author": "peng456",
50
+ "homepage": "https://github.com/peng456/dsh-project-deck#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/peng456/dsh-project-deck/issues"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "registry": "https://registry.npmjs.org/"
57
+ }
58
+ }