@weibaohui/skills-management 0.2.0 → 0.4.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/client/bundle.js +132 -62
- package/client/index.js +25 -62
- package/package.json +2 -1
- package/src/index.js +8 -104
package/client/bundle.js
CHANGED
|
@@ -8,6 +8,113 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
var exports = module.exports
|
|
9
9
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
|
|
10
10
|
var React = require("react")
|
|
11
|
+
/**
|
|
12
|
+
* @weibaohui/dsh-plugin-kit — client source(由消费者构建脚本内联进 bundle,
|
|
13
|
+
* 不经 loader 运行时加载)。对外暴露 PluginKit:
|
|
14
|
+
*
|
|
15
|
+
* PluginKit.substituteParams(template, params) — {{key}} 模板插值
|
|
16
|
+
* PluginKit.makeActionShareDialog(React, opts) — 返回 ActionShareDialog 组件
|
|
17
|
+
*
|
|
18
|
+
* ActionShareDialog props:
|
|
19
|
+
* title / hint / rows: [[label, value], ...] / initialPrompt
|
|
20
|
+
* run: async (prompt) => { jobId } — 发起执行
|
|
21
|
+
* poll: async (jobId) => { status, output, code }
|
|
22
|
+
* labels: { copy, copied, run, running, done, failed, outputLabel, openSession, close }
|
|
23
|
+
* onOpenSession: (sessionId) => void — 可选;job 出现 sessionId 时渲染「打开会话」
|
|
24
|
+
* onClose
|
|
25
|
+
*
|
|
26
|
+
* 全部样式内联(主题 token + 回退值),消费者无需自带 CSS。
|
|
27
|
+
*/
|
|
28
|
+
var PluginKit = (function () {
|
|
29
|
+
function substituteParams(template, params) {
|
|
30
|
+
var out = String(template || '')
|
|
31
|
+
for (var key in (params || {})) out = out.split('{{' + key + '}}').join(String(params[key]))
|
|
32
|
+
return out
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeActionShareDialog(React, options) {
|
|
36
|
+
options = options || {}
|
|
37
|
+
var h = React.createElement
|
|
38
|
+
var useState = React.useState
|
|
39
|
+
var useEffect = React.useEffect
|
|
40
|
+
var doFetch = options.fetch || (typeof fetch !== 'undefined' ? fetch : null)
|
|
41
|
+
var inputStyle = { width: '100%', minHeight: 190, resize: 'vertical', fontFamily: 'var(--dsw-font-family)', lineHeight: 1.6, fontSize: 12, background: 'var(--dsw-alias-bg-layer-2,transparent)', color: 'inherit', border: '1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3))', borderRadius: '8px', padding: '10px', boxSizing: 'border-box' }
|
|
42
|
+
var btnStyle = { background: 'transparent', color: 'inherit', border: '1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3))', borderRadius: '8px', padding: '5px 12px', fontSize: 13, cursor: 'pointer', font: 'inherit' }
|
|
43
|
+
var primaryStyle = Object.assign({}, btnStyle, { background: 'var(--dsw-alias-brand-primary,#4a7dff)', borderColor: 'var(--dsw-alias-brand-primary,#4a7dff)', color: '#fff' })
|
|
44
|
+
|
|
45
|
+
return function ActionShareDialog(props) {
|
|
46
|
+
var title = props.title
|
|
47
|
+
var hint = props.hint
|
|
48
|
+
var labels = props.labels || {}
|
|
49
|
+
var _p = useState(props.initialPrompt || '')
|
|
50
|
+
var prompt = _p[0]; var setPrompt = _p[1]
|
|
51
|
+
var _j = useState(null)
|
|
52
|
+
var job = _j[0]; var setJob = _j[1]
|
|
53
|
+
var _b = useState(false)
|
|
54
|
+
var busy = _b[0]; var setBusy = _b[1]
|
|
55
|
+
var _c = useState(false)
|
|
56
|
+
var copied = _c[0]; var setCopied = _c[1]
|
|
57
|
+
var _e = useState('')
|
|
58
|
+
var error = _e[0]; var setError = _e[1]
|
|
59
|
+
var _d = useState(false)
|
|
60
|
+
var dirty = _d[0]; var setDirty = _d[1]
|
|
61
|
+
|
|
62
|
+
// initialPrompt 异步到位(如宿主先要下发真实路径)时跟随刷新;用户编辑过则不打断
|
|
63
|
+
useEffect(function () {
|
|
64
|
+
if (!dirty) setPrompt(props.initialPrompt || '')
|
|
65
|
+
}, [props.initialPrompt])
|
|
66
|
+
|
|
67
|
+
useEffect(function () {
|
|
68
|
+
if (job === null || job.status !== 'running' || typeof props.poll !== 'function') return
|
|
69
|
+
var timer = setInterval(function () {
|
|
70
|
+
props.poll(job.jobId).then(function (d) {
|
|
71
|
+
setJob({ jobId: job.jobId, status: d.status, output: d.output || '', code: d.code !== undefined ? d.code : null, sessionId: d.sessionId })
|
|
72
|
+
}).catch(function () {})
|
|
73
|
+
}, 1500)
|
|
74
|
+
return function () { clearInterval(timer) }
|
|
75
|
+
}, [job !== null && job.jobId])
|
|
76
|
+
|
|
77
|
+
var doRun = function () {
|
|
78
|
+
if (typeof props.run !== 'function') return
|
|
79
|
+
setBusy(true); setError('')
|
|
80
|
+
props.run(prompt).then(function (r) {
|
|
81
|
+
setJob({ jobId: r.jobId, status: 'running', output: '', code: null })
|
|
82
|
+
}).catch(function (e) { setError(String(e && e.message)) }).finally(function () { setBusy(false) })
|
|
83
|
+
}
|
|
84
|
+
var canOpenSession = typeof props.onOpenSession === 'function' && job !== null && job.sessionId
|
|
85
|
+
var openSession = function () { props.onOpenSession(job.sessionId) }
|
|
86
|
+
var copy = function () {
|
|
87
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard && navigator.clipboard.writeText) {
|
|
88
|
+
navigator.clipboard.writeText(prompt).then(function () { setCopied(true); setTimeout(function () { setCopied(false) }, 1500) }).catch(function () {})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
var statusText = job === null ? '' : job.status === 'running' ? (labels.running || 'running') : job.status === 'done' ? (labels.done || 'done') : (labels.failed || 'failed') + (job.code != null ? ' (' + job.code + ')' : '')
|
|
92
|
+
|
|
93
|
+
return h('div', { onClick: function (e) { if (e.target === e.currentTarget && props.onClose) props.onClose() }, style: { position: 'fixed', inset: 0, zIndex: 2147483000, background: 'rgba(0,0,0,.45)', display: 'flex', alignItems: 'center', justifyContent: 'center' } },
|
|
94
|
+
h('div', { style: { width: 'min(640px,92vw)', maxHeight: '86vh', overflow: 'auto', background: 'var(--dsw-alias-bg-layer-1,#fff)', border: '1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3))', borderRadius: '16px', padding: '20px', display: 'flex', flexDirection: 'column', gap: 12, color: 'var(--dsw-alias-label-primary,inherit)', font: 'var(--dsw-font-family,inherit)' } },
|
|
95
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
|
|
96
|
+
h('div', { style: { fontSize: 17, fontWeight: 600 } }, title || ''),
|
|
97
|
+
h('button', { onClick: props.onClose, style: Object.assign({}, btnStyle, { marginLeft: 'auto', width: 28, height: 28, padding: 0, borderRadius: 28 }) }, '✕')),
|
|
98
|
+
hint ? h('div', { style: { fontSize: 12, opacity: .7 } }, hint) : null,
|
|
99
|
+
(props.rows || []).length > 0 ? h('div', { style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 13 } },
|
|
100
|
+
props.rows.map(function (r, i) {
|
|
101
|
+
return r[1] ? h('div', { key: i }, h('b', null, r[0] + ':'), h('span', null, r[1])) : null
|
|
102
|
+
})) : null,
|
|
103
|
+
h('textarea', { value: prompt, onChange: function (e) { setDirty(true); setPrompt(e.target.value) }, spellCheck: false, style: inputStyle }),
|
|
104
|
+
error !== '' ? h('div', { style: { fontSize: 12, color: 'var(--dsw-alias-state-error,#c75050)' } }, error) : null,
|
|
105
|
+
job !== null ? h('div', null,
|
|
106
|
+
h('div', { style: { fontSize: 12, opacity: .7, margin: '4px 0' } }, (labels.outputLabel || 'Output') + ' · ' + statusText),
|
|
107
|
+
h('pre', { style: { maxHeight: 220, margin: 0, overflow: 'auto', whiteSpace: 'pre-wrap', fontSize: 12, background: 'var(--dsw-alias-bg-layer-2,transparent)', border: '1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.2))', borderRadius: '8px', padding: '8px' } }, job.output || '…')) : null,
|
|
108
|
+
h('div', { style: { display: 'flex', gap: 8 } },
|
|
109
|
+
canOpenSession ? h('button', { onClick: openSession, style: btnStyle }, labels.openSession || 'Open chat') : null,
|
|
110
|
+
h('button', { onClick: copy, style: btnStyle }, copied ? (labels.copied || 'Copied') : (labels.copy || 'Copy')),
|
|
111
|
+
h('button', { onClick: doRun, disabled: busy || (job !== null && job.status === 'running'), style: primaryStyle }, job !== null && job.status === 'running' ? (labels.running || 'Running…') : (labels.run || 'Run')))))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { substituteParams: substituteParams, makeActionShareDialog: makeActionShareDialog }
|
|
116
|
+
})()
|
|
117
|
+
|
|
11
118
|
/**
|
|
12
119
|
* dsh-plugin-skills-management - Browser half.
|
|
13
120
|
*
|
|
@@ -467,24 +574,16 @@ window.__ModuleLoader__.load({
|
|
|
467
574
|
// ── Pure helpers ────────────────────────────────────────────────────────
|
|
468
575
|
|
|
469
576
|
/** ntd ActionButton 同款 {{key}} 替换:split/join 规避正则元字符 */
|
|
470
|
-
function substituteParams(template, params) {
|
|
471
|
-
let out = template
|
|
472
|
-
for (const [key, value] of Object.entries(params)) {
|
|
473
|
-
out = out.split(`{{${key}}}`).join(String(value))
|
|
474
|
-
}
|
|
475
|
-
return out
|
|
476
|
-
}
|
|
477
|
-
|
|
478
577
|
const SHARE_PROMPT_ZH = [
|
|
479
578
|
'请把本地技能「{{skillName}}」{{version}}打包提交到 GitCode 官方仓库,作为一个 PR 供维护者审核。',
|
|
480
579
|
'',
|
|
481
580
|
'## 关键信息',
|
|
482
581
|
'- 技能目录:{{resourceDir}}(~ 表示当前用户家目录,执行前先展开为绝对路径)',
|
|
483
582
|
'- 官方仓库:weibaohui/ntd-resource(GitCode,API base = https://api.gitcode.com)',
|
|
484
|
-
'- PAT
|
|
583
|
+
'- PAT 位置:{{settingsFile}} 中 skills-management.market 段的 token 字段(由技能市场设置面板保存)。',
|
|
485
584
|
'',
|
|
486
585
|
'## 执行步骤(严格按顺序)',
|
|
487
|
-
'1. 读取 PAT:读取
|
|
586
|
+
'1. 读取 PAT:读取 {{settingsFile}},定位 skills-management.market 段下的 token 字段。token 是敏感凭据,读取后不要把明文打印到输出、日志或最终结果里。',
|
|
488
587
|
'2. 展开技能目录为绝对路径,遍历该目录,收集每个文件的「相对该目录的路径」与内容;跳过 .downloaded_at、.clawhub、.git 三类同步元数据。',
|
|
489
588
|
'3. 把第 1 步读到的 token 作为 HTTP 认证令牌,附加到下面每个 GitCode API 请求的认证头里(bearer 认证方式),不要写成占位符:',
|
|
490
589
|
' a. 验证用户:`GET https://api.gitcode.com/api/v5/user`,拿到返回的 login 字段——这是 token 真实所属的账号,后续所有 URL 里的 {owner} 一律用它。',
|
|
@@ -871,60 +970,31 @@ window.__ModuleLoader__.load({
|
|
|
871
970
|
return h('div', { className: 'sk-toast' }, text)
|
|
872
971
|
}
|
|
873
972
|
|
|
874
|
-
|
|
875
|
-
function
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
973
|
+
let _actionShareDialog = null
|
|
974
|
+
function getActionShareDialog() {
|
|
975
|
+
if (_actionShareDialog === null) _actionShareDialog = PluginKit.makeActionShareDialog(__React)
|
|
976
|
+
return _actionShareDialog
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/** 分享抽屉:壳交给 PluginKit(ActionShareDialog),本插件只负责
|
|
980
|
+
* hasToken 提示、settingsFile 插值与 run/poll 的 API 映射。 */
|
|
981
|
+
function ShareSkillDialog({ t, params, onClose }) {
|
|
982
|
+
const _st = useState(null)
|
|
983
|
+
const status = _st[0]; const setStatus = _st[1]
|
|
880
984
|
useEffect(() => {
|
|
881
|
-
getJson(API + '/market/status').then(d =>
|
|
985
|
+
getJson(API + '/market/status').then((d) => setStatus(d)).catch(() => setStatus({ hasToken: false }))
|
|
882
986
|
}, [])
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
},
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
894
|
-
const doRun = async () => {
|
|
895
|
-
setBusy(true)
|
|
896
|
-
try {
|
|
897
|
-
const r = await fetch(API + '/share/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt, dir: params.resourceDir }) })
|
|
898
|
-
const d = await r.json().catch(() => ({}))
|
|
899
|
-
if (!r.ok) throw new Error(d.error || 'HTTP ' + r.status)
|
|
900
|
-
setJob({ jobId: d.jobId, status: 'running', output: '', code: null })
|
|
901
|
-
} catch (e) { onToast(t('runFailed') + ': ' + e.message) } finally { setBusy(false) }
|
|
902
|
-
}
|
|
903
|
-
const row = (label, value) => h('div', { style: { display: 'flex', justifyContent: 'space-between', gap: 12, padding: '3px 0' } },
|
|
904
|
-
h('span', { className: 'sk-dir' }, label), h('span', { className: 'sk-hint', style: { wordBreak: 'break-all', textAlign: 'right' } }, value))
|
|
905
|
-
const copy = () => {
|
|
906
|
-
navigator.clipboard.writeText(prompt).then(() => onToast(t('promptCopied'))).catch(() => {})
|
|
907
|
-
}
|
|
908
|
-
return h(SkDialog, { title: t('shareTitle'), onClose, wide: true },
|
|
909
|
-
h('div', { style: { display: 'flex', flexDirection: 'column', gap: 10, minWidth: 380 } },
|
|
910
|
-
h('div', { className: 'sk-hint' }, hasToken === false ? t('shareHintPatMissing') : t('shareHint')),
|
|
911
|
-
h('div', null,
|
|
912
|
-
row(t('shareParamName'), params.skillName),
|
|
913
|
-
row(t('shareParamVersion'), params.version || '-'),
|
|
914
|
-
row(t('shareParamDir'), params.resourceDir),
|
|
915
|
-
row(t('shareParamRemote'), params.remotePath)),
|
|
916
|
-
h('textarea', { className: 'sk-input', value: prompt, onChange: e => setPrompt(e.target.value),
|
|
917
|
-
style: { width: '100%', minHeight: 190, resize: 'vertical', fontFamily: 'var(--dsw-font-family)', lineHeight: 1.6 } }),
|
|
918
|
-
job !== null && h('div', null,
|
|
919
|
-
h('div', { className: 'sk-dir', style: { margin: '4px 0' } },
|
|
920
|
-
t('outputLabel') + ' · ' + (job.status === 'running' ? t('running') : job.status === 'done' ? t('runDone') : t('runFailed') + (job.code != null ? ' (' + job.code + ')' : ''))),
|
|
921
|
-
h('pre', { className: 'sk-preview', style: { maxHeight: 220, margin: 0, whiteSpace: 'pre-wrap', fontSize: 12 } },
|
|
922
|
-
job.output || '…')),
|
|
923
|
-
h('div', { className: 'sk-dlg-foot', style: { marginTop: 0 } },
|
|
924
|
-
h(ButtonLite, { onClick: copy }, t('copyPrompt')),
|
|
925
|
-
job !== null && job.sessionId && sessionsSvc() && h(ButtonLite, { onClick: () => { if (openRunSession(job.sessionId)) onClose() } }, t('openChat')),
|
|
926
|
-
h(ButtonLite, { primary: true, disabled: busy || (job !== null && job.status === 'running'), onClick: doRun },
|
|
927
|
-
job !== null && job.status === 'running' ? t('running') : t('runBtn')))))
|
|
987
|
+
const hasToken = status ? status.hasToken === true : null
|
|
988
|
+
const hint = hasToken === false ? t('shareHintPatMissing') : t('shareHint')
|
|
989
|
+
const initialPrompt = PluginKit.substituteParams(SHARE_PROMPT_ZH, { ...params, settingsFile: (status && status.settingsFile) || '' })
|
|
990
|
+
return h(getActionShareDialog(), {
|
|
991
|
+
title: t('shareTitle'), hint, initialPrompt,
|
|
992
|
+
rows: [[t('shareParamName'), params.skillName], [t('shareParamVersion'), params.version || '-'], [t('shareParamDir'), params.resourceDir]],
|
|
993
|
+
labels: { copy: t('copyPrompt'), copied: t('copied'), run: t('runBtn'), running: t('running'), done: t('runDone'), failed: t('runFailed'), outputLabel: t('outputLabel') },
|
|
994
|
+
run: (prompt) => fetch(API + '/share/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt, dir: params.resourceDir }) }).then((r) => r.json()),
|
|
995
|
+
poll: (id) => fetch(API + '/share/run?id=' + encodeURIComponent(id)).then((r) => r.json()),
|
|
996
|
+
onClose,
|
|
997
|
+
})
|
|
928
998
|
}
|
|
929
999
|
|
|
930
1000
|
/** Market sync settings: status card, sync action, editable url/branch. */
|
package/client/index.js
CHANGED
|
@@ -457,24 +457,16 @@ const EN = {
|
|
|
457
457
|
// ── Pure helpers ────────────────────────────────────────────────────────
|
|
458
458
|
|
|
459
459
|
/** ntd ActionButton 同款 {{key}} 替换:split/join 规避正则元字符 */
|
|
460
|
-
function substituteParams(template, params) {
|
|
461
|
-
let out = template
|
|
462
|
-
for (const [key, value] of Object.entries(params)) {
|
|
463
|
-
out = out.split(`{{${key}}}`).join(String(value))
|
|
464
|
-
}
|
|
465
|
-
return out
|
|
466
|
-
}
|
|
467
|
-
|
|
468
460
|
const SHARE_PROMPT_ZH = [
|
|
469
461
|
'请把本地技能「{{skillName}}」{{version}}打包提交到 GitCode 官方仓库,作为一个 PR 供维护者审核。',
|
|
470
462
|
'',
|
|
471
463
|
'## 关键信息',
|
|
472
464
|
'- 技能目录:{{resourceDir}}(~ 表示当前用户家目录,执行前先展开为绝对路径)',
|
|
473
465
|
'- 官方仓库:weibaohui/ntd-resource(GitCode,API base = https://api.gitcode.com)',
|
|
474
|
-
'- PAT
|
|
466
|
+
'- PAT 位置:{{settingsFile}} 中 skills-management.market 段的 token 字段(由技能市场设置面板保存)。',
|
|
475
467
|
'',
|
|
476
468
|
'## 执行步骤(严格按顺序)',
|
|
477
|
-
'1. 读取 PAT:读取
|
|
469
|
+
'1. 读取 PAT:读取 {{settingsFile}},定位 skills-management.market 段下的 token 字段。token 是敏感凭据,读取后不要把明文打印到输出、日志或最终结果里。',
|
|
478
470
|
'2. 展开技能目录为绝对路径,遍历该目录,收集每个文件的「相对该目录的路径」与内容;跳过 .downloaded_at、.clawhub、.git 三类同步元数据。',
|
|
479
471
|
'3. 把第 1 步读到的 token 作为 HTTP 认证令牌,附加到下面每个 GitCode API 请求的认证头里(bearer 认证方式),不要写成占位符:',
|
|
480
472
|
' a. 验证用户:`GET https://api.gitcode.com/api/v5/user`,拿到返回的 login 字段——这是 token 真实所属的账号,后续所有 URL 里的 {owner} 一律用它。',
|
|
@@ -861,60 +853,31 @@ function InToast({ text }) {
|
|
|
861
853
|
return h('div', { className: 'sk-toast' }, text)
|
|
862
854
|
}
|
|
863
855
|
|
|
864
|
-
|
|
865
|
-
function
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
856
|
+
let _actionShareDialog = null
|
|
857
|
+
function getActionShareDialog() {
|
|
858
|
+
if (_actionShareDialog === null) _actionShareDialog = PluginKit.makeActionShareDialog(__React)
|
|
859
|
+
return _actionShareDialog
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/** 分享抽屉:壳交给 PluginKit(ActionShareDialog),本插件只负责
|
|
863
|
+
* hasToken 提示、settingsFile 插值与 run/poll 的 API 映射。 */
|
|
864
|
+
function ShareSkillDialog({ t, params, onClose }) {
|
|
865
|
+
const _st = useState(null)
|
|
866
|
+
const status = _st[0]; const setStatus = _st[1]
|
|
870
867
|
useEffect(() => {
|
|
871
|
-
getJson(API + '/market/status').then(d =>
|
|
868
|
+
getJson(API + '/market/status').then((d) => setStatus(d)).catch(() => setStatus({ hasToken: false }))
|
|
872
869
|
}, [])
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
},
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
884
|
-
const doRun = async () => {
|
|
885
|
-
setBusy(true)
|
|
886
|
-
try {
|
|
887
|
-
const r = await fetch(API + '/share/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt, dir: params.resourceDir }) })
|
|
888
|
-
const d = await r.json().catch(() => ({}))
|
|
889
|
-
if (!r.ok) throw new Error(d.error || 'HTTP ' + r.status)
|
|
890
|
-
setJob({ jobId: d.jobId, status: 'running', output: '', code: null })
|
|
891
|
-
} catch (e) { onToast(t('runFailed') + ': ' + e.message) } finally { setBusy(false) }
|
|
892
|
-
}
|
|
893
|
-
const row = (label, value) => h('div', { style: { display: 'flex', justifyContent: 'space-between', gap: 12, padding: '3px 0' } },
|
|
894
|
-
h('span', { className: 'sk-dir' }, label), h('span', { className: 'sk-hint', style: { wordBreak: 'break-all', textAlign: 'right' } }, value))
|
|
895
|
-
const copy = () => {
|
|
896
|
-
navigator.clipboard.writeText(prompt).then(() => onToast(t('promptCopied'))).catch(() => {})
|
|
897
|
-
}
|
|
898
|
-
return h(SkDialog, { title: t('shareTitle'), onClose, wide: true },
|
|
899
|
-
h('div', { style: { display: 'flex', flexDirection: 'column', gap: 10, minWidth: 380 } },
|
|
900
|
-
h('div', { className: 'sk-hint' }, hasToken === false ? t('shareHintPatMissing') : t('shareHint')),
|
|
901
|
-
h('div', null,
|
|
902
|
-
row(t('shareParamName'), params.skillName),
|
|
903
|
-
row(t('shareParamVersion'), params.version || '-'),
|
|
904
|
-
row(t('shareParamDir'), params.resourceDir),
|
|
905
|
-
row(t('shareParamRemote'), params.remotePath)),
|
|
906
|
-
h('textarea', { className: 'sk-input', value: prompt, onChange: e => setPrompt(e.target.value),
|
|
907
|
-
style: { width: '100%', minHeight: 190, resize: 'vertical', fontFamily: 'var(--dsw-font-family)', lineHeight: 1.6 } }),
|
|
908
|
-
job !== null && h('div', null,
|
|
909
|
-
h('div', { className: 'sk-dir', style: { margin: '4px 0' } },
|
|
910
|
-
t('outputLabel') + ' · ' + (job.status === 'running' ? t('running') : job.status === 'done' ? t('runDone') : t('runFailed') + (job.code != null ? ' (' + job.code + ')' : ''))),
|
|
911
|
-
h('pre', { className: 'sk-preview', style: { maxHeight: 220, margin: 0, whiteSpace: 'pre-wrap', fontSize: 12 } },
|
|
912
|
-
job.output || '…')),
|
|
913
|
-
h('div', { className: 'sk-dlg-foot', style: { marginTop: 0 } },
|
|
914
|
-
h(ButtonLite, { onClick: copy }, t('copyPrompt')),
|
|
915
|
-
job !== null && job.sessionId && sessionsSvc() && h(ButtonLite, { onClick: () => { if (openRunSession(job.sessionId)) onClose() } }, t('openChat')),
|
|
916
|
-
h(ButtonLite, { primary: true, disabled: busy || (job !== null && job.status === 'running'), onClick: doRun },
|
|
917
|
-
job !== null && job.status === 'running' ? t('running') : t('runBtn')))))
|
|
870
|
+
const hasToken = status ? status.hasToken === true : null
|
|
871
|
+
const hint = hasToken === false ? t('shareHintPatMissing') : t('shareHint')
|
|
872
|
+
const initialPrompt = PluginKit.substituteParams(SHARE_PROMPT_ZH, { ...params, settingsFile: (status && status.settingsFile) || '' })
|
|
873
|
+
return h(getActionShareDialog(), {
|
|
874
|
+
title: t('shareTitle'), hint, initialPrompt,
|
|
875
|
+
rows: [[t('shareParamName'), params.skillName], [t('shareParamVersion'), params.version || '-'], [t('shareParamDir'), params.resourceDir]],
|
|
876
|
+
labels: { copy: t('copyPrompt'), copied: t('copied'), run: t('runBtn'), running: t('running'), done: t('runDone'), failed: t('runFailed'), outputLabel: t('outputLabel') },
|
|
877
|
+
run: (prompt) => fetch(API + '/share/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt, dir: params.resourceDir }) }).then((r) => r.json()),
|
|
878
|
+
poll: (id) => fetch(API + '/share/run?id=' + encodeURIComponent(id)).then((r) => r.json()),
|
|
879
|
+
onClose,
|
|
880
|
+
})
|
|
918
881
|
}
|
|
919
882
|
|
|
920
883
|
/** Market sync settings: status card, sync action, editable url/branch. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weibaohui/skills-management",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "dsh 插件 · 技能市场:一个页面管理本机所有 coding agent 的技能(Claude、ZCode、Codex 等十余个执行器),一键收编进 DSH 用户库供 skill 工具调用;内置 6600+ ntd 技能市场可浏览搜索安装,支持模型可见性治理与市场每日自动同步。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"node": ">=22.5"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"@weibaohui/dsh-plugin-kit": "^0.2.0",
|
|
46
47
|
"yaml": "^2.9.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
package/src/index.js
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const { createReadStream } = require('node:fs')
|
|
17
|
-
const { execFile
|
|
17
|
+
const { execFile } = require('node:child_process')
|
|
18
|
+
const { createShareRunJob } = require('@weibaohui/dsh-plugin-kit')
|
|
18
19
|
const { randomUUID } = require('node:crypto')
|
|
19
20
|
const fsP = require('node:fs/promises')
|
|
20
21
|
const { basename, join, relative, resolve, sep } = require('node:path')
|
|
@@ -477,99 +478,6 @@ function mergeMarketSync(config, overrides) {
|
|
|
477
478
|
// (`dsh --profile headless "<task>"`, cwd = the skill directory — the
|
|
478
479
|
// workspace, session and model loop are owned by that one-shot process). ──
|
|
479
480
|
|
|
480
|
-
const SHARE_RUN_TIMEOUT_MS = 30 * 60 * 1000
|
|
481
|
-
const SHARE_RUN_OUTPUT_CAP = 256 * 1024
|
|
482
|
-
|
|
483
|
-
/** In-process run: drive the same Agent services the web app uses and
|
|
484
|
-
* stream assistant/chunk tokens + tool calls into the job's output as they
|
|
485
|
-
* happen (headless prints only the final message — no live channel there).
|
|
486
|
-
* Mirrors packages/bundle/headless/src/index.ts run(). */
|
|
487
|
-
async function runShareInProcess(services, { prompt, dir, job, logger }) {
|
|
488
|
-
const selection = services.agentDefaultModel.currentSelection()
|
|
489
|
-
const sessionId = 'session-' + randomUUID()
|
|
490
|
-
job.sessionId = sessionId
|
|
491
|
-
const { agent } = await services.agents.create({
|
|
492
|
-
sessionId,
|
|
493
|
-
// 标准预设:不带显式选择会继承用户默认(如 Solo Thinking 只有
|
|
494
|
-
// thinking/notify 工具),读文件/调 API 都做不了
|
|
495
|
-
meta: { cwd: dir, agentPreset: 'standard' },
|
|
496
|
-
agentOptions: { provider: selection.provider, model: selection.model },
|
|
497
|
-
})
|
|
498
|
-
await agent.whenIdle()
|
|
499
|
-
const firstSeq = agent.session.seq
|
|
500
|
-
const seen = new Set()
|
|
501
|
-
const liveLine = (text) => {
|
|
502
|
-
job.output = (job.output + text).slice(-SHARE_RUN_OUTPUT_CAP)
|
|
503
|
-
}
|
|
504
|
-
const pump = () => {
|
|
505
|
-
for (const ev of agent.session.events) {
|
|
506
|
-
if (ev.seq < firstSeq || seen.has(ev.seq)) continue
|
|
507
|
-
seen.add(ev.seq)
|
|
508
|
-
const d = ev.data || {}
|
|
509
|
-
if (ev.type === 'assistant/chunk' && d.chunk && d.chunk.type === 'text' && d.chunk.text) {
|
|
510
|
-
liveLine(d.chunk.text)
|
|
511
|
-
} else if (ev.type === 'tool/call') {
|
|
512
|
-
liveLine('\n[tool] ' + d.name + ' ')
|
|
513
|
-
} else if (ev.type === 'assistant/message') {
|
|
514
|
-
liveLine('\n')
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
const timer = setInterval(pump, 300)
|
|
519
|
-
if (typeof timer.unref === 'function') timer.unref()
|
|
520
|
-
try {
|
|
521
|
-
agent.followup({ content: [{ type: 'text', text: prompt }], source: { kind: 'user' } })
|
|
522
|
-
await agent.whenIdle()
|
|
523
|
-
} finally {
|
|
524
|
-
clearInterval(timer)
|
|
525
|
-
pump()
|
|
526
|
-
}
|
|
527
|
-
try { await services.sessions.flush(agent.session) } catch {}
|
|
528
|
-
job.status = 'done'
|
|
529
|
-
job.code = 0
|
|
530
|
-
return job
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function createShareRunJob({ binary, prompt, dir, jobs, logger, services }) {
|
|
534
|
-
const id = 'sr' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8)
|
|
535
|
-
const job = { id, status: 'running', startedAt: new Date().toISOString(), dir, promptHead: prompt.slice(0, 80), output: '', code: null }
|
|
536
|
-
jobs.set(id, job)
|
|
537
|
-
if (services && services.agents && services.agentDefaultModel) {
|
|
538
|
-
runShareInProcess(services, { prompt, dir, job, logger })
|
|
539
|
-
.catch(e => { job.status = 'error'; job.output = (job.output + '\n' + String(e && e.message)).slice(-SHARE_RUN_OUTPUT_CAP) })
|
|
540
|
-
return job
|
|
541
|
-
}
|
|
542
|
-
let child
|
|
543
|
-
try {
|
|
544
|
-
child = spawn(binary, ['--profile', 'headless', prompt], { cwd: dir })
|
|
545
|
-
} catch (e) {
|
|
546
|
-
job.status = 'error'
|
|
547
|
-
job.output = String(e && e.message)
|
|
548
|
-
return job
|
|
549
|
-
}
|
|
550
|
-
const append = (chunk) => {
|
|
551
|
-
job.output = (job.output + String(chunk)).slice(-SHARE_RUN_OUTPUT_CAP)
|
|
552
|
-
}
|
|
553
|
-
child.stdout && child.stdout.on('data', append)
|
|
554
|
-
child.stderr && child.stderr.on('data', append)
|
|
555
|
-
const timer = setTimeout(() => {
|
|
556
|
-
try { child.kill('SIGKILL') } catch {}
|
|
557
|
-
job.status = 'error'
|
|
558
|
-
job.output += '\n[killed: timeout]'
|
|
559
|
-
}, SHARE_RUN_TIMEOUT_MS)
|
|
560
|
-
if (typeof timer.unref === 'function') timer.unref()
|
|
561
|
-
child.on('error', (e) => { clearTimeout(timer); job.status = 'error'; append('\n' + String(e && e.message)) })
|
|
562
|
-
child.on('close', (code) => {
|
|
563
|
-
clearTimeout(timer)
|
|
564
|
-
if (job.status === 'running') {
|
|
565
|
-
job.status = code === 0 ? 'done' : 'error'
|
|
566
|
-
job.code = code
|
|
567
|
-
}
|
|
568
|
-
logger.info && logger.info(`skills-management: share run ${id} ${job.status} (code ${code})`)
|
|
569
|
-
})
|
|
570
|
-
return job
|
|
571
|
-
}
|
|
572
|
-
|
|
573
481
|
function contentTypeFor(p) {
|
|
574
482
|
const ext = p.slice(p.lastIndexOf('.') + 1).toLowerCase()
|
|
575
483
|
const map = { md: 'text/markdown; charset=utf-8', txt: 'text/plain; charset=utf-8', json: 'application/json; charset=utf-8', js: 'text/javascript', mjs: 'text/javascript', ts: 'text/typescript', tsx: 'text/typescript', css: 'text/css', html: 'text/html', svg: 'image/svg+xml', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp', yaml: 'text/yaml', yml: 'text/yaml' }
|
|
@@ -578,7 +486,7 @@ function contentTypeFor(p) {
|
|
|
578
486
|
|
|
579
487
|
module.exports = {
|
|
580
488
|
name: 'skills-management',
|
|
581
|
-
inject: ['skills', 'webServer', 'settings'],
|
|
489
|
+
inject: ['skills', 'webServer', 'settings', 'agents', 'agentDefaultModel', 'sessions'],
|
|
582
490
|
__internals: { extractFrontmatter, parseSkillMd, invocationPolicy, installDirName, EXECUTOR_DEFS },
|
|
583
491
|
|
|
584
492
|
apply(ctx, config = {}) {
|
|
@@ -851,14 +759,9 @@ module.exports = {
|
|
|
851
759
|
}, 'skills-management: market auto-sync')
|
|
852
760
|
|
|
853
761
|
const shareRunJobs = new Map()
|
|
854
|
-
// Same-process Agent services
|
|
855
|
-
//
|
|
856
|
-
|
|
857
|
-
try {
|
|
858
|
-
if (ctx.inject && typeof ctx.inject === 'function') {
|
|
859
|
-
ctx.inject(['agents', 'agentDefaultModel', 'sessions'], (svcs) => { shareServices = svcs })
|
|
860
|
-
}
|
|
861
|
-
} catch {}
|
|
762
|
+
// Same-process Agent services(静态注入:apply 时已就绪;动态 ctx.inject 在
|
|
763
|
+
// apply 内不触发是平台 gotcha)。
|
|
764
|
+
const shareServices = { agents: ctx.agents, agentDefaultModel: ctx.agentDefaultModel, sessions: ctx.sessions }
|
|
862
765
|
let providerControl
|
|
863
766
|
const invalidate = () => { if (providerControl !== undefined) providerControl.invalidate() }
|
|
864
767
|
|
|
@@ -949,6 +852,7 @@ module.exports = {
|
|
|
949
852
|
hasToken: typeof eff.token === 'string' && eff.token !== '',
|
|
950
853
|
syncing: marketSyncRun !== null,
|
|
951
854
|
sparsePaths: marketSparsePaths() ?? null,
|
|
855
|
+
settingsFile: join(dshHome(), 'settings.yaml'),
|
|
952
856
|
})
|
|
953
857
|
return
|
|
954
858
|
}
|
|
@@ -988,7 +892,7 @@ module.exports = {
|
|
|
988
892
|
}
|
|
989
893
|
const eff = marketSettings()
|
|
990
894
|
const { token, ...safe } = eff // token 只写不回读
|
|
991
|
-
sendJson(res, 200, { settings: safe, hasToken: typeof token === 'string' && token !== '' })
|
|
895
|
+
sendJson(res, 200, { settings: safe, hasToken: typeof token === 'string' && token !== '', settingsFile: join(dshHome(), 'settings.yaml') })
|
|
992
896
|
return
|
|
993
897
|
}
|
|
994
898
|
|