dsh-skill-picker 0.5.7 → 0.5.8
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/lib/index.js +17 -6
- package/lib/index.js.map +2 -2
- package/package.json +5 -2
- package/src/patch-ui-skill.js +21 -7
package/lib/index.js
CHANGED
|
@@ -22,17 +22,28 @@ var PATCHES = [
|
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
id: "fuzzy-candidates",
|
|
25
|
-
title: "prefix matcher \u2192 fuzzy+pinyin matcher",
|
|
25
|
+
title: "prefix/rank matcher \u2192 fuzzy+pinyin matcher",
|
|
26
26
|
isApplied(text) {
|
|
27
27
|
return text.includes(FUZZY_MARKER);
|
|
28
28
|
},
|
|
29
29
|
apply(text) {
|
|
30
|
-
|
|
31
|
-
/(\t*)return
|
|
32
|
-
(
|
|
33
|
-
|
|
30
|
+
const ANCHORS = [
|
|
31
|
+
/(\t*)return (\(0, [\w.$]+\.rankByName\)\(skills, query\)|rankByName\(skills, query\))\.map\(\(skill\) => \(\{/,
|
|
32
|
+
/(\t*)return (skills\.filter\(\(skill\) => skill\.name\.startsWith\(query\)\))\.map\(\(skill\) => \(\{/
|
|
33
|
+
];
|
|
34
|
+
for (const re of ANCHORS) {
|
|
35
|
+
const m = text.match(re);
|
|
36
|
+
if (!m) continue;
|
|
37
|
+
const [, indent, official] = m;
|
|
38
|
+
return text.replace(
|
|
39
|
+
re,
|
|
40
|
+
`${indent}// dsh-skill-picker patch: fuzzy+pinyin matcher (self-healed)
|
|
41
|
+
${indent}const officialMatcher = ${official};
|
|
42
|
+
${indent}const matcher = typeof window.${FUZZY_MARKER} === "function" ? window.${FUZZY_MARKER}(skills, query) : officialMatcher;
|
|
34
43
|
${indent}return matcher.map((skill) => ({`
|
|
35
|
-
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return text;
|
|
36
47
|
}
|
|
37
48
|
},
|
|
38
49
|
{
|
package/lib/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.js", "../src/patch-ui-skill.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * dsh-skill-picker \u2014 host half: exposes the installed-skill catalog to the\n * browser half through a small JSON route (`/dsh-skill-picker/skills`) and\n * announces the picker to every agent through the system-prompt section\n * mechanism.\n *\n * The catalog is scanned directly from the DSH user skills directory\n * (`$DSH_HOME/skills`, default `~/.dsh/skills`) by reading each skill's\n * `SKILL.md` frontmatter. Why not `ctx.skills`? The filesystem skill provider\n * is mounted in agent scope (the standard preset's standing mount), so a\n * host-context `ctx.skills.snapshot({})` sees only the global layer \u2014 which\n * is empty for user skills. Scanning the same roots the provider uses keeps\n * the picker's list in sync with what agents actually load.\n *\n * The browser half (exports \"./client\") is served by client-modules from the\n * same package's dsh.client declaration.\n *\n * @module dsh-skill-picker\n */\n\nimport { readFile, readdir } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport { healUiSkillPatches } from './patch-ui-skill.js'\n\n/** Required services: the route registry and the prompt band. */\nexport const inject = ['webServer', 'systemPrompt']\n\n/** Order of the announcement section within the tool-guidance band. */\nconst SECTION_ORDER = 215\n\n/** Model-facing announcement: picker presence and the user-visible gesture. */\nexport const SKILL_PICKER_GUIDANCE =\n '\u672C\u673A\u5DF2\u5B89\u88C5 dsh-skill-picker \u63D2\u4EF6\uFF08Web GUI \u7684\u6280\u80FD\u9009\u62E9\u5668\uFF09\uFF1A\u8F93\u5165\u6846\u65C1\u6709\u6280\u80FD\u6309\u94AE\uFF0C\u7528\u6237\u70B9\u9009\u6280\u80FD\u540E\u4F1A\u628A `/\u6280\u80FD\u540D`\uFF08\u5982 /duo-xuan-pi-gai\uFF09\u63D2\u5165\u53D1\u9001\u6846\u5E76\u968F\u6D88\u606F\u53D1\u51FA\u3002DSH \u5B98\u65B9\u673A\u5236\u4F1A\u628A\u7528\u6237\u6D88\u606F\u91CC\u7684 `/\u6280\u80FD\u540D` \u624B\u52BF\u5F53\u4F5C\u6280\u80FD\u76F4\u63A5\u8C03\u7528\u5E76\u81EA\u52A8\u52A0\u8F7D\u6280\u80FD\u5185\u5BB9\u2014\u2014\u4F60\u7167\u5E38\u6309\u52A0\u8F7D\u540E\u7684\u6280\u80FD\u6307\u4EE4\u6267\u884C\u5373\u53EF\uFF0C\u65E0\u9700\u989D\u5916\u64CD\u4F5C\u3002\u7528\u6237\u8BF4\u300C\u6280\u80FD\u9009\u62E9\u5668 / \u9009\u4E2A\u6280\u80FD / \u6280\u80FD\u5217\u8868\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\u3002'\n\n/** Resolve the user skills directory, mirroring the official provider's default. */\nfunction userSkillsDir() {\n const home = process.env.DSH_HOME ?? path.join(os.homedir(), '.dsh')\n return path.join(home, 'skills')\n}\n\n/**\n * Resolve the user agents-home skills directory, mirroring the official\n * provider's default (`$DSH_AGENTS_HOME` > `~/.agents`). This is the\n * cross-tool `.agents` convention; the official `dsh-skill-filesystem` scans\n * it as its `user-agents` root (rank 500), so the fallback must too \u2014 else\n * the picker's list silently misses skills that DSH's own `/` completion\n * shows (issue #5).\n */\nfunction userAgentsSkillsDir() {\n const agentsHome = process.env.DSH_AGENTS_HOME ?? path.join(os.homedir(), '.agents')\n return path.join(agentsHome, 'skills')\n}\n\n/** Parse a SKILL.md frontmatter block into a key/value map (flat YAML subset). */\nfunction parseFrontmatter(content) {\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/)\n if (!match) return {}\n const out = {}\n for (const line of match[1].split(/\\r?\\n/)) {\n const kv = line.match(/^([A-Za-z0-9_-]+):\\s*(.*)$/)\n if (!kv) continue\n const value = kv[2].trim().replace(/^[\"']|[\"']$/g, '')\n if (value !== '') out[kv[1]] = value\n }\n return out\n}\n\n/** Scan one skill directory into the map; never throws (missing dir is a no-op). */\nasync function scanSkillsDirInto(map, dir) {\n let entries\n try {\n entries = await readdir(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const skillDir = path.join(dir, entry.name)\n let content\n try {\n content = await readFile(path.join(skillDir, 'SKILL.md'), 'utf8')\n } catch {\n continue\n }\n const meta = parseFrontmatter(content)\n // Later writes win, so project-level skills override same-named user skills.\n map.set(meta.name ?? entry.name, {\n name: meta.name ?? entry.name,\n description: meta.description ?? '',\n path: skillDir,\n })\n }\n}\n\n/**\n * Scan the same roots the official `dsh-skill-filesystem` provider uses, so\n * the fallback route stays in sync with what agents actually load:\n * project `.dsh/skills` (rank 100) > project `.agents/skills` (200) >\n * user `~/.dsh/skills` (400) > user `~/.agents/skills` (500). Scanned\n * low-priority first, so later writes (higher priority) win in the map.\n * Never throws (a missing dir yields []).\n * @param cwd - the active session's workspace root (undefined = user level only).\n */\nasync function scanSkills(cwd) {\n const map = new Map()\n await scanSkillsDirInto(map, userAgentsSkillsDir())\n await scanSkillsDirInto(map, userSkillsDir())\n if (typeof cwd === 'string' && cwd !== '') {\n await scanSkillsDirInto(map, path.join(cwd, '.agents', 'skills'))\n await scanSkillsDirInto(map, path.join(cwd, '.dsh', 'skills'))\n }\n return [...map.values()].sort((a, b) => a.name.localeCompare(b.name))\n}\n\n/**\n * Mount the skills route and the prompt section.\n * @param ctx - context carrying webServer and systemPrompt.\n */\nexport function apply(ctx) {\n ctx.effect(() => {\n const handler = async (req, res) => {\n try {\n // cwd query carries the active session's workspace root from the client.\n const cwd = req.url !== undefined ? new URL(req.url, 'http://dsh').searchParams.get('cwd') ?? undefined : undefined\n const skills = await scanSkills(cwd)\n res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: true, complete: true, skills }))\n } catch (error) {\n res.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: false, error: String(error?.message ?? error) }))\n }\n }\n return ctx.webServer.register({ kind: 'prefix', path: '/dsh-skill-picker', handler })\n }, 'dsh-skill-picker: routes')\n\n ctx.effect(() => ctx.systemPrompt.section({\n name: 'plugin:skill-picker',\n order: SECTION_ORDER,\n text: SKILL_PICKER_GUIDANCE,\n }), 'dsh-skill-picker: prompt section')\n\n // Self-healing patch for the official ui-skill package: keeps the `/`\n // completion's skill group ordered above commands and its matching fuzzy\n // across DSH upgrades. Runs once per boot; idempotent, backed up, and\n // never allowed to take the host down.\n ctx.effect(() => {\n healUiSkillPatches().then((report) => {\n if (report.files.length > 0) {\n console.log('[dsh-skill-picker] ui-skill patch report:', JSON.stringify(report))\n }\n }).catch((error) => {\n console.warn('[dsh-skill-picker] ui-skill patch failed:', error)\n })\n return () => {}\n }, 'dsh-skill-picker: ui-skill self-heal patch')\n}\n", "/**\n * dsh-skill-picker \u2014 host-side self-healing patch for the official\n * `@deepseek-ai/dsh-client-ui-skill` package.\n *\n * The picker upgrades the official `/` completion in two ways that the\n * official package does not provide out of the box:\n *\n * 1. `order: 2 \u2192 -1` \u2014 the skill group sorts ABOVE the command group\n * (commands register with the default order 0; lower = higher in the\n * official menu).\n * 2. fuzzy+pinyin candidates \u2014 the official prefix-only matcher\n * (`skill.name.startsWith(query)`) is replaced by the picker's\n * `window.__dshSkillPickerFuzzy` matcher when the picker is mounted\n * (single source group, same list, upgraded matching).\n *\n * Every DSH boot this module scans every profile under `$DSH_HOME/profiles`\n * (default `~/.dsh/profiles`) for an installed ui-skill `lib/client.js` \u2014\n * either the user's local patched copy (`local/dsh-client-ui-skill`) or the\n * plain npm install (`node_modules/@deepseek-ai/dsh-client-ui-skill`) \u2014 and\n * re-applies both patches when DSH upgrades overwrote them. The original file\n * is backed up once as `<file>.dsh-skill-picker.bak` before the first write.\n * All operations are idempotent and never throw: a missing profile, package\n * or read failure is reported and skipped so a broken patch can never take\n * the host down.\n *\n * @module dsh-skill-picker/patch-ui-skill\n */\n\nimport { readFile, writeFile, copyFile, readdir, access } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\n/** Marker that the candidates patch is already in place. */\nconst FUZZY_MARKER = '__dshSkillPickerFuzzy'\n\n/** Marker that the pick-tracking patch is already in place. */\nconst TRACK_MARKER = '__dshSkillPickerTrack'\n\n/** The self-healing patches, in application order. */\nexport const PATCHES = [\n {\n id: 'order',\n title: 'skill group order 2 \u2192 -1 (above commands)',\n isApplied(text) {\n return /name: \"skill\",[\\s\\S]*?order:\\s*-1,/.test(text)\n },\n apply(text) {\n return text.replace(/(name: \"skill\",\\s*order: )2,/, '$1-1,')\n },\n },\n {\n id: 'fuzzy-candidates',\n title: 'prefix matcher \u2192 fuzzy+pinyin matcher',\n isApplied(text) {\n return text.includes(FUZZY_MARKER)\n },\n apply(text) {\n return text.replace(\n /(\\t*)return skills\\.filter\\(\\(skill\\) => skill\\.name\\.startsWith\\(query\\)\\)\\.map\\(\\(skill\\) => \\(\\{/,\n (match, indent) =>\n `${indent}// dsh-skill-picker patch: fuzzy+pinyin matcher (self-healed)\\n` +\n `${indent}const matcher = typeof window.${FUZZY_MARKER} === \"function\" ? window.${FUZZY_MARKER}(skills, query) : skills.filter((skill) => skill.name.startsWith(query));\\n` +\n `${indent}return matcher.map((skill) => ({`,\n )\n },\n },\n {\n id: 'pick-tracking',\n title: 'record usage when picked from the official / menu',\n isApplied(text) {\n return text.includes(TRACK_MARKER)\n },\n apply(text) {\n return text.replace(\n /(\\t*)onPick\\(\\{ candidate \\}\\) \\{\\n(\\t*)return \\{ text: `\\/\\$\\{candidate\\.name\\} ` \\};\\n(\\t*)\\}/,\n (match, i1, i2, i3) =>\n `${i1}onPick({ candidate }) {\\n` +\n `${i2} // dsh-skill-picker patch: usage tracking (self-healed)\\n` +\n `${i2} try { window.${TRACK_MARKER}?.(candidate.name) } catch { /* best-effort */ }\\n` +\n `${i2} return { text: \\`/\\${candidate.name} \\` };\\n` +\n `${i3}}`,\n )\n },\n },\n]\n\n/** Resolve the DSH home directory, mirroring the official convention. */\nexport function dshHome() {\n return process.env.DSH_HOME ?? path.join(os.homedir(), '.dsh')\n}\n\n/**\n * Enumerate every installed ui-skill `lib/client.js` across all profiles:\n * the user's local patched copy first (that is what the profile actually\n * loads when linked), then the plain npm install. Deduplicated by real path.\n * Never throws \u2014 a missing profiles dir yields [].\n * @returns {Promise<string[]>} candidate file paths.\n */\nexport async function uiSkillClientPaths() {\n const profilesDir = path.join(dshHome(), 'profiles')\n let profiles\n try {\n profiles = await readdir(profilesDir, { withFileTypes: true })\n } catch {\n return []\n }\n const seen = new Set()\n const found = []\n for (const entry of profiles) {\n if (!entry.isDirectory()) continue\n const candidates = [\n path.join(profilesDir, entry.name, 'local', 'dsh-client-ui-skill', 'lib', 'client.js'),\n path.join(profilesDir, entry.name, 'node_modules', '@deepseek-ai', 'dsh-client-ui-skill', 'lib', 'client.js'),\n ]\n for (const candidate of candidates) {\n try {\n await access(candidate)\n const real = await import('node:fs/promises').then(({ realpath }) => realpath(candidate))\n if (!seen.has(real)) {\n seen.add(real)\n found.push(candidate)\n }\n } catch {\n /* not present at this location */\n }\n }\n }\n return found\n}\n\n/**\n * Apply both patches to one ui-skill client.js. Idempotent: already-applied\n * patches are reported as skipped; the original file is backed up once before\n * the first modification. Never throws for a patch that does not match (it is\n * reported as `noop`), only for actual I/O failures.\n * @param {string} file - absolute path to the target client.js.\n * @returns {Promise<{file: string, patched: string[], skipped: string[], noop: string[]}>}\n */\nexport async function patchUiSkillFile(file) {\n const text = await readFile(file, 'utf8')\n const result = { file, patched: [], skipped: [], noop: [] }\n let next = text\n for (const patch of PATCHES) {\n if (patch.isApplied(next)) {\n result.skipped.push(patch.id)\n continue\n }\n const candidate = patch.apply(next)\n if (candidate === next) {\n result.noop.push(patch.id)\n continue\n }\n next = candidate\n result.patched.push(patch.id)\n }\n if (result.patched.length === 0) return result\n const backup = `${file}.dsh-skill-picker.bak`\n try {\n await access(backup)\n } catch {\n await copyFile(file, backup)\n }\n await writeFile(file, next, 'utf8')\n return result\n}\n\n/**\n * Self-heal entry point: scan all profiles, patch every ui-skill copy found,\n * and return one combined report. Never throws \u2014 each failure is collected\n * into `errors` so the host boot is never taken down by a broken patch.\n * @returns {Promise<{files: Array, errors: string[]}>}\n */\nexport async function healUiSkillPatches() {\n const files = await uiSkillClientPaths()\n const filesReport = []\n const errors = []\n for (const file of files) {\n try {\n filesReport.push(await patchUiSkillFile(file))\n } catch (error) {\n errors.push(`${file}: ${String(error?.message ?? error)}`)\n }\n }\n return { files: filesReport, errors }\n}\n"],
|
|
5
|
-
"mappings": ";AAoBA,SAAS,YAAAA,WAAU,WAAAC,gBAAe;AAClC,OAAOC,SAAQ;AACf,OAAOC,WAAU;;;ACMjB,SAAS,UAAU,WAAW,UAAU,SAAS,cAAc;AAC/D,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,IAAM,eAAe;AAGrB,IAAM,eAAe;AAGd,IAAM,UAAU;AAAA,EACrB;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,MAAM;AACd,aAAO,qCAAqC,KAAK,IAAI;AAAA,IACvD;AAAA,IACA,MAAM,MAAM;AACV,aAAO,KAAK,QAAQ,gCAAgC,OAAO;AAAA,IAC7D;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,MAAM;AACd,aAAO,KAAK,SAAS,YAAY;AAAA,IACnC;AAAA,IACA,MAAM,MAAM;
|
|
4
|
+
"sourcesContent": ["/**\n * dsh-skill-picker \u2014 host half: exposes the installed-skill catalog to the\n * browser half through a small JSON route (`/dsh-skill-picker/skills`) and\n * announces the picker to every agent through the system-prompt section\n * mechanism.\n *\n * The catalog is scanned directly from the DSH user skills directory\n * (`$DSH_HOME/skills`, default `~/.dsh/skills`) by reading each skill's\n * `SKILL.md` frontmatter. Why not `ctx.skills`? The filesystem skill provider\n * is mounted in agent scope (the standard preset's standing mount), so a\n * host-context `ctx.skills.snapshot({})` sees only the global layer \u2014 which\n * is empty for user skills. Scanning the same roots the provider uses keeps\n * the picker's list in sync with what agents actually load.\n *\n * The browser half (exports \"./client\") is served by client-modules from the\n * same package's dsh.client declaration.\n *\n * @module dsh-skill-picker\n */\n\nimport { readFile, readdir } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport { healUiSkillPatches } from './patch-ui-skill.js'\n\n/** Required services: the route registry and the prompt band. */\nexport const inject = ['webServer', 'systemPrompt']\n\n/** Order of the announcement section within the tool-guidance band. */\nconst SECTION_ORDER = 215\n\n/** Model-facing announcement: picker presence and the user-visible gesture. */\nexport const SKILL_PICKER_GUIDANCE =\n '\u672C\u673A\u5DF2\u5B89\u88C5 dsh-skill-picker \u63D2\u4EF6\uFF08Web GUI \u7684\u6280\u80FD\u9009\u62E9\u5668\uFF09\uFF1A\u8F93\u5165\u6846\u65C1\u6709\u6280\u80FD\u6309\u94AE\uFF0C\u7528\u6237\u70B9\u9009\u6280\u80FD\u540E\u4F1A\u628A `/\u6280\u80FD\u540D`\uFF08\u5982 /duo-xuan-pi-gai\uFF09\u63D2\u5165\u53D1\u9001\u6846\u5E76\u968F\u6D88\u606F\u53D1\u51FA\u3002DSH \u5B98\u65B9\u673A\u5236\u4F1A\u628A\u7528\u6237\u6D88\u606F\u91CC\u7684 `/\u6280\u80FD\u540D` \u624B\u52BF\u5F53\u4F5C\u6280\u80FD\u76F4\u63A5\u8C03\u7528\u5E76\u81EA\u52A8\u52A0\u8F7D\u6280\u80FD\u5185\u5BB9\u2014\u2014\u4F60\u7167\u5E38\u6309\u52A0\u8F7D\u540E\u7684\u6280\u80FD\u6307\u4EE4\u6267\u884C\u5373\u53EF\uFF0C\u65E0\u9700\u989D\u5916\u64CD\u4F5C\u3002\u7528\u6237\u8BF4\u300C\u6280\u80FD\u9009\u62E9\u5668 / \u9009\u4E2A\u6280\u80FD / \u6280\u80FD\u5217\u8868\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\u3002'\n\n/** Resolve the user skills directory, mirroring the official provider's default. */\nfunction userSkillsDir() {\n const home = process.env.DSH_HOME ?? path.join(os.homedir(), '.dsh')\n return path.join(home, 'skills')\n}\n\n/**\n * Resolve the user agents-home skills directory, mirroring the official\n * provider's default (`$DSH_AGENTS_HOME` > `~/.agents`). This is the\n * cross-tool `.agents` convention; the official `dsh-skill-filesystem` scans\n * it as its `user-agents` root (rank 500), so the fallback must too \u2014 else\n * the picker's list silently misses skills that DSH's own `/` completion\n * shows (issue #5).\n */\nfunction userAgentsSkillsDir() {\n const agentsHome = process.env.DSH_AGENTS_HOME ?? path.join(os.homedir(), '.agents')\n return path.join(agentsHome, 'skills')\n}\n\n/** Parse a SKILL.md frontmatter block into a key/value map (flat YAML subset). */\nfunction parseFrontmatter(content) {\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/)\n if (!match) return {}\n const out = {}\n for (const line of match[1].split(/\\r?\\n/)) {\n const kv = line.match(/^([A-Za-z0-9_-]+):\\s*(.*)$/)\n if (!kv) continue\n const value = kv[2].trim().replace(/^[\"']|[\"']$/g, '')\n if (value !== '') out[kv[1]] = value\n }\n return out\n}\n\n/** Scan one skill directory into the map; never throws (missing dir is a no-op). */\nasync function scanSkillsDirInto(map, dir) {\n let entries\n try {\n entries = await readdir(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const skillDir = path.join(dir, entry.name)\n let content\n try {\n content = await readFile(path.join(skillDir, 'SKILL.md'), 'utf8')\n } catch {\n continue\n }\n const meta = parseFrontmatter(content)\n // Later writes win, so project-level skills override same-named user skills.\n map.set(meta.name ?? entry.name, {\n name: meta.name ?? entry.name,\n description: meta.description ?? '',\n path: skillDir,\n })\n }\n}\n\n/**\n * Scan the same roots the official `dsh-skill-filesystem` provider uses, so\n * the fallback route stays in sync with what agents actually load:\n * project `.dsh/skills` (rank 100) > project `.agents/skills` (200) >\n * user `~/.dsh/skills` (400) > user `~/.agents/skills` (500). Scanned\n * low-priority first, so later writes (higher priority) win in the map.\n * Never throws (a missing dir yields []).\n * @param cwd - the active session's workspace root (undefined = user level only).\n */\nasync function scanSkills(cwd) {\n const map = new Map()\n await scanSkillsDirInto(map, userAgentsSkillsDir())\n await scanSkillsDirInto(map, userSkillsDir())\n if (typeof cwd === 'string' && cwd !== '') {\n await scanSkillsDirInto(map, path.join(cwd, '.agents', 'skills'))\n await scanSkillsDirInto(map, path.join(cwd, '.dsh', 'skills'))\n }\n return [...map.values()].sort((a, b) => a.name.localeCompare(b.name))\n}\n\n/**\n * Mount the skills route and the prompt section.\n * @param ctx - context carrying webServer and systemPrompt.\n */\nexport function apply(ctx) {\n ctx.effect(() => {\n const handler = async (req, res) => {\n try {\n // cwd query carries the active session's workspace root from the client.\n const cwd = req.url !== undefined ? new URL(req.url, 'http://dsh').searchParams.get('cwd') ?? undefined : undefined\n const skills = await scanSkills(cwd)\n res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: true, complete: true, skills }))\n } catch (error) {\n res.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: false, error: String(error?.message ?? error) }))\n }\n }\n return ctx.webServer.register({ kind: 'prefix', path: '/dsh-skill-picker', handler })\n }, 'dsh-skill-picker: routes')\n\n ctx.effect(() => ctx.systemPrompt.section({\n name: 'plugin:skill-picker',\n order: SECTION_ORDER,\n text: SKILL_PICKER_GUIDANCE,\n }), 'dsh-skill-picker: prompt section')\n\n // Self-healing patch for the official ui-skill package: keeps the `/`\n // completion's skill group ordered above commands and its matching fuzzy\n // across DSH upgrades. Runs once per boot; idempotent, backed up, and\n // never allowed to take the host down.\n ctx.effect(() => {\n healUiSkillPatches().then((report) => {\n if (report.files.length > 0) {\n console.log('[dsh-skill-picker] ui-skill patch report:', JSON.stringify(report))\n }\n }).catch((error) => {\n console.warn('[dsh-skill-picker] ui-skill patch failed:', error)\n })\n return () => {}\n }, 'dsh-skill-picker: ui-skill self-heal patch')\n}\n", "/**\n * dsh-skill-picker \u2014 host-side self-healing patch for the official\n * `@deepseek-ai/dsh-client-ui-skill` package.\n *\n * The picker upgrades the official `/` completion in two ways that the\n * official package does not provide out of the box:\n *\n * 1. `order: 2 \u2192 -1` \u2014 the skill group sorts ABOVE the command group\n * (commands register with the default order 0; lower = higher in the\n * official menu).\n * 2. fuzzy+pinyin candidates \u2014 the official prefix-only matcher\n * (`skill.name.startsWith(query)`) is replaced by the picker's\n * `window.__dshSkillPickerFuzzy` matcher when the picker is mounted\n * (single source group, same list, upgraded matching).\n *\n * Every DSH boot this module scans every profile under `$DSH_HOME/profiles`\n * (default `~/.dsh/profiles`) for an installed ui-skill `lib/client.js` \u2014\n * either the user's local patched copy (`local/dsh-client-ui-skill`) or the\n * plain npm install (`node_modules/@deepseek-ai/dsh-client-ui-skill`) \u2014 and\n * re-applies both patches when DSH upgrades overwrote them. The original file\n * is backed up once as `<file>.dsh-skill-picker.bak` before the first write.\n * All operations are idempotent and never throw: a missing profile, package\n * or read failure is reported and skipped so a broken patch can never take\n * the host down.\n *\n * @module dsh-skill-picker/patch-ui-skill\n */\n\nimport { readFile, writeFile, copyFile, readdir, access } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\n/** Marker that the candidates patch is already in place. */\nconst FUZZY_MARKER = '__dshSkillPickerFuzzy'\n\n/** Marker that the pick-tracking patch is already in place. */\nconst TRACK_MARKER = '__dshSkillPickerTrack'\n\n/** The self-healing patches, in application order. */\nexport const PATCHES = [\n {\n id: 'order',\n title: 'skill group order 2 \u2192 -1 (above commands)',\n isApplied(text) {\n return /name: \"skill\",[\\s\\S]*?order:\\s*-1,/.test(text)\n },\n apply(text) {\n return text.replace(/(name: \"skill\",\\s*order: )2,/, '$1-1,')\n },\n },\n {\n id: 'fuzzy-candidates',\n title: 'prefix/rank matcher \u2192 fuzzy+pinyin matcher',\n isApplied(text) {\n return text.includes(FUZZY_MARKER)\n },\n apply(text) {\n // \u5B98\u65B9\u5B9E\u73B0\u968F\u7248\u672C\u53D8\u8FC7\u4E24\u6B21\uFF0C\u4E24\u4E2A\u5F62\u6001\u90FD\u8981\u8BA4\uFF082026-09-11 \u5B9E\u6D4B\uFF09\uFF1A\n // 0.1.2-alpha.x : return skills.filter((skill) => skill.name.startsWith(query)).map(...)\n // 0.1.5-rc.x : return (0, _xxx.rankByName)(skills, query).map(...) \u2190 \u6539\u6210 async candidates\n // \u6BCF\u7EC4\u6355\u83B7\uFF1A$1 = \u7F29\u8FDB\uFF0C$2 = \u5B98\u65B9\u90A3\u4E00\u6BB5\u8868\u8FBE\u5F0F\uFF08\u539F\u6837\u4FDD\u7559\u505A fallback\uFF09\u3002\n const ANCHORS = [\n /(\\t*)return (\\(0, [\\w.$]+\\.rankByName\\)\\(skills, query\\)|rankByName\\(skills, query\\))\\.map\\(\\(skill\\) => \\(\\{/,\n /(\\t*)return (skills\\.filter\\(\\(skill\\) => skill\\.name\\.startsWith\\(query\\)\\))\\.map\\(\\(skill\\) => \\(\\{/,\n ]\n for (const re of ANCHORS) {\n const m = text.match(re)\n if (!m) continue\n const [, indent, official] = m\n return text.replace(\n re,\n `${indent}// dsh-skill-picker patch: fuzzy+pinyin matcher (self-healed)\\n` +\n `${indent}const officialMatcher = ${official};\\n` +\n `${indent}const matcher = typeof window.${FUZZY_MARKER} === \"function\" ? window.${FUZZY_MARKER}(skills, query) : officialMatcher;\\n` +\n `${indent}return matcher.map((skill) => ({`,\n )\n }\n return text\n },\n },\n {\n id: 'pick-tracking',\n title: 'record usage when picked from the official / menu',\n isApplied(text) {\n return text.includes(TRACK_MARKER)\n },\n apply(text) {\n return text.replace(\n /(\\t*)onPick\\(\\{ candidate \\}\\) \\{\\n(\\t*)return \\{ text: `\\/\\$\\{candidate\\.name\\} ` \\};\\n(\\t*)\\}/,\n (match, i1, i2, i3) =>\n `${i1}onPick({ candidate }) {\\n` +\n `${i2} // dsh-skill-picker patch: usage tracking (self-healed)\\n` +\n `${i2} try { window.${TRACK_MARKER}?.(candidate.name) } catch { /* best-effort */ }\\n` +\n `${i2} return { text: \\`/\\${candidate.name} \\` };\\n` +\n `${i3}}`,\n )\n },\n },\n]\n\n/** Resolve the DSH home directory, mirroring the official convention. */\nexport function dshHome() {\n return process.env.DSH_HOME ?? path.join(os.homedir(), '.dsh')\n}\n\n/**\n * Enumerate every installed ui-skill `lib/client.js` across all profiles:\n * the user's local patched copy first (that is what the profile actually\n * loads when linked), then the plain npm install. Deduplicated by real path.\n * Never throws \u2014 a missing profiles dir yields [].\n * @returns {Promise<string[]>} candidate file paths.\n */\nexport async function uiSkillClientPaths() {\n const profilesDir = path.join(dshHome(), 'profiles')\n let profiles\n try {\n profiles = await readdir(profilesDir, { withFileTypes: true })\n } catch {\n return []\n }\n const seen = new Set()\n const found = []\n for (const entry of profiles) {\n if (!entry.isDirectory()) continue\n const candidates = [\n path.join(profilesDir, entry.name, 'local', 'dsh-client-ui-skill', 'lib', 'client.js'),\n path.join(profilesDir, entry.name, 'node_modules', '@deepseek-ai', 'dsh-client-ui-skill', 'lib', 'client.js'),\n ]\n for (const candidate of candidates) {\n try {\n await access(candidate)\n const real = await import('node:fs/promises').then(({ realpath }) => realpath(candidate))\n if (!seen.has(real)) {\n seen.add(real)\n found.push(candidate)\n }\n } catch {\n /* not present at this location */\n }\n }\n }\n return found\n}\n\n/**\n * Apply both patches to one ui-skill client.js. Idempotent: already-applied\n * patches are reported as skipped; the original file is backed up once before\n * the first modification. Never throws for a patch that does not match (it is\n * reported as `noop`), only for actual I/O failures.\n * @param {string} file - absolute path to the target client.js.\n * @returns {Promise<{file: string, patched: string[], skipped: string[], noop: string[]}>}\n */\nexport async function patchUiSkillFile(file) {\n const text = await readFile(file, 'utf8')\n const result = { file, patched: [], skipped: [], noop: [] }\n let next = text\n for (const patch of PATCHES) {\n if (patch.isApplied(next)) {\n result.skipped.push(patch.id)\n continue\n }\n const candidate = patch.apply(next)\n if (candidate === next) {\n result.noop.push(patch.id)\n continue\n }\n next = candidate\n result.patched.push(patch.id)\n }\n if (result.patched.length === 0) return result\n const backup = `${file}.dsh-skill-picker.bak`\n try {\n await access(backup)\n } catch {\n await copyFile(file, backup)\n }\n await writeFile(file, next, 'utf8')\n return result\n}\n\n/**\n * Self-heal entry point: scan all profiles, patch every ui-skill copy found,\n * and return one combined report. Never throws \u2014 each failure is collected\n * into `errors` so the host boot is never taken down by a broken patch.\n * @returns {Promise<{files: Array, errors: string[]}>}\n */\nexport async function healUiSkillPatches() {\n const files = await uiSkillClientPaths()\n const filesReport = []\n const errors = []\n for (const file of files) {\n try {\n filesReport.push(await patchUiSkillFile(file))\n } catch (error) {\n errors.push(`${file}: ${String(error?.message ?? error)}`)\n }\n }\n return { files: filesReport, errors }\n}\n"],
|
|
5
|
+
"mappings": ";AAoBA,SAAS,YAAAA,WAAU,WAAAC,gBAAe;AAClC,OAAOC,SAAQ;AACf,OAAOC,WAAU;;;ACMjB,SAAS,UAAU,WAAW,UAAU,SAAS,cAAc;AAC/D,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,IAAM,eAAe;AAGrB,IAAM,eAAe;AAGd,IAAM,UAAU;AAAA,EACrB;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,MAAM;AACd,aAAO,qCAAqC,KAAK,IAAI;AAAA,IACvD;AAAA,IACA,MAAM,MAAM;AACV,aAAO,KAAK,QAAQ,gCAAgC,OAAO;AAAA,IAC7D;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,MAAM;AACd,aAAO,KAAK,SAAS,YAAY;AAAA,IACnC;AAAA,IACA,MAAM,MAAM;AAKV,YAAM,UAAU;AAAA,QACd;AAAA,QACA;AAAA,MACF;AACA,iBAAW,MAAM,SAAS;AACxB,cAAM,IAAI,KAAK,MAAM,EAAE;AACvB,YAAI,CAAC,EAAG;AACR,cAAM,CAAC,EAAE,QAAQ,QAAQ,IAAI;AAC7B,eAAO,KAAK;AAAA,UACV;AAAA,UACA,GAAG,MAAM;AAAA,EACJ,MAAM,2BAA2B,QAAQ;AAAA,EACzC,MAAM,iCAAiC,YAAY,4BAA4B,YAAY;AAAA,EAC3F,MAAM;AAAA,QACb;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,MAAM;AACd,aAAO,KAAK,SAAS,YAAY;AAAA,IACnC;AAAA,IACA,MAAM,MAAM;AACV,aAAO,KAAK;AAAA,QACV;AAAA,QACA,CAAC,OAAO,IAAI,IAAI,OACd,GAAG,EAAE;AAAA,EACF,EAAE;AAAA,EACF,EAAE,kBAAkB,YAAY;AAAA,EAChC,EAAE;AAAA,EACF,EAAE;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;AAGO,SAAS,UAAU;AACxB,SAAO,QAAQ,IAAI,YAAY,KAAK,KAAK,GAAG,QAAQ,GAAG,MAAM;AAC/D;AASA,eAAsB,qBAAqB;AACzC,QAAM,cAAc,KAAK,KAAK,QAAQ,GAAG,UAAU;AACnD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,QAAQ,aAAa,EAAE,eAAe,KAAK,CAAC;AAAA,EAC/D,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,OAAO,oBAAI,IAAI;AACrB,QAAM,QAAQ,CAAC;AACf,aAAW,SAAS,UAAU;AAC5B,QAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,UAAM,aAAa;AAAA,MACjB,KAAK,KAAK,aAAa,MAAM,MAAM,SAAS,uBAAuB,OAAO,WAAW;AAAA,MACrF,KAAK,KAAK,aAAa,MAAM,MAAM,gBAAgB,gBAAgB,uBAAuB,OAAO,WAAW;AAAA,IAC9G;AACA,eAAW,aAAa,YAAY;AAClC,UAAI;AACF,cAAM,OAAO,SAAS;AACtB,cAAM,OAAO,MAAM,OAAO,kBAAkB,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,SAAS,SAAS,CAAC;AACxF,YAAI,CAAC,KAAK,IAAI,IAAI,GAAG;AACnB,eAAK,IAAI,IAAI;AACb,gBAAM,KAAK,SAAS;AAAA,QACtB;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAUA,eAAsB,iBAAiB,MAAM;AAC3C,QAAM,OAAO,MAAM,SAAS,MAAM,MAAM;AACxC,QAAM,SAAS,EAAE,MAAM,SAAS,CAAC,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,EAAE;AAC1D,MAAI,OAAO;AACX,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,UAAU,IAAI,GAAG;AACzB,aAAO,QAAQ,KAAK,MAAM,EAAE;AAC5B;AAAA,IACF;AACA,UAAM,YAAY,MAAM,MAAM,IAAI;AAClC,QAAI,cAAc,MAAM;AACtB,aAAO,KAAK,KAAK,MAAM,EAAE;AACzB;AAAA,IACF;AACA,WAAO;AACP,WAAO,QAAQ,KAAK,MAAM,EAAE;AAAA,EAC9B;AACA,MAAI,OAAO,QAAQ,WAAW,EAAG,QAAO;AACxC,QAAM,SAAS,GAAG,IAAI;AACtB,MAAI;AACF,UAAM,OAAO,MAAM;AAAA,EACrB,QAAQ;AACN,UAAM,SAAS,MAAM,MAAM;AAAA,EAC7B;AACA,QAAM,UAAU,MAAM,MAAM,MAAM;AAClC,SAAO;AACT;AAQA,eAAsB,qBAAqB;AACzC,QAAM,QAAQ,MAAM,mBAAmB;AACvC,QAAM,cAAc,CAAC;AACrB,QAAM,SAAS,CAAC;AAChB,aAAW,QAAQ,OAAO;AACxB,QAAI;AACF,kBAAY,KAAK,MAAM,iBAAiB,IAAI,CAAC;AAAA,IAC/C,SAAS,OAAO;AACd,aAAO,KAAK,GAAG,IAAI,KAAK,OAAO,OAAO,WAAW,KAAK,CAAC,EAAE;AAAA,IAC3D;AAAA,EACF;AACA,SAAO,EAAE,OAAO,aAAa,OAAO;AACtC;;;AD3KO,IAAM,SAAS,CAAC,aAAa,cAAc;AAGlD,IAAM,gBAAgB;AAGf,IAAM,wBACX;AAGF,SAAS,gBAAgB;AACvB,QAAM,OAAO,QAAQ,IAAI,YAAYC,MAAK,KAAKC,IAAG,QAAQ,GAAG,MAAM;AACnE,SAAOD,MAAK,KAAK,MAAM,QAAQ;AACjC;AAUA,SAAS,sBAAsB;AAC7B,QAAM,aAAa,QAAQ,IAAI,mBAAmBA,MAAK,KAAKC,IAAG,QAAQ,GAAG,SAAS;AACnF,SAAOD,MAAK,KAAK,YAAY,QAAQ;AACvC;AAGA,SAAS,iBAAiB,SAAS;AACjC,QAAM,QAAQ,QAAQ,MAAM,6BAA6B;AACzD,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,QAAM,MAAM,CAAC;AACb,aAAW,QAAQ,MAAM,CAAC,EAAE,MAAM,OAAO,GAAG;AAC1C,UAAM,KAAK,KAAK,MAAM,4BAA4B;AAClD,QAAI,CAAC,GAAI;AACT,UAAM,QAAQ,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,gBAAgB,EAAE;AACrD,QAAI,UAAU,GAAI,KAAI,GAAG,CAAC,CAAC,IAAI;AAAA,EACjC;AACA,SAAO;AACT;AAGA,eAAe,kBAAkB,KAAK,KAAK;AACzC,MAAI;AACJ,MAAI;AACF,cAAU,MAAME,SAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,EACtD,QAAQ;AACN;AAAA,EACF;AACA,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,UAAM,WAAWF,MAAK,KAAK,KAAK,MAAM,IAAI;AAC1C,QAAI;AACJ,QAAI;AACF,gBAAU,MAAMG,UAASH,MAAK,KAAK,UAAU,UAAU,GAAG,MAAM;AAAA,IAClE,QAAQ;AACN;AAAA,IACF;AACA,UAAM,OAAO,iBAAiB,OAAO;AAErC,QAAI,IAAI,KAAK,QAAQ,MAAM,MAAM;AAAA,MAC/B,MAAM,KAAK,QAAQ,MAAM;AAAA,MACzB,aAAa,KAAK,eAAe;AAAA,MACjC,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACF;AAWA,eAAe,WAAW,KAAK;AAC7B,QAAM,MAAM,oBAAI,IAAI;AACpB,QAAM,kBAAkB,KAAK,oBAAoB,CAAC;AAClD,QAAM,kBAAkB,KAAK,cAAc,CAAC;AAC5C,MAAI,OAAO,QAAQ,YAAY,QAAQ,IAAI;AACzC,UAAM,kBAAkB,KAAKA,MAAK,KAAK,KAAK,WAAW,QAAQ,CAAC;AAChE,UAAM,kBAAkB,KAAKA,MAAK,KAAK,KAAK,QAAQ,QAAQ,CAAC;AAAA,EAC/D;AACA,SAAO,CAAC,GAAG,IAAI,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACtE;AAMO,SAAS,MAAM,KAAK;AACzB,MAAI,OAAO,MAAM;AACf,UAAM,UAAU,OAAO,KAAK,QAAQ;AAClC,UAAI;AAEF,cAAM,MAAM,IAAI,QAAQ,SAAY,IAAI,IAAI,IAAI,KAAK,YAAY,EAAE,aAAa,IAAI,KAAK,KAAK,SAAY;AAC1G,cAAM,SAAS,MAAM,WAAW,GAAG;AACnC,YAAI,UAAU,KAAK,EAAE,gBAAgB,kCAAkC,CAAC;AACxE,YAAI,IAAI,KAAK,UAAU,EAAE,IAAI,MAAM,UAAU,MAAM,OAAO,CAAC,CAAC;AAAA,MAC9D,SAAS,OAAO;AACd,YAAI,UAAU,KAAK,EAAE,gBAAgB,kCAAkC,CAAC;AACxE,YAAI,IAAI,KAAK,UAAU,EAAE,IAAI,OAAO,OAAO,OAAO,OAAO,WAAW,KAAK,EAAE,CAAC,CAAC;AAAA,MAC/E;AAAA,IACF;AACA,WAAO,IAAI,UAAU,SAAS,EAAE,MAAM,UAAU,MAAM,qBAAqB,QAAQ,CAAC;AAAA,EACtF,GAAG,0BAA0B;AAE7B,MAAI,OAAO,MAAM,IAAI,aAAa,QAAQ;AAAA,IACxC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,EACR,CAAC,GAAG,kCAAkC;AAMtC,MAAI,OAAO,MAAM;AACf,uBAAmB,EAAE,KAAK,CAAC,WAAW;AACpC,UAAI,OAAO,MAAM,SAAS,GAAG;AAC3B,gBAAQ,IAAI,6CAA6C,KAAK,UAAU,MAAM,CAAC;AAAA,MACjF;AAAA,IACF,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,cAAQ,KAAK,6CAA6C,KAAK;AAAA,IACjE,CAAC;AACD,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB,GAAG,4CAA4C;AACjD;",
|
|
6
6
|
"names": ["readFile", "readdir", "os", "path", "path", "os", "readdir", "readFile"]
|
|
7
7
|
}
|
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.5.
|
|
4
|
+
"version": "0.5.8",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
"0.1.2-alpha.2": "compatible",
|
|
35
35
|
"0.1.2-alpha.3": "compatible",
|
|
36
36
|
"0.1.2-alpha.4": "compatible",
|
|
37
|
-
"0.1.2-alpha.5": "compatible"
|
|
37
|
+
"0.1.2-alpha.5": "compatible",
|
|
38
|
+
"0.1.2-rc.1": "compatible",
|
|
39
|
+
"0.1.5-rc.1": "compatible",
|
|
40
|
+
"0.1.5-rc.2": "compatible"
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
},
|
package/src/patch-ui-skill.js
CHANGED
|
@@ -50,18 +50,32 @@ export const PATCHES = [
|
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
id: 'fuzzy-candidates',
|
|
53
|
-
title: 'prefix matcher → fuzzy+pinyin matcher',
|
|
53
|
+
title: 'prefix/rank matcher → fuzzy+pinyin matcher',
|
|
54
54
|
isApplied(text) {
|
|
55
55
|
return text.includes(FUZZY_MARKER)
|
|
56
56
|
},
|
|
57
57
|
apply(text) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
// 官方实现随版本变过两次,两个形态都要认(2026-09-11 实测):
|
|
59
|
+
// 0.1.2-alpha.x : return skills.filter((skill) => skill.name.startsWith(query)).map(...)
|
|
60
|
+
// 0.1.5-rc.x : return (0, _xxx.rankByName)(skills, query).map(...) ← 改成 async candidates
|
|
61
|
+
// 每组捕获:$1 = 缩进,$2 = 官方那一段表达式(原样保留做 fallback)。
|
|
62
|
+
const ANCHORS = [
|
|
63
|
+
/(\t*)return (\(0, [\w.$]+\.rankByName\)\(skills, query\)|rankByName\(skills, query\))\.map\(\(skill\) => \(\{/,
|
|
64
|
+
/(\t*)return (skills\.filter\(\(skill\) => skill\.name\.startsWith\(query\)\))\.map\(\(skill\) => \(\{/,
|
|
65
|
+
]
|
|
66
|
+
for (const re of ANCHORS) {
|
|
67
|
+
const m = text.match(re)
|
|
68
|
+
if (!m) continue
|
|
69
|
+
const [, indent, official] = m
|
|
70
|
+
return text.replace(
|
|
71
|
+
re,
|
|
61
72
|
`${indent}// dsh-skill-picker patch: fuzzy+pinyin matcher (self-healed)\n` +
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
`${indent}const officialMatcher = ${official};\n` +
|
|
74
|
+
`${indent}const matcher = typeof window.${FUZZY_MARKER} === "function" ? window.${FUZZY_MARKER}(skills, query) : officialMatcher;\n` +
|
|
75
|
+
`${indent}return matcher.map((skill) => ({`,
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
return text
|
|
65
79
|
},
|
|
66
80
|
},
|
|
67
81
|
{
|