@weibaohui/skills-management 0.1.4 → 0.1.6
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 +4 -0
- package/client/bundle.js +76 -1
- package/client/index.js +76 -1
- package/docs/demo.gif +0 -0
- package/package.json +2 -1
- package/src/index.js +34 -6
package/README.md
CHANGED
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
|
|
6
6
|
**技能市场插件**:一个页面管理本机所有 coding agent 的技能,还能浏览安装 6600+ 的 ntd 技能市场。
|
|
7
7
|
|
|
8
|
+

|
|
9
|
+
|
|
8
10
|
## 核心功能
|
|
9
11
|
|
|
10
12
|
- **全来源扫描**:自动扫描本机各 coding agent 的技能目录(Claude、ZCode、Codex 等十余个执行器),统一在一个页面查看、查看详情、单文件预览
|
|
11
13
|
- **一键收编**:任意执行器的技能可一键复制进 DSH 用户库,供模型的 `skill` 工具直接调用
|
|
12
14
|
- **技能市场**:内置 ntd 技能合集(6600+ 条),按来源分组浏览、搜索筛选、详情预览、一键安装
|
|
13
15
|
- **模型可见性治理**:每个已装技能都有「模型可调用」开关,不想暴露给模型的技能一键隐藏/恢复
|
|
16
|
+
- **输入框 +技能**:composer 工具行新增「+技能」按钮,弹出技能候选菜单,选中即把 `/技能名` 写入草稿,发送时技能内容注入该条消息
|
|
14
17
|
- **市场自动同步**:市场仓库自动克隆与每日更新(可关),支持 GitCode 私有仓库 access token
|
|
18
|
+
- **稀疏检出**:ntd-resource 仓库同时携带专家/模板等子树,市场只检出 `skills` 子目录(git partial clone + sparse-checkout),省一半以上流量与磁盘;已有全量检出会在下次同步时原地转换
|
|
15
19
|
- **软链布局兼容**:各执行器目录间软链共享的技能不会重复展示
|
|
16
20
|
|
|
17
21
|
## 安装
|
package/client/bundle.js
CHANGED
|
@@ -74,6 +74,37 @@ window.__ModuleLoader__.load({
|
|
|
74
74
|
let sessionsApi = null
|
|
75
75
|
const sessionsSvc = () => sessionsApi
|
|
76
76
|
|
|
77
|
+
// Composer services (inputTriggers + sessions) for the +技能 button: opens
|
|
78
|
+
// the `skill` source (registered by dsh-client-ui-skill) as a menu. Absence
|
|
79
|
+
// hides the button; nothing else depends on it.
|
|
80
|
+
let composerScope = null
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Open one registered '/' source over a synthetic collapsed span appended at
|
|
84
|
+
* the draft end (host toggleCommandMenu 同款调用形状;标准 kit 不暴露光标,
|
|
85
|
+
* pick 依赖 span-CAS:点击后草稿若再变动则本次 pick 静默作废)。
|
|
86
|
+
*/
|
|
87
|
+
function openTriggerSource(scope, sessionId, input, sourceName) {
|
|
88
|
+
const inputTriggers = scope && scope.inputTriggers
|
|
89
|
+
const sessions = scope && scope.sessions
|
|
90
|
+
if (!inputTriggers || !sessions) return false
|
|
91
|
+
let actx
|
|
92
|
+
try { actx = sessions.scope(sessionId) } catch { return false }
|
|
93
|
+
if (actx === undefined || actx === null) return false
|
|
94
|
+
let controller
|
|
95
|
+
try { controller = inputTriggers.sessionOf(actx) } catch { return false }
|
|
96
|
+
const draft = (input && input.draft) || ''
|
|
97
|
+
const at = draft.length
|
|
98
|
+
controller.toggleSource(sourceName, {
|
|
99
|
+
trigger: '/',
|
|
100
|
+
query: '',
|
|
101
|
+
quoted: false,
|
|
102
|
+
position: draft.trim() === '' ? 'leading' : 'inline',
|
|
103
|
+
span: { start: at, end: at, draftRev: (input && input.draftRev) || 0 },
|
|
104
|
+
})
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
77
108
|
// ── Locale ───────────────────────────────────────────────────────────────
|
|
78
109
|
|
|
79
110
|
const NS = 'skillsManagement'
|
|
@@ -183,6 +214,8 @@ window.__ModuleLoader__.load({
|
|
|
183
214
|
invocationHint: '关闭后技能保留在库里,但不再注入对话目录(skill 工具也调不到)',
|
|
184
215
|
pathLabel: '路径',
|
|
185
216
|
meTag: '本机',
|
|
217
|
+
pickSkill: '+技能',
|
|
218
|
+
pickSkillTitle: '选择一个技能,其内容将注入本条消息',
|
|
186
219
|
}
|
|
187
220
|
|
|
188
221
|
const EN = {
|
|
@@ -290,6 +323,8 @@ window.__ModuleLoader__.load({
|
|
|
290
323
|
invocationHint: 'When off, the skill stays in the library but is not injected into conversation catalogs',
|
|
291
324
|
pathLabel: 'Path',
|
|
292
325
|
meTag: 'me',
|
|
326
|
+
pickSkill: '+ Skill',
|
|
327
|
+
pickSkillTitle: 'Pick a skill; its content is injected into this message',
|
|
293
328
|
}
|
|
294
329
|
|
|
295
330
|
// ── Pure helpers ────────────────────────────────────────────────────────
|
|
@@ -432,6 +467,8 @@ window.__ModuleLoader__.load({
|
|
|
432
467
|
.sk-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
433
468
|
.sk-menu-item.on{color:var(--dsw-alias-state-business-primary);font-weight:500}
|
|
434
469
|
.sk-tabpill{background:transparent;border:none;color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-family)}
|
|
470
|
+
.sk-chip{display:inline-flex;align-items:center;gap:4px;height:26px;padding:0 9px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-family);font-size:12px;cursor:pointer;white-space:nowrap}
|
|
471
|
+
.sk-chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
435
472
|
</style>`
|
|
436
473
|
|
|
437
474
|
// ── Fetch layer ─────────────────────────────────────────────────────────
|
|
@@ -1290,7 +1327,7 @@ window.__ModuleLoader__.load({
|
|
|
1290
1327
|
module.exports = {
|
|
1291
1328
|
name: CLIENT_NAME,
|
|
1292
1329
|
inject: ['slots', 'locale'],
|
|
1293
|
-
__internals: { NS, ZH, EN, matchSkill, formatSize, formatTime },
|
|
1330
|
+
__internals: { NS, ZH, EN, matchSkill, formatSize, formatTime, openTriggerSource },
|
|
1294
1331
|
/** Test/host helper: mount a standalone page into any container. */
|
|
1295
1332
|
__boot(container, opts = {}) {
|
|
1296
1333
|
ensureStyles()
|
|
@@ -1318,6 +1355,13 @@ window.__ModuleLoader__.load({
|
|
|
1318
1355
|
})
|
|
1319
1356
|
}
|
|
1320
1357
|
} catch {}
|
|
1358
|
+
// Composer services (inputTriggers + sessions) for the +技能 button;
|
|
1359
|
+
// absence hides the button only.
|
|
1360
|
+
try {
|
|
1361
|
+
if (typeof ctx.inject === 'function') {
|
|
1362
|
+
ctx.inject(['inputTriggers', 'sessions'], (scope) => { composerScope = scope })
|
|
1363
|
+
}
|
|
1364
|
+
} catch {}
|
|
1321
1365
|
// Locale service is optional at boot order — degrade to EN until present
|
|
1322
1366
|
let t = (key, vars) => {
|
|
1323
1367
|
let out = EN[key] ?? key
|
|
@@ -1369,9 +1413,40 @@ window.__ModuleLoader__.load({
|
|
|
1369
1413
|
}))
|
|
1370
1414
|
} catch (e) { (globalThis.__skErrors = globalThis.__skErrors || []).push('settings:' + (e && e.message)); throw e }
|
|
1371
1415
|
}, 'skills-management: settings section')
|
|
1416
|
+
ctx.effect(() => {
|
|
1417
|
+
try {
|
|
1418
|
+
ctx.slots.inject('conversation.input.left', () => ctx.slots.register({
|
|
1419
|
+
name: 'conversation.input.left',
|
|
1420
|
+
id: CLIENT_NAME,
|
|
1421
|
+
order: 60,
|
|
1422
|
+
locale: NS,
|
|
1423
|
+
label: () => t('pickSkill'),
|
|
1424
|
+
inject: () => ({ t }),
|
|
1425
|
+
}, function SkillButtonSlot(apiProps) {
|
|
1426
|
+
return h(ComposerButtonSlot, {
|
|
1427
|
+
__t: t, icon: '⚡', label: t('pickSkill'), title: t('pickSkillTitle'),
|
|
1428
|
+
source: 'skill', sessionId: apiProps && apiProps.sessionId, input: apiProps && apiProps.input,
|
|
1429
|
+
})
|
|
1430
|
+
}))
|
|
1431
|
+
} catch (e) { (globalThis.__skErrors = globalThis.__skErrors || []).push('input.left:' + (e && e.message)); throw e }
|
|
1432
|
+
}, 'skills-management: input left button')
|
|
1372
1433
|
},
|
|
1373
1434
|
}
|
|
1374
1435
|
|
|
1436
|
+
/** Composer tool-row button: opens a registered '/' source (ui-skill's
|
|
1437
|
+
* `skill` source) over the session's trigger controller. Hidden while the
|
|
1438
|
+
* inputTriggers/sessions services are absent. */
|
|
1439
|
+
function ComposerButtonSlot(props) {
|
|
1440
|
+
useEffect(ensureStyles, [])
|
|
1441
|
+
const ready = !!(composerScope && composerScope.inputTriggers && composerScope.sessions && props.sessionId)
|
|
1442
|
+
if (!ready) return null
|
|
1443
|
+
return h('button', {
|
|
1444
|
+
className: 'sk-chip',
|
|
1445
|
+
title: props.title || props.label,
|
|
1446
|
+
onClick: () => { openTriggerSource(composerScope, props.sessionId, props.input, props.source) },
|
|
1447
|
+
}, `${props.icon || ''}${props.icon ? ' ' : ''}${props.label}`)
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1375
1450
|
return module.exports
|
|
1376
1451
|
}
|
|
1377
1452
|
})
|
package/client/index.js
CHANGED
|
@@ -64,6 +64,37 @@ const prim = (name) => P && P[name]
|
|
|
64
64
|
let sessionsApi = null
|
|
65
65
|
const sessionsSvc = () => sessionsApi
|
|
66
66
|
|
|
67
|
+
// Composer services (inputTriggers + sessions) for the +技能 button: opens
|
|
68
|
+
// the `skill` source (registered by dsh-client-ui-skill) as a menu. Absence
|
|
69
|
+
// hides the button; nothing else depends on it.
|
|
70
|
+
let composerScope = null
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Open one registered '/' source over a synthetic collapsed span appended at
|
|
74
|
+
* the draft end (host toggleCommandMenu 同款调用形状;标准 kit 不暴露光标,
|
|
75
|
+
* pick 依赖 span-CAS:点击后草稿若再变动则本次 pick 静默作废)。
|
|
76
|
+
*/
|
|
77
|
+
function openTriggerSource(scope, sessionId, input, sourceName) {
|
|
78
|
+
const inputTriggers = scope && scope.inputTriggers
|
|
79
|
+
const sessions = scope && scope.sessions
|
|
80
|
+
if (!inputTriggers || !sessions) return false
|
|
81
|
+
let actx
|
|
82
|
+
try { actx = sessions.scope(sessionId) } catch { return false }
|
|
83
|
+
if (actx === undefined || actx === null) return false
|
|
84
|
+
let controller
|
|
85
|
+
try { controller = inputTriggers.sessionOf(actx) } catch { return false }
|
|
86
|
+
const draft = (input && input.draft) || ''
|
|
87
|
+
const at = draft.length
|
|
88
|
+
controller.toggleSource(sourceName, {
|
|
89
|
+
trigger: '/',
|
|
90
|
+
query: '',
|
|
91
|
+
quoted: false,
|
|
92
|
+
position: draft.trim() === '' ? 'leading' : 'inline',
|
|
93
|
+
span: { start: at, end: at, draftRev: (input && input.draftRev) || 0 },
|
|
94
|
+
})
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
|
|
67
98
|
// ── Locale ───────────────────────────────────────────────────────────────
|
|
68
99
|
|
|
69
100
|
const NS = 'skillsManagement'
|
|
@@ -173,6 +204,8 @@ const ZH = {
|
|
|
173
204
|
invocationHint: '关闭后技能保留在库里,但不再注入对话目录(skill 工具也调不到)',
|
|
174
205
|
pathLabel: '路径',
|
|
175
206
|
meTag: '本机',
|
|
207
|
+
pickSkill: '+技能',
|
|
208
|
+
pickSkillTitle: '选择一个技能,其内容将注入本条消息',
|
|
176
209
|
}
|
|
177
210
|
|
|
178
211
|
const EN = {
|
|
@@ -280,6 +313,8 @@ const EN = {
|
|
|
280
313
|
invocationHint: 'When off, the skill stays in the library but is not injected into conversation catalogs',
|
|
281
314
|
pathLabel: 'Path',
|
|
282
315
|
meTag: 'me',
|
|
316
|
+
pickSkill: '+ Skill',
|
|
317
|
+
pickSkillTitle: 'Pick a skill; its content is injected into this message',
|
|
283
318
|
}
|
|
284
319
|
|
|
285
320
|
// ── Pure helpers ────────────────────────────────────────────────────────
|
|
@@ -422,6 +457,8 @@ const STYLE = `<style>
|
|
|
422
457
|
.sk-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
423
458
|
.sk-menu-item.on{color:var(--dsw-alias-state-business-primary);font-weight:500}
|
|
424
459
|
.sk-tabpill{background:transparent;border:none;color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-family)}
|
|
460
|
+
.sk-chip{display:inline-flex;align-items:center;gap:4px;height:26px;padding:0 9px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-family);font-size:12px;cursor:pointer;white-space:nowrap}
|
|
461
|
+
.sk-chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
425
462
|
</style>`
|
|
426
463
|
|
|
427
464
|
// ── Fetch layer ─────────────────────────────────────────────────────────
|
|
@@ -1280,7 +1317,7 @@ const CLIENT_NAME = '@weibaohui/skills-management'
|
|
|
1280
1317
|
module.exports = {
|
|
1281
1318
|
name: CLIENT_NAME,
|
|
1282
1319
|
inject: ['slots', 'locale'],
|
|
1283
|
-
__internals: { NS, ZH, EN, matchSkill, formatSize, formatTime },
|
|
1320
|
+
__internals: { NS, ZH, EN, matchSkill, formatSize, formatTime, openTriggerSource },
|
|
1284
1321
|
/** Test/host helper: mount a standalone page into any container. */
|
|
1285
1322
|
__boot(container, opts = {}) {
|
|
1286
1323
|
ensureStyles()
|
|
@@ -1308,6 +1345,13 @@ module.exports = {
|
|
|
1308
1345
|
})
|
|
1309
1346
|
}
|
|
1310
1347
|
} catch {}
|
|
1348
|
+
// Composer services (inputTriggers + sessions) for the +技能 button;
|
|
1349
|
+
// absence hides the button only.
|
|
1350
|
+
try {
|
|
1351
|
+
if (typeof ctx.inject === 'function') {
|
|
1352
|
+
ctx.inject(['inputTriggers', 'sessions'], (scope) => { composerScope = scope })
|
|
1353
|
+
}
|
|
1354
|
+
} catch {}
|
|
1311
1355
|
// Locale service is optional at boot order — degrade to EN until present
|
|
1312
1356
|
let t = (key, vars) => {
|
|
1313
1357
|
let out = EN[key] ?? key
|
|
@@ -1359,5 +1403,36 @@ module.exports = {
|
|
|
1359
1403
|
}))
|
|
1360
1404
|
} catch (e) { (globalThis.__skErrors = globalThis.__skErrors || []).push('settings:' + (e && e.message)); throw e }
|
|
1361
1405
|
}, 'skills-management: settings section')
|
|
1406
|
+
ctx.effect(() => {
|
|
1407
|
+
try {
|
|
1408
|
+
ctx.slots.inject('conversation.input.left', () => ctx.slots.register({
|
|
1409
|
+
name: 'conversation.input.left',
|
|
1410
|
+
id: CLIENT_NAME,
|
|
1411
|
+
order: 60,
|
|
1412
|
+
locale: NS,
|
|
1413
|
+
label: () => t('pickSkill'),
|
|
1414
|
+
inject: () => ({ t }),
|
|
1415
|
+
}, function SkillButtonSlot(apiProps) {
|
|
1416
|
+
return h(ComposerButtonSlot, {
|
|
1417
|
+
__t: t, icon: '⚡', label: t('pickSkill'), title: t('pickSkillTitle'),
|
|
1418
|
+
source: 'skill', sessionId: apiProps && apiProps.sessionId, input: apiProps && apiProps.input,
|
|
1419
|
+
})
|
|
1420
|
+
}))
|
|
1421
|
+
} catch (e) { (globalThis.__skErrors = globalThis.__skErrors || []).push('input.left:' + (e && e.message)); throw e }
|
|
1422
|
+
}, 'skills-management: input left button')
|
|
1362
1423
|
},
|
|
1363
1424
|
}
|
|
1425
|
+
|
|
1426
|
+
/** Composer tool-row button: opens a registered '/' source (ui-skill's
|
|
1427
|
+
* `skill` source) over the session's trigger controller. Hidden while the
|
|
1428
|
+
* inputTriggers/sessions services are absent. */
|
|
1429
|
+
function ComposerButtonSlot(props) {
|
|
1430
|
+
useEffect(ensureStyles, [])
|
|
1431
|
+
const ready = !!(composerScope && composerScope.inputTriggers && composerScope.sessions && props.sessionId)
|
|
1432
|
+
if (!ready) return null
|
|
1433
|
+
return h('button', {
|
|
1434
|
+
className: 'sk-chip',
|
|
1435
|
+
title: props.title || props.label,
|
|
1436
|
+
onClick: () => { openTriggerSource(composerScope, props.sessionId, props.input, props.source) },
|
|
1437
|
+
}, `${props.icon || ''}${props.icon ? ' ' : ''}${props.label}`)
|
|
1438
|
+
}
|
package/docs/demo.gif
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weibaohui/skills-management",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "dsh 插件 · 技能市场:一个页面管理本机所有 coding agent 的技能(Claude、ZCode、Codex 等十余个执行器),一键收编进 DSH 用户库供 skill 工具调用;内置 6600+ ntd 技能市场可浏览搜索安装,支持模型可见性治理与市场每日自动同步。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"files": [
|
|
31
31
|
"src",
|
|
32
32
|
"client",
|
|
33
|
+
"docs",
|
|
33
34
|
"cordis.patch.yml"
|
|
34
35
|
],
|
|
35
36
|
"scripts": {
|
package/src/index.js
CHANGED
|
@@ -36,7 +36,6 @@ function loadSchemastery() {
|
|
|
36
36
|
}
|
|
37
37
|
const Schema = loadSchemastery()
|
|
38
38
|
|
|
39
|
-
const DEFAULT_MARKET_DIRS = [join(homedir(), '.ntd', 'bundled', 'skills')]
|
|
40
39
|
const MARKET_SCAN_SKIP = new Set(['.git', 'node_modules'])
|
|
41
40
|
const RANK_INSTALLED = 100
|
|
42
41
|
const RANK_MARKET = 500
|
|
@@ -312,6 +311,11 @@ function expandTilde(p) {
|
|
|
312
311
|
return p === '~' || p.startsWith('~/') || p.startsWith('~\\') ? join(homedir(), p.slice(2)) : p
|
|
313
312
|
}
|
|
314
313
|
|
|
314
|
+
/** dsh 数据根(与宿主一致:$DSH_HOME,缺省 ~/.dsh)。 */
|
|
315
|
+
function dshHome() {
|
|
316
|
+
return process.env.DSH_HOME ? resolve(process.env.DSH_HOME) : join(homedir(), '.dsh')
|
|
317
|
+
}
|
|
318
|
+
|
|
315
319
|
/**
|
|
316
320
|
* 切换 dsh 原生治理键 `disable-model-invocation`(docs/subsystems/skills.md)。
|
|
317
321
|
* modelInvocable=true 时移除该键;false 时写入 true。其余 frontmatter 键与正文原样保留。
|
|
@@ -377,17 +381,35 @@ function authedUrl(url, token) {
|
|
|
377
381
|
return String(url).replace(/^(https?:\/\/)([^@/]+@)?/, `$1oauth2:${encodeURIComponent(token)}@`)
|
|
378
382
|
}
|
|
379
383
|
|
|
380
|
-
/** Clone (first time) or fetch+reset (update); remote branch is truth.
|
|
381
|
-
|
|
384
|
+
/** Clone (first time) or fetch+reset (update); remote branch is truth.
|
|
385
|
+
* `sparsePaths` (e.g. ['skills']) switches the checkout to sparse mode: fresh
|
|
386
|
+
* clones pass --filter=blob:none --sparse so only those subtrees download
|
|
387
|
+
* (the ntd-resource monorepo also carries experts/ + templates/, ~2x the
|
|
388
|
+
* skills payload); servers without filter support just warn and fall back to
|
|
389
|
+
* a full clone, which sparse-checkout still prunes. An existing full checkout
|
|
390
|
+
* is converted in place — the worktree prunes immediately, already-packed
|
|
391
|
+
* blobs stay (reachable from HEAD), so the big win is on fresh clones. */
|
|
392
|
+
async function gitSyncRepo(binary, url, branch, repoDir, token, sparsePaths) {
|
|
382
393
|
const remote = authedUrl(url, token)
|
|
394
|
+
const sparse = Array.isArray(sparsePaths) && sparsePaths.length > 0 ? sparsePaths : undefined
|
|
383
395
|
let repoExists = false
|
|
384
396
|
try { await fsP.access(join(repoDir, '.git')); repoExists = true } catch { repoExists = false }
|
|
385
397
|
if (!repoExists) {
|
|
386
398
|
await fsP.rm(repoDir, { recursive: true, force: true })
|
|
387
399
|
await fsP.mkdir(join(repoDir, '..'), { recursive: true })
|
|
388
|
-
|
|
400
|
+
if (sparse) {
|
|
401
|
+
await gitExec(binary, ['clone', '-b', branch, '--depth', '1', '--filter=blob:none', '--sparse', remote, repoDir])
|
|
402
|
+
await gitExec(binary, ['sparse-checkout', 'set', '--cone', ...sparse], repoDir)
|
|
403
|
+
} else {
|
|
404
|
+
await gitExec(binary, ['clone', '-b', branch, '--depth', '1', remote, repoDir])
|
|
405
|
+
}
|
|
389
406
|
return { isFirstClone: true, hasUpdates: true, before: undefined, after: await gitCurrentCommit(binary, repoDir) }
|
|
390
407
|
}
|
|
408
|
+
// Migrate a pre-sparse full checkout in place (idempotent no-op once sparse).
|
|
409
|
+
if (sparse) {
|
|
410
|
+
try { await gitExec(binary, ['sparse-checkout', 'set', '--cone', ...sparse], repoDir) }
|
|
411
|
+
catch (e) { console.warn(`skills-management: sparse-checkout conversion failed, continuing full: ${e && e.message}`) }
|
|
412
|
+
}
|
|
391
413
|
const before = await gitCurrentCommit(binary, repoDir)
|
|
392
414
|
await gitExec(binary, ['fetch', remote, branch], repoDir)
|
|
393
415
|
await gitExec(binary, ['reset', '--hard', 'FETCH_HEAD'], repoDir)
|
|
@@ -547,10 +569,15 @@ module.exports = {
|
|
|
547
569
|
return resolve(expandTilde(
|
|
548
570
|
typeof eff.repoDir === 'string' && eff.repoDir !== '' ? eff.repoDir
|
|
549
571
|
: config.marketRepoDir !== undefined ? config.marketRepoDir
|
|
550
|
-
: join(
|
|
572
|
+
: join(dshHome(), 'skills-management', 'market')))
|
|
551
573
|
}
|
|
552
574
|
const marketRoots = () => configMarketDirs !== undefined ? configMarketDirs : [join(effectiveRepoDir(), 'skills')]
|
|
553
575
|
const marketDirs = marketRoots // scan/install/locate call sites read through this
|
|
576
|
+
// 稀疏检出子树:ntd-resource 仓库同时携带 experts/templates,只检 skills 一份省一半以上
|
|
577
|
+
// 流量与磁盘。config.marketSparsePaths: null 关闭;自定义数组换目标子树。
|
|
578
|
+
const marketSparsePaths = () => config.marketSparsePaths === null
|
|
579
|
+
? undefined
|
|
580
|
+
: (Array.isArray(config.marketSparsePaths) && config.marketSparsePaths.length > 0 ? config.marketSparsePaths.map(String) : ['skills'])
|
|
554
581
|
const installedDir = resolve(expandTilde(config.installedDir !== undefined ? config.installedDir : process.env.DSH_HOME ? join(process.env.DSH_HOME, 'skills') : join(homedir(), '.dsh', 'skills')))
|
|
555
582
|
const providerName = config.providerName !== undefined ? config.providerName : 'ntd-skills'
|
|
556
583
|
// 市场库存(数几千条)默认不进模型目录 available_skills —— 只作为可浏览/可安装的货架。
|
|
@@ -771,7 +798,7 @@ module.exports = {
|
|
|
771
798
|
if (!ok) throw new Error('git is not available on PATH')
|
|
772
799
|
const started = Date.now()
|
|
773
800
|
const repoDir = effectiveRepoDir()
|
|
774
|
-
const result = await gitSyncRepo(eff.gitBinary, eff.url, eff.branch, repoDir, eff.token)
|
|
801
|
+
const result = await gitSyncRepo(eff.gitBinary, eff.url, eff.branch, repoDir, eff.token, marketSparsePaths())
|
|
775
802
|
marketState.lastSyncAt = new Date().toISOString()
|
|
776
803
|
marketState.lastResult = { ...result, at: marketState.lastSyncAt, durationMs: Date.now() - started }
|
|
777
804
|
await saveMarketState()
|
|
@@ -898,6 +925,7 @@ module.exports = {
|
|
|
898
925
|
autoSync: eff.autoSync, syncOnStartup: eff.syncOnStartup,
|
|
899
926
|
hasToken: typeof eff.token === 'string' && eff.token !== '',
|
|
900
927
|
syncing: marketSyncRun !== null,
|
|
928
|
+
sparsePaths: marketSparsePaths() ?? null,
|
|
901
929
|
})
|
|
902
930
|
return
|
|
903
931
|
}
|