dsh-skill-picker 0.3.3 → 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/README.md +18 -1
- package/lib/client.js +21 -80
- package/lib/client.js.map +2 -2
- package/package.json +1 -1
- package/src/client/index.jsx +34 -97
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-skill-picker",
|
|
3
3
|
"description": "DSH Web GUI skill picker: a button beside the composer opens a searchable list of installed skills; picking one inserts the official `/skill-name` gesture into the input box, so the skill loads with your message (WorkBuddy-style skill invocation for DeepSeek Harness).",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
package/src/client/index.jsx
CHANGED
|
@@ -445,12 +445,15 @@ function SkillPickerButton(props) {
|
|
|
445
445
|
|
|
446
446
|
/** Apply the browser half: register the picker into the composer tool row. */
|
|
447
447
|
export function apply(ctx) {
|
|
448
|
-
// Primary skill source: the official host skills API
|
|
449
|
-
//
|
|
448
|
+
// Primary skill source: the official host skills API. In DSH 0.1.2-alpha.x
|
|
449
|
+
// the RPC moved from `connection.api.skills` (rc.x) to `remote.skills`
|
|
450
|
+
// (used by the official ui-skill plugin); try both before falling back.
|
|
450
451
|
const listSkills = async (sessionId) => {
|
|
451
|
-
const
|
|
452
|
+
const remoteSkills = ctx.remote?.skills
|
|
453
|
+
const connectionSkills = ctx.connection?.api?.skills
|
|
454
|
+
const skills = remoteSkills ?? connectionSkills
|
|
452
455
|
if (skills === undefined || typeof skills.list !== 'function') {
|
|
453
|
-
throw new Error('connection.api.skills
|
|
456
|
+
throw new Error('skills RPC unavailable (remote.skills / connection.api.skills)')
|
|
454
457
|
}
|
|
455
458
|
const controller = new AbortController()
|
|
456
459
|
const { result } = await skills.list({ sessionId }, controller.signal)
|
|
@@ -471,6 +474,7 @@ export function apply(ctx) {
|
|
|
471
474
|
currentCwd = ''
|
|
472
475
|
}
|
|
473
476
|
}
|
|
477
|
+
|
|
474
478
|
syncCwd()
|
|
475
479
|
const unsubscribe = ctx.sessions.list.subscribe(syncCwd)
|
|
476
480
|
ctx.effect(() => {
|
|
@@ -491,101 +495,34 @@ export function apply(ctx) {
|
|
|
491
495
|
}
|
|
492
496
|
}, 'dsh-skill-picker: composer input slot')
|
|
493
497
|
|
|
494
|
-
// Fuzzy `/` completion
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
+
// Fuzzy `/` completion: instead of registering a parallel source group
|
|
499
|
+
// (which would appear as a second list next to the official one), expose a
|
|
500
|
+
// global matcher that the patched official ui-skill candidates calls. The
|
|
501
|
+
// official group stays THE single `/` list; only its matching behaviour is
|
|
502
|
+
// upgraded to fuzzy + pinyin (name AND description, subsequence scoring).
|
|
498
503
|
ctx.effect(() => {
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const source = {
|
|
519
|
-
trigger: '/',
|
|
520
|
-
name: 'skill-fuzzy',
|
|
521
|
-
order: -10,
|
|
522
|
-
async candidates(session, { query, signal }) {
|
|
523
|
-
const skills = await listSkills(session.sessionId)
|
|
524
|
-
if (signal.aborted) return []
|
|
525
|
-
namesCache.set(session.sessionId, skills.map((s) => s.name))
|
|
526
|
-
notifyLexicon(session.sessionId)
|
|
527
|
-
const ordered = rankByUsage(skills, loadUsage())
|
|
528
|
-
const q = String(query ?? '').trim().toLowerCase()
|
|
529
|
-
if (q === '') {
|
|
530
|
-
// Empty `/` surfaces ALL skills, usage first (same rule as ⚡ panel).
|
|
531
|
-
return ordered.map((s) => ({ name: s.name, description: s.description }))
|
|
532
|
-
}
|
|
533
|
-
// Typing uses fuzzysort (subsequence matching + relevance score) over
|
|
534
|
-
// name AND description, so partial/gappy queries and keywords match.
|
|
535
|
-
// The search string also carries the skill's pinyin forms (spaced,
|
|
536
|
-
// joined, initials — name and description), so `ji yi` / `jiyi` / `jy`
|
|
537
|
-
// match Chinese skill names & descriptions.
|
|
538
|
-
const targets = ordered.map((s) => ({
|
|
539
|
-
s,
|
|
540
|
-
search: `${s.name} ${s.description ?? ''} ${skillPinyinText(s.name, s.description ?? '')}`,
|
|
541
|
-
}))
|
|
542
|
-
const results = fuzzysort.go(q, targets, {
|
|
543
|
-
key: 'search',
|
|
544
|
-
limit: 12,
|
|
545
|
-
threshold: -10000,
|
|
546
|
-
})
|
|
547
|
-
return results
|
|
548
|
-
.filter((r) => r.score > 0)
|
|
549
|
-
.map((r) => ({ name: r.obj.s.name, description: r.obj.s.description }))
|
|
550
|
-
},
|
|
551
|
-
warm(session) {
|
|
552
|
-
refreshNames(session.sessionId)
|
|
553
|
-
},
|
|
554
|
-
lexicon(session) {
|
|
555
|
-
return namesCache.get(session.sessionId)
|
|
556
|
-
},
|
|
557
|
-
subscribeLexicon(session, listener) {
|
|
558
|
-
const key = session.sessionId
|
|
559
|
-
const set = lexiconListeners.get(key) ?? new Set()
|
|
560
|
-
set.add(listener)
|
|
561
|
-
lexiconListeners.set(key, set)
|
|
562
|
-
return () => {
|
|
563
|
-
const cur = lexiconListeners.get(key)
|
|
564
|
-
if (cur !== undefined) {
|
|
565
|
-
cur.delete(listener)
|
|
566
|
-
if (cur.size === 0) lexiconListeners.delete(key)
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
},
|
|
570
|
-
onPick({ candidate }) {
|
|
571
|
-
// Record usage (same rule as the ⚡ panel) so slash-picked skills
|
|
572
|
-
// rank higher the next time they open `/`.
|
|
573
|
-
try {
|
|
574
|
-
const usage = loadUsage()
|
|
575
|
-
const name = candidate.name
|
|
576
|
-
const next = { ...usage, [name]: { count: (usage[name]?.count ?? 0) + 1, lastUsed: Date.now() } }
|
|
577
|
-
saveUsage(next)
|
|
578
|
-
} catch {
|
|
579
|
-
/* usage recording is best-effort */
|
|
580
|
-
}
|
|
581
|
-
return { text: `/${candidate.name} ` }
|
|
582
|
-
},
|
|
504
|
+
// Mirror the ⚡ panel's `rankByUsage` semantics: recently/frequently used
|
|
505
|
+
// skills first, then the untouched rest — so usage history keeps working.
|
|
506
|
+
const fuzzyMatch = (skills, query = '') => {
|
|
507
|
+
const ordered = rankByUsage(skills, loadUsage())
|
|
508
|
+
const q = String(query).trim().toLowerCase()
|
|
509
|
+
if (q === '') return ordered
|
|
510
|
+
const targets = ordered.map((s) => ({
|
|
511
|
+
s,
|
|
512
|
+
search: `${s.name} ${s.description ?? ''} ${skillPinyinText(s.name, s.description ?? '')}`,
|
|
513
|
+
}))
|
|
514
|
+
const results = fuzzysort.go(q, targets, {
|
|
515
|
+
key: 'search',
|
|
516
|
+
limit: 12,
|
|
517
|
+
threshold: -10000,
|
|
518
|
+
})
|
|
519
|
+
return results
|
|
520
|
+
.filter((r) => r.score > 0)
|
|
521
|
+
.map((r) => r.obj.s)
|
|
583
522
|
}
|
|
584
|
-
|
|
523
|
+
window.__dshSkillPickerFuzzy = fuzzyMatch
|
|
585
524
|
return () => {
|
|
586
|
-
|
|
587
|
-
namesCache.clear()
|
|
588
|
-
lexiconListeners.clear()
|
|
525
|
+
if (window.__dshSkillPickerFuzzy === fuzzyMatch) delete window.__dshSkillPickerFuzzy
|
|
589
526
|
}
|
|
590
|
-
}, 'dsh-skill-picker: fuzzy / source')
|
|
527
|
+
}, 'dsh-skill-picker: fuzzy matcher for official / source')
|
|
591
528
|
}
|