dsh-skill-picker 0.5.22 → 0.5.23
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 +2 -1
- package/lib/client.js +312 -2
- package/lib/client.js.map +2 -2
- package/lib/index.js +244 -18
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
- package/src/client/index.jsx +382 -2
- package/src/index.js +129 -2
- package/src/skill-ops.js +233 -0
package/lib/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/index.js", "../src/dir-entry.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 { mkdir, readFile, readdir, rename, writeFile } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport { isDirectoryEntry } from './dir-entry.js'\nimport { dshHome, healUiSkillPatches, revertUiSkillPatches } 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/** The YAML frontmatter block of a SKILL.md body, or undefined when absent. */\nfunction frontmatterBlock(content) {\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/)\n return match === null ? undefined : match[1]\n}\n\n/** Parse a SKILL.md frontmatter block into a key/value map (flat YAML subset). */\nfunction parseFrontmatter(content) {\n const block = frontmatterBlock(content)\n if (block === undefined) return {}\n const out = {}\n for (const line of block.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/**\n * Whether the frontmatter carries `key` at all, whatever its value.\n * `parseFrontmatter` drops empty values, so presence checks that must also\n * see a bare `key:` line go through here instead.\n */\nfunction hasFrontmatterKey(content, key) {\n const block = frontmatterBlock(content)\n if (block === undefined) return false\n return block.split(/\\r?\\n/).some((line) => {\n const kv = line.match(/^([A-Za-z0-9_-]+):\\s*(.*)$/)\n return kv !== null && kv[1] === key\n })\n}\n\n/**\n * Legacy invocation spellings the official `dsh-skill-filesystem` provider\n * rejects outright (`rejectLegacyInvocationKey`) \u2014 a skill carrying one is\n * dropped from every surface, so the picker must drop it too.\n */\nconst LEGACY_INVOCATION_KEYS = ['disableModelInvocation', 'modelInvocable', 'userInvocable']\n\n/**\n * Read a frontmatter boolean with the official provider's semantics: YAML\n * booleans plus the case-insensitive `true`/`false`, `yes`/`no`, `on`/`off`\n * and `1`/`0` forms. An absent key yields undefined (\"surface permitted\").\n */\nfunction frontmatterBoolean(meta, key) {\n if (!Object.hasOwn(meta, key)) return undefined\n const value = meta[key]\n if (typeof value === 'boolean') return value\n if (value === 1 || value === '1') return true\n if (value === 0 || value === '0') return false\n if (typeof value === 'string') {\n switch (value.toLowerCase()) {\n case 'true':\n case 'yes':\n case 'on':\n return true\n case 'false':\n case 'no':\n case 'off':\n return false\n }\n }\n throw new TypeError(`frontmatter field \"${key}\" must be a boolean`)\n}\n\n/**\n * Whether a scanned skill may be offered by the picker, i.e. whether it is\n * user-invocable. Mirrors `parseInvocationPolicy` in\n * `@deepseek-ai/dsh-skill-filesystem`: `user-invocable: false` keeps the skill\n * out of human-facing commands, a rejected spelling or a legacy invocation key\n * drops it entirely.\n *\n * Why this exists (issue #10): the official `skills/list` Remote filters with\n * `isUserInvocable` before answering, but this host's own fallback scan read\n * name/description only \u2014 so the picker offered skills the official `/` menu\n * hides, and picking one inserted a `/name` gesture that `dsh-tool-skill` then\n * refused to load (a silent no-op).\n *\n * @param meta - parsed frontmatter map.\n * @param content - the raw SKILL.md body (legacy keys are detected on it).\n */\nfunction isUserInvocableSkill(meta, content) {\n for (const legacy of LEGACY_INVOCATION_KEYS) {\n if (hasFrontmatterKey(content, legacy)) return false\n }\n try {\n // Omitted \u2192 permitted; only an explicit `false` hides the skill.\n return frontmatterBoolean(meta, 'user-invocable') !== false\n } catch {\n return false\n }\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 // Links are followed (`isDirectoryEntry`), so a skill may live behind a\n // symlink/junction \u2014 e.g. `~/.agents/skills/neat` \u2192 another repo (#6).\n if (!(await isDirectoryEntry(dir, entry))) 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 // The picker is a human-facing surface, so honour the invocation policy:\n // `user-invocable: false` (and the legacy spellings the official provider\n // rejects) must never be offered. The official `skills/list` Remote filters\n // the same way \u2014 without this the fallback route listed skills that the\n // official `/` menu hides and that the `/name` gesture then refuses to load\n // (issue #10).\n if (!isUserInvocableSkill(meta, content)) continue\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 * @returns the deduplicated, name-sorted skill list.\n */\nexport async 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 * Decide what (if anything) the ui-skill self-heal patch should say on boot\n * (issue #8).\n *\n * `healUiSkillPatches()` reports **every target file it found**, whether or not\n * this boot changed it. Keying the log off `report.files.length` therefore\n * printed a report-shaped JSON line on *every* start \u2014 even when the patches\n * were already up to date \u2014 which reads like a warning and buries the lines\n * that really matter. Speak up only when there is something to say:\n *\n * - `noop` \u2192 an anchor did not match: the official implementation moved and\n * the enhancement is NOT applied. Always a warning (this is the\n * same class of silent failure as issue #7).\n * - `patched` \u2192 this boot really rewrote a file: one report line.\n * - `errors` \u2192 I/O failures: one warning (folded into the report line when a\n * patch also succeeded, so a single line carries both).\n * - none \u2192 silent: the install is already up to date.\n *\n * Set `DSH_SKILL_PICKER_LOG=debug` for the full report including `skipped`.\n *\n * Exported for tests; `io` is injectable so they can capture the output.\n *\n * @param {{files?: Array, errors?: string[]}} report - result of healUiSkillPatches().\n * @param {Pick<Console, 'log' | 'warn'>} [io] - sinks (defaults to the console).\n */\nexport function reportUiSkillPatches(report, io = console) {\n const files = report?.files ?? []\n const errors = report?.errors ?? []\n // An anchor miss means the enhancement silently did not apply \u2014 never bury it.\n const stale = files.filter((file) => file.noop.length > 0)\n if (stale.length > 0) {\n io.warn('[dsh-skill-picker] ui-skill patch: anchors not found, enhancement NOT applied '\n + '(the official implementation likely changed):', JSON.stringify(stale))\n }\n const changed = files.filter((file) => file.patched.length > 0)\n // Read per call so the switch also works when set by tests or a wrapper.\n const debug = process.env.DSH_SKILL_PICKER_LOG === 'debug'\n if (debug) {\n io.log('[dsh-skill-picker] ui-skill patch report (debug):', JSON.stringify({ files, errors }))\n } else if (changed.length > 0) {\n // Only the files this boot actually touched, plus every error.\n io.log('[dsh-skill-picker] ui-skill patch report:', JSON.stringify({ files: changed, errors }))\n } else if (errors.length > 0) {\n io.warn('[dsh-skill-picker] ui-skill patch errors:', JSON.stringify(errors))\n }\n}\n\n/**\n * Shared picker state: the pinned list and the usage history, stored ONCE per\n * DSH home instead of per browser origin.\n *\n * Why it exists: `localStorage` is scoped to an origin, so the desktop app\n * (`dsh-app://app`), the web UI (`http://127.0.0.1:3080`) and a phone all kept\n * their own copy \u2014 pin a skill on the desktop and the web UI never sees it.\n * Both halves now read and write this one file.\n *\n * @returns the absolute path of the shared state file.\n */\nfunction sharedStateFile() {\n return path.join(dshHome(), 'dsh-skill-picker-state.json')\n}\n\n/** Coerce anything into `{ pinned: string[], usage: {name: {count, lastUsed}} }`. */\nfunction normalizeSharedState(value) {\n const source = value !== null && typeof value === 'object' ? value : {}\n const pinned = Array.isArray(source.pinned)\n ? [...new Set(source.pinned.filter((name) => typeof name === 'string' && name !== ''))]\n : []\n const usage = {}\n const rawUsage = source.usage !== null && typeof source.usage === 'object' ? source.usage : {}\n for (const [name, entry] of Object.entries(rawUsage)) {\n if (typeof name !== 'string' || name === '') continue\n const record = entry !== null && typeof entry === 'object' ? entry : {}\n const count = Number.isFinite(record.count) ? Math.max(0, Math.trunc(record.count)) : 0\n const lastUsed = Number.isFinite(record.lastUsed) ? Math.max(0, Math.trunc(record.lastUsed)) : 0\n if (count === 0 && lastUsed === 0) continue\n usage[name] = { count, lastUsed }\n }\n return { pinned, usage }\n}\n\n/** Read the shared state, or null when the file does not exist yet. */\nasync function readSharedState() {\n try {\n return normalizeSharedState(JSON.parse(await readFile(sharedStateFile(), 'utf8')))\n } catch {\n return null\n }\n}\n\n/** Write the shared state atomically (temp + rename), creating the home if needed. */\nasync function writeSharedState(state) {\n const normal = normalizeSharedState(state)\n const file = sharedStateFile()\n await mkdir(path.dirname(file), { recursive: true })\n const tmp = `${file}.tmp`\n await writeFile(tmp, `${JSON.stringify(normal, null, 2)}\\n`, 'utf8')\n await rename(tmp, file)\n return normal\n}\n\n/**\n * First-run migration: union both ends instead of letting whichever browser\n * happened to write first win, so nobody silently loses their pins.\n */\nfunction mergeSharedState(a, b) {\n const left = normalizeSharedState(a)\n const right = normalizeSharedState(b)\n const pinned = [...left.pinned]\n for (const name of right.pinned) if (!pinned.includes(name)) pinned.push(name)\n const usage = { ...left.usage }\n for (const [name, entry] of Object.entries(right.usage)) {\n const previous = usage[name]\n usage[name] = previous === undefined\n ? entry\n : { count: Math.max(previous.count, entry.count), lastUsed: Math.max(previous.lastUsed, entry.lastUsed) }\n }\n return normalizeSharedState({ pinned, usage })\n}\n\n/** Read a JSON request body with a hard cap, so a bad client cannot grow memory. */\nasync function readJsonBody(req) {\n const chunks = []\n let size = 0\n for await (const chunk of req) {\n size += chunk.length\n if (size > 1_000_000) throw new Error('shared state body too large')\n chunks.push(chunk)\n }\n if (chunks.length === 0) return {}\n return JSON.parse(Buffer.concat(chunks).toString('utf8'))\n}\n\n/**\n * `GET /dsh-skill-picker/state` \u2192 `{ ok, state }` (`state: null` before the\n * first write). `PUT` with `{ pinned, usage }` \u2192 the stored state; `?migrate=1`\n * unions the incoming state with the stored one and returns the result.\n */\nasync function handleSharedState(req, res, url) {\n const json = (code, payload) => {\n res.writeHead(code, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify(payload))\n }\n try {\n if (req.method === 'PUT' || req.method === 'POST') {\n const incoming = normalizeSharedState(await readJsonBody(req))\n const migrate = url.searchParams.get('migrate') === '1'\n const stored = migrate ? await readSharedState() : null\n const next = stored === null ? await writeSharedState(incoming) : await writeSharedState(mergeSharedState(stored, incoming))\n json(200, { ok: true, state: next })\n return\n }\n json(200, { ok: true, state: await readSharedState() })\n } catch (error) {\n json(500, { ok: false, error: String(error?.message ?? error) })\n }\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 const url = new URL(req.url ?? '/', 'http://dsh')\n if (url.pathname === '/dsh-skill-picker/state') {\n await handleSharedState(req, res, url)\n return\n }\n // cwd query carries the active session's workspace root from the client.\n const cwd = url.searchParams.get('cwd') ?? 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 // The `/` enhancement no longer edits official files.\n //\n // Since v0.5.15 it takes over the LIVE trigger source at runtime, which does\n // everything the old file patch did (skill group first, fuzzy+pinyin matching,\n // usage tracking) without touching a single file. The patch is therefore\n // redundant \u2014 and a modified copy of an official package is confusing in its\n // own right: it made the desktop and web profiles disagree about one shared\n // file, and it is what made \"is this the original?\" impossible to answer.\n //\n // So the default is now to RESTORE our own patch (from the `.bak` it made).\n // Set DSH_SKILL_PICKER_FILE_PATCH=1 to fall back to the old behaviour on a\n // kernel whose trigger registry has no live sources to take over.\n ctx.effect(() => {\n const enabled = process.env.DSH_SKILL_PICKER_FILE_PATCH === '1'\n const task = enabled\n ? healUiSkillPatches().then((report) => reportUiSkillPatches(report))\n : revertUiSkillPatches().then((report) => {\n if (report.restored.length > 0) {\n console.log('[dsh-skill-picker] ui-skill patch retired; restored original file(s):',\n report.restored.join(', '))\n }\n if (report.errors.length > 0) {\n console.warn('[dsh-skill-picker] ui-skill restore errors:', JSON.stringify(report.errors))\n }\n })\n task.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 * Directory-entry helpers shared by the skill scan and the ui-skill patch scan.\n *\n * `readdir(dir, { withFileTypes: true })` describes the entry itself, not what\n * it points at: on Windows a symbolic link **and a junction** both come back as\n * `isDirectory() === false` / `isSymbolicLink() === true` (Node reports junction\n * through the same lstat semantics). A directory filter that only tests\n * `isDirectory()` therefore drops every linked skill silently \u2014 that is exactly\n * the `~/.agents/skills/neat` \u2192 repo case in issue #6.\n *\n * @module dsh-skill-picker/dir-entry\n */\n\nimport { stat } from 'node:fs/promises'\nimport path from 'node:path'\n\n/**\n * Decide whether a dirent points at a directory we may descend into.\n *\n * Real directories answer straight from the dirent (no extra syscall). Link\n * entries are resolved with `stat`, which follows the link, so junctions and\n * symlinks are treated exactly like the directories they point at. A broken\n * link (or a link into a permission wall) makes `stat` throw and is reported as\n * \"not a directory\" rather than taking the caller down.\n *\n * @param dir - the directory the entry was read from.\n * @param entry - the `Dirent` returned by `readdir`.\n * @returns whether the entry is, or points at, a directory.\n */\nexport async function isDirectoryEntry(dir, entry) {\n if (entry.isDirectory()) return true\n if (!entry.isSymbolicLink()) return false\n try {\n return (await stat(path.join(dir, entry.name))).isDirectory()\n } catch {\n return false\n }\n}\n", "/**\r\n * dsh-skill-picker \u2014 host-side self-healing patch for the official\r\n * `@deepseek-ai/dsh-client-ui-skill` package.\r\n *\r\n * The picker upgrades the official `/` completion in two ways that the\r\n * official package does not provide out of the box:\r\n *\r\n * 1. `order: 2 \u2192 -1` \u2014 the skill group sorts ABOVE the command group\r\n * (commands register with the default order 0; lower = higher in the\r\n * official menu).\r\n * 2. fuzzy+pinyin candidates \u2014 the official prefix-only matcher\r\n * (`skill.name.startsWith(query)`) is replaced by the picker's\r\n * `window.__dshSkillPickerFuzzy` matcher when the picker is mounted\r\n * (single source group, same list, upgraded matching).\r\n *\r\n * Every DSH boot this module scans every profile under `$DSH_HOME/profiles`\r\n * (default `~/.dsh/profiles`) for an installed ui-skill `lib/client.js` \u2014 the\r\n * shared core root (`profiles/node_modules/@deepseek-ai/\u2026`, used by global\r\n * installs), the user's local patched copy (`local/dsh-client-ui-skill`), or\r\n * the plain npm install (`node_modules/@deepseek-ai/dsh-client-ui-skill`) \u2014 and\r\n * re-applies both patches when DSH upgrades overwrote them. The original file\r\n * is backed up once as `<file>.dsh-skill-picker.bak` before the first write.\r\n * All operations are idempotent and never throw: a missing profile, package\r\n * or read failure is reported and skipped so a broken patch can never take\r\n * the host down.\r\n *\r\n * @module dsh-skill-picker/patch-ui-skill\r\n */\r\n\r\nimport { readFile, writeFile, copyFile, readdir, access, realpath, rename } from 'node:fs/promises'\r\nimport { createRequire } from 'node:module'\r\nimport os from 'node:os'\r\nimport path from 'node:path'\r\n\r\nimport { isDirectoryEntry } from './dir-entry.js'\r\n\r\n/** Marker that the candidates patch is already in place. */\r\nconst FUZZY_MARKER = '__dshSkillPickerFuzzy'\r\n\r\n/** Marker that the pick-tracking patch is already in place. */\r\nconst TRACK_MARKER = '__dshSkillPickerTrack'\r\n\r\n/** The self-healing patches, in application order. */\r\nexport const PATCHES = [\r\n {\r\n id: 'order',\r\n title: 'skill group order 2 \u2192 -1 (above commands)',\r\n isApplied(text) {\r\n return /name: \"skill\",[\\s\\S]*?order:\\s*-1,/.test(text)\r\n },\r\n apply(text) {\r\n return text.replace(/(name: \"skill\",\\s*order: )2,/, '$1-1,')\r\n },\r\n },\r\n {\r\n id: 'fuzzy-candidates',\r\n title: 'prefix/rank matcher \u2192 fuzzy+pinyin matcher',\r\n isApplied(text) {\r\n return text.includes(FUZZY_MARKER)\r\n },\r\n apply(text) {\r\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\r\n // 0.1.2-alpha.x : return skills.filter((skill) => skill.name.startsWith(query)).map(...)\r\n // 0.1.5-rc.x : return (0, _xxx.rankByName)(skills, query).map(...) \u2190 \u6539\u6210 async candidates\r\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\r\n const ANCHORS = [\r\n /(\\t*)return (\\(0, [\\w.$]+\\.rankByName\\)\\(skills, query\\)|rankByName\\(skills, query\\))\\.map\\(\\(skill\\) => \\(\\{/,\r\n /(\\t*)return (skills\\.filter\\(\\(skill\\) => skill\\.name\\.startsWith\\(query\\)\\))\\.map\\(\\(skill\\) => \\(\\{/,\r\n ]\r\n for (const re of ANCHORS) {\r\n const m = text.match(re)\r\n if (!m) continue\r\n const [, indent, official] = m\r\n return text.replace(\r\n re,\r\n `${indent}// dsh-skill-picker patch: fuzzy+pinyin matcher (self-healed)\\n` +\r\n `${indent}const officialMatcher = ${official};\\n` +\r\n `${indent}const matcher = typeof window.${FUZZY_MARKER} === \"function\" ? window.${FUZZY_MARKER}(skills, query) : officialMatcher;\\n` +\r\n `${indent}return matcher.map((skill) => ({`,\r\n )\r\n }\r\n return text\r\n },\r\n },\r\n {\r\n id: 'pick-tracking',\r\n title: 'record usage when picked from the official / menu',\r\n isApplied(text) {\r\n return text.includes(TRACK_MARKER)\r\n },\r\n apply(text) {\r\n return text.replace(\r\n /(\\t*)onPick\\(\\{ candidate \\}\\) \\{\\n(\\t*)return \\{ text: `\\/\\$\\{candidate\\.name\\} ` \\};\\n(\\t*)\\}/,\r\n (match, i1, i2, i3) =>\r\n `${i1}onPick({ candidate }) {\\n` +\r\n `${i2} // dsh-skill-picker patch: usage tracking (self-healed)\\n` +\r\n `${i2} try { window.${TRACK_MARKER}?.(candidate.name) } catch { /* best-effort */ }\\n` +\r\n `${i2} return { text: \\`/\\${candidate.name} \\` };\\n` +\r\n `${i3}}`,\r\n )\r\n },\r\n },\r\n]\r\n\r\n/** Resolve the DSH home directory, mirroring the official convention. */\r\nexport function dshHome() {\r\n return process.env.DSH_HOME ?? path.join(os.homedir(), '.dsh')\r\n}\r\n\r\n/** Subpath of the official skill UI inside any installation tree. */\r\nconst UI_SKILL_SUBPATH = ['node_modules', '@deepseek-ai', 'dsh-client-ui-skill', 'lib', 'client.js']\r\n\r\n/** Whether a path exists (any type). Never throws. */\r\nasync function pathExists(candidate) {\r\n try {\r\n await access(candidate)\r\n return true\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\n/**\r\n * Where the *running desktop build* keeps its own copy of the official skill UI.\r\n *\r\n * A packaged DSH Desktop ships that UI in one of two shapes (issue #9):\r\n *\r\n * - `resources/app/node_modules/\u2026` \u2014 an **unpacked** build. The app is the\r\n * installation-scoped owner, so this file is what gets served, and it is an\r\n * ordinary writable file.\r\n * - `resources/app.asar/dsh/node_modules/\u2026` \u2014 a **packed** build. Electron's\r\n * `fs` shim can *read* through the archive but cannot write into it, so this\r\n * path is evidence for diagnosis only and is never a patch target.\r\n *\r\n * `process.resourcesPath` only exists inside Electron; a plain `dsh` CLI or web\r\n * run returns empty lists and nothing changes for it.\r\n * @returns {{writable: string[], packed: string[]}} candidate paths.\r\n */\r\nexport function desktopUiSkillPaths() {\r\n const resources = process.resourcesPath\r\n if (typeof resources !== 'string' || resources === '') return { writable: [], packed: [] }\r\n return {\r\n writable: [path.join(resources, 'app', ...UI_SKILL_SUBPATH)],\r\n packed: [path.join(resources, 'app.asar', 'dsh', ...UI_SKILL_SUBPATH)],\r\n }\r\n}\r\n\r\n/**\r\n * Whether a profile manifest redirects the official skill UI to its own copy.\r\n *\r\n * This is the switch that decides which copy a desktop build serves: without a\r\n * declared dependency the profile resolves the package from the installation\r\n * (`resources/app` or `app.asar`), and every profile-local copy is dead weight.\r\n * @param profileDir - absolute profile directory.\r\n * @returns true when the manifest declares `@deepseek-ai/dsh-client-ui-skill`.\r\n */\r\nasync function profileRedirectsUiSkill(profileDir) {\r\n try {\r\n const manifest = JSON.parse(await readFile(path.join(profileDir, 'package.json'), 'utf8'))\r\n const declared = { ...manifest?.dependencies, ...manifest?.devDependencies }\r\n return typeof declared['@deepseek-ai/dsh-client-ui-skill'] === 'string'\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\n/**\r\n * Explain why the patch targets found (or not found) may not be what the host\r\n * actually serves, for a packed desktop build (issue #9).\r\n *\r\n * Silence here was the worst part of #9: patching a stale profile copy looks\r\n * exactly like success, so the `/` menu kept its official matcher with no\r\n * signal at all.\r\n * @returns a sentence to append to a warning, or '' when there is nothing to say.\r\n */\r\nasync function packedDesktopNote() {\r\n const { packed } = desktopUiSkillPaths()\r\n if (packed.length === 0 || !(await pathExists(packed[0]))) return ''\r\n const profilesDir = path.join(dshHome(), 'profiles')\r\n let redirected = false\r\n try {\r\n for (const entry of await readdir(profilesDir, { withFileTypes: true })) {\r\n if (entry.name === 'node_modules') continue\r\n if (await profileRedirectsUiSkill(path.join(profilesDir, entry.name))) {\r\n redirected = true\r\n break\r\n }\r\n }\r\n } catch {\r\n /* unreadable profile list: report the pessimistic case */\r\n }\r\n if (redirected) return ''\r\n return ' This desktop build keeps the official skill UI inside `app.asar`, which cannot be written in place,'\r\n + ' and no profile redirects the package to a local copy \u2014 so the served `/` menu keeps the official matcher.'\r\n + ' Install a profile-local override (`\"@deepseek-ai/dsh-client-ui-skill\": \"link:\u2026\"`) or run DSH from the CLI.'\r\n}\r\n\r\n/**\r\n * Enumerate every installed ui-skill `lib/client.js` across all profiles.\r\n *\r\n * Four layouts are covered (deduplicated by real path):\r\n * 1. the **shared core root** `profiles/node_modules/@deepseek-ai/\u2026` \u2014 used by\r\n * a global `npm i -g @deepseek-ai/dsh`, where the official package is NOT\r\n * below any single profile (issue #7 \u2014 missing this made the whole patch\r\n * silently no-op for those installs);\r\n * 2. the user's local patched copy (`profiles/<p>/local/dsh-client-ui-skill`)\r\n * \u2014 that is what a linked profile actually loads;\r\n * 3. the plain npm install inside a profile\r\n * (`profiles/<p>/node_modules/@deepseek-ai/\u2026`);\r\n * 4. the **running desktop build's own unpacked tree**\r\n * (`resources/app/node_modules/@deepseek-ai/\u2026`) \u2014 issue #9: a desktop\r\n * install owns the package at installation scope, so its copy is the one\r\n * served whenever no profile redirects the name.\r\n *\r\n * As a defensive extra, each profile is also asked through Node's own resolver\r\n * (`createRequire().resolve()`), so a layout we did not enumerate still works.\r\n * Never throws \u2014 a missing profiles dir yields [].\r\n * @returns {Promise<string[]>} candidate file paths.\r\n */\r\nexport async function uiSkillClientPaths() {\r\n const profilesDir = path.join(dshHome(), 'profiles')\r\n let profiles\r\n try {\r\n profiles = await readdir(profilesDir, { withFileTypes: true })\r\n } catch {\r\n // A desktop build can still be patchable through its own resource tree\r\n // even when $DSH_HOME/profiles is absent or unreadable.\r\n profiles = []\r\n }\r\n const seen = new Set()\r\n const found = []\r\n\r\n /** Add one candidate if it exists; deduplicate by real path. */\r\n const collect = async (candidate) => {\r\n try {\r\n await access(candidate)\r\n const real = await realpath(candidate)\r\n if (seen.has(real)) return\r\n seen.add(real)\r\n found.push(candidate)\r\n } catch {\r\n /* not present at this location */\r\n }\r\n }\r\n\r\n /** Ask Node's resolver where this profile would load the package from. */\r\n const collectByResolve = async (profileDir) => {\r\n try {\r\n const require = createRequire(path.join(profileDir, 'package.json'))\r\n await collect(require.resolve('@deepseek-ai/dsh-client-ui-skill/lib/client.js'))\r\n } catch {\r\n /* not resolvable from this profile */\r\n }\r\n }\r\n\r\n // 1. shared core root (global installs) \u2014 see the doc comment above.\r\n await collect(path.join(profilesDir, 'node_modules', '@deepseek-ai', 'dsh-client-ui-skill', 'lib', 'client.js'))\r\n\r\n // 4. the running desktop build's own unpacked tree (issue #9): this is the\r\n // copy a packaged Electron app serves when no profile redirects the name.\r\n for (const candidate of desktopUiSkillPaths().writable) await collect(candidate)\r\n\r\n for (const entry of profiles) {\r\n // `node_modules` is a sibling of the profiles, not a profile itself.\r\n if (entry.name === 'node_modules') continue\r\n // A profile directory may itself be reached through a link; follow it\r\n // instead of relying on the lstat-based dirent (see dir-entry.js).\r\n if (!(await isDirectoryEntry(profilesDir, entry))) continue\r\n // 2. the user's local patched copy (what a linked profile actually loads).\r\n await collect(path.join(profilesDir, entry.name, 'local', 'dsh-client-ui-skill', 'lib', 'client.js'))\r\n // 3. the plain npm install inside the profile.\r\n await collect(path.join(profilesDir, entry.name, 'node_modules', '@deepseek-ai', 'dsh-client-ui-skill', 'lib', 'client.js'))\r\n // Defensive: whatever Node itself would resolve (dupes removed above).\r\n await collectByResolve(path.join(profilesDir, entry.name))\r\n }\r\n return found\r\n}\r\n\r\n/**\r\n * Apply both patches to one ui-skill client.js. Idempotent: already-applied\r\n * patches are reported as skipped; the original file is backed up once before\r\n * the first modification. Never throws for a patch that does not match (it is\r\n * reported as `noop`), only for actual I/O failures.\r\n * @param {string} file - absolute path to the target client.js.\r\n * @returns {Promise<{file: string, patched: string[], skipped: string[], noop: string[]}>}\r\n */\r\nexport async function patchUiSkillFile(file) {\r\n const text = await readFile(file, 'utf8')\r\n const result = { file, patched: [], skipped: [], noop: [] }\r\n let next = text\r\n for (const patch of PATCHES) {\r\n if (patch.isApplied(next)) {\r\n result.skipped.push(patch.id)\r\n continue\r\n }\r\n const candidate = patch.apply(next)\r\n if (candidate === next) {\r\n result.noop.push(patch.id)\r\n continue\r\n }\r\n next = candidate\r\n result.patched.push(patch.id)\r\n }\r\n if (result.patched.length === 0) return result\r\n const backup = `${file}.dsh-skill-picker.bak`\r\n try {\r\n await access(backup)\r\n } catch {\r\n await copyFile(file, backup)\r\n }\r\n // Write through a temp file + rename instead of an in-place `writeFile`:\r\n // - pnpm installs are HARDLINKED to a shared content-addressable store, so\r\n // writing in place would mutate that shared inode and silently change\r\n // every other project using the same package version. rename() swaps the\r\n // directory entry instead, leaving the shared inode untouched.\r\n // - rename is also atomic, so an interrupted boot cannot leave a torn file.\r\n const tmp = `${file}.dsh-skill-picker.tmp`\r\n await writeFile(tmp, next, 'utf8')\r\n await rename(tmp, file)\r\n return result\r\n}\r\n\r\n/**\r\n * Self-heal entry point: scan all profiles, patch every ui-skill copy found,\r\n * and return one combined report. Never throws \u2014 each failure is collected\r\n * into `errors` so the host boot is never taken down by a broken patch.\r\n * @returns {Promise<{files: Array, errors: string[]}>}\r\n */\r\n/**\r\n * Undo our own file patch: restore every `<file>.dsh-skill-picker.bak` we made.\r\n *\r\n * The `/` enhancement has not needed this patch since v0.5.15, which takes over\r\n * the LIVE trigger source at runtime instead. Keeping the patch meant shipping a\r\n * modified copy of an official package \u2014 confusing on its own, and it made the\r\n * desktop and web profiles disagree about the same file.\r\n *\r\n * Restores only a file that still carries OUR marker, so a file edited by\r\n * something else since is left alone. Idempotent: after one pass there is\r\n * nothing left to restore.\r\n *\r\n * @returns {Promise<{restored: string[], errors: string[]}>}\r\n */\r\nexport async function revertUiSkillPatches() {\r\n const files = await uiSkillClientPaths()\r\n const restored = []\r\n const errors = []\r\n for (const file of files) {\r\n const backup = `${file}.dsh-skill-picker.bak`\r\n try {\r\n let text\r\n try {\r\n text = await readFile(file, 'utf8')\r\n } catch {\r\n continue\r\n }\r\n if (!PATCHES.some((patch) => patch.isApplied(text))) continue\r\n let original\r\n try {\r\n original = await readFile(backup, 'utf8')\r\n } catch {\r\n continue\r\n }\r\n // Same temp+rename dance as the patch itself: never write a hardlinked\r\n // package file in place (that would mutate the shared pnpm store).\r\n const tmp = `${file}.dsh-skill-picker.tmp`\r\n await writeFile(tmp, original, 'utf8')\r\n await rename(tmp, file)\r\n restored.push(file)\r\n } catch (error) {\r\n errors.push(`${file}: ${String(error?.message ?? error)}`)\r\n }\r\n }\r\n return { restored, errors }\r\n}\r\n\r\nexport async function healUiSkillPatches() {\r\n const files = await uiSkillClientPaths()\r\n const filesReport = []\r\n const errors = []\r\n for (const file of files) {\r\n try {\r\n filesReport.push(await patchUiSkillFile(file))\r\n } catch (error) {\r\n errors.push(`${file}: ${String(error?.message ?? error)}`)\r\n }\r\n }\r\n // Loud on the empty case. Silence here was the most confusing part of\r\n // issue #7: \"found 0 targets\" and \"everything already applied\" used to print\r\n // identically, so nobody could tell the patch had never run at all.\r\n const note = await packedDesktopNote()\r\n if (files.length === 0) {\r\n console.warn('[dsh-skill-picker] ui-skill patch: 0 target client.js found under '\r\n + `${path.join(dshHome(), 'profiles')} \u2014 fuzzy+pinyin matching will NOT be applied.`\r\n + `${note} See https://github.com/a735624258/dsh-skill-picker/issues/14`)\r\n } else if (note !== '') {\r\n // Targets existed, so the old code stayed silent \u2014 but none of them is the\r\n // copy the host serves. That is issue #9's silent failure: a \"successful\"\r\n // patch run that changes nothing the user can see.\r\n console.warn(`[dsh-skill-picker] ui-skill patch: patched ${files.length} profile copy/copies,`\r\n + ` but none of them is the one this host serves.${note}`)\r\n }\r\n return { files: filesReport, errors }\r\n}\r\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["readFile", "readdir", "rename", "writeFile", "os", "path", "path", "path", "require", "path", "os", "readdir", "readFile", "writeFile", "rename"]
|
|
3
|
+
"sources": ["../src/index.js", "../src/dir-entry.js", "../src/patch-ui-skill.js", "../src/skill-ops.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 { mkdir, readFile, readdir, rename, writeFile } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport { isDirectoryEntry } from './dir-entry.js'\nimport { dshHome, healUiSkillPatches, revertUiSkillPatches } from './patch-ui-skill.js'\nimport {\n CLIENT_HEADER,\n DISABLED_ENTRY,\n ENTRY,\n listSkillBackups,\n resolveSkillPath,\n restoreSkillBackup,\n revealSkill,\n setSkillDisabled,\n uninstallSkill,\n} from './skill-ops.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/** The YAML frontmatter block of a SKILL.md body, or undefined when absent. */\nfunction frontmatterBlock(content) {\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/)\n return match === null ? undefined : match[1]\n}\n\n/** Parse a SKILL.md frontmatter block into a key/value map (flat YAML subset). */\nfunction parseFrontmatter(content) {\n const block = frontmatterBlock(content)\n if (block === undefined) return {}\n const out = {}\n for (const line of block.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/**\n * Whether the frontmatter carries `key` at all, whatever its value.\n * `parseFrontmatter` drops empty values, so presence checks that must also\n * see a bare `key:` line go through here instead.\n */\nfunction hasFrontmatterKey(content, key) {\n const block = frontmatterBlock(content)\n if (block === undefined) return false\n return block.split(/\\r?\\n/).some((line) => {\n const kv = line.match(/^([A-Za-z0-9_-]+):\\s*(.*)$/)\n return kv !== null && kv[1] === key\n })\n}\n\n/**\n * Legacy invocation spellings the official `dsh-skill-filesystem` provider\n * rejects outright (`rejectLegacyInvocationKey`) \u2014 a skill carrying one is\n * dropped from every surface, so the picker must drop it too.\n */\nconst LEGACY_INVOCATION_KEYS = ['disableModelInvocation', 'modelInvocable', 'userInvocable']\n\n/**\n * Read a frontmatter boolean with the official provider's semantics: YAML\n * booleans plus the case-insensitive `true`/`false`, `yes`/`no`, `on`/`off`\n * and `1`/`0` forms. An absent key yields undefined (\"surface permitted\").\n */\nfunction frontmatterBoolean(meta, key) {\n if (!Object.hasOwn(meta, key)) return undefined\n const value = meta[key]\n if (typeof value === 'boolean') return value\n if (value === 1 || value === '1') return true\n if (value === 0 || value === '0') return false\n if (typeof value === 'string') {\n switch (value.toLowerCase()) {\n case 'true':\n case 'yes':\n case 'on':\n return true\n case 'false':\n case 'no':\n case 'off':\n return false\n }\n }\n throw new TypeError(`frontmatter field \"${key}\" must be a boolean`)\n}\n\n/**\n * Whether a scanned skill may be offered by the picker, i.e. whether it is\n * user-invocable. Mirrors `parseInvocationPolicy` in\n * `@deepseek-ai/dsh-skill-filesystem`: `user-invocable: false` keeps the skill\n * out of human-facing commands, a rejected spelling or a legacy invocation key\n * drops it entirely.\n *\n * Why this exists (issue #10): the official `skills/list` Remote filters with\n * `isUserInvocable` before answering, but this host's own fallback scan read\n * name/description only \u2014 so the picker offered skills the official `/` menu\n * hides, and picking one inserted a `/name` gesture that `dsh-tool-skill` then\n * refused to load (a silent no-op).\n *\n * @param meta - parsed frontmatter map.\n * @param content - the raw SKILL.md body (legacy keys are detected on it).\n */\nfunction isUserInvocableSkill(meta, content) {\n for (const legacy of LEGACY_INVOCATION_KEYS) {\n if (hasFrontmatterKey(content, legacy)) return false\n }\n try {\n // Omitted \u2192 permitted; only an explicit `false` hides the skill.\n return frontmatterBoolean(meta, 'user-invocable') !== false\n } catch {\n return false\n }\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 // Links are followed (`isDirectoryEntry`), so a skill may live behind a\n // symlink/junction \u2014 e.g. `~/.agents/skills/neat` \u2192 another repo (#6).\n if (!(await isDirectoryEntry(dir, entry))) continue\n const skillDir = path.join(dir, entry.name)\n let content\n let entryFile = ENTRY\n let disabled = false\n try {\n content = await readFile(path.join(skillDir, ENTRY), 'utf8')\n } catch {\n // A disabled skill keeps its entry under a different name, which the\n // official provider ignores entirely. The panel still has to list it, or\n // there would be no way to switch it back on.\n try {\n content = await readFile(path.join(skillDir, DISABLED_ENTRY), 'utf8')\n entryFile = DISABLED_ENTRY\n disabled = true\n } catch {\n continue\n }\n }\n const meta = parseFrontmatter(content)\n // The picker is a human-facing surface, so honour the invocation policy:\n // `user-invocable: false` (and the legacy spellings the official provider\n // rejects) must never be offered. The official `skills/list` Remote filters\n // the same way \u2014 without this the fallback route listed skills that the\n // official `/` menu hides and that the `/name` gesture then refuses to load\n // (issue #10).\n if (!isUserInvocableSkill(meta, content)) continue\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 entry: entryFile,\n disabled,\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 * @returns the deduplicated, name-sorted skill list.\n */\nexport async 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 * Decide what (if anything) the ui-skill self-heal patch should say on boot\n * (issue #8).\n *\n * `healUiSkillPatches()` reports **every target file it found**, whether or not\n * this boot changed it. Keying the log off `report.files.length` therefore\n * printed a report-shaped JSON line on *every* start \u2014 even when the patches\n * were already up to date \u2014 which reads like a warning and buries the lines\n * that really matter. Speak up only when there is something to say:\n *\n * - `noop` \u2192 an anchor did not match: the official implementation moved and\n * the enhancement is NOT applied. Always a warning (this is the\n * same class of silent failure as issue #7).\n * - `patched` \u2192 this boot really rewrote a file: one report line.\n * - `errors` \u2192 I/O failures: one warning (folded into the report line when a\n * patch also succeeded, so a single line carries both).\n * - none \u2192 silent: the install is already up to date.\n *\n * Set `DSH_SKILL_PICKER_LOG=debug` for the full report including `skipped`.\n *\n * Exported for tests; `io` is injectable so they can capture the output.\n *\n * @param {{files?: Array, errors?: string[]}} report - result of healUiSkillPatches().\n * @param {Pick<Console, 'log' | 'warn'>} [io] - sinks (defaults to the console).\n */\nexport function reportUiSkillPatches(report, io = console) {\n const files = report?.files ?? []\n const errors = report?.errors ?? []\n // An anchor miss means the enhancement silently did not apply \u2014 never bury it.\n const stale = files.filter((file) => file.noop.length > 0)\n if (stale.length > 0) {\n io.warn('[dsh-skill-picker] ui-skill patch: anchors not found, enhancement NOT applied '\n + '(the official implementation likely changed):', JSON.stringify(stale))\n }\n const changed = files.filter((file) => file.patched.length > 0)\n // Read per call so the switch also works when set by tests or a wrapper.\n const debug = process.env.DSH_SKILL_PICKER_LOG === 'debug'\n if (debug) {\n io.log('[dsh-skill-picker] ui-skill patch report (debug):', JSON.stringify({ files, errors }))\n } else if (changed.length > 0) {\n // Only the files this boot actually touched, plus every error.\n io.log('[dsh-skill-picker] ui-skill patch report:', JSON.stringify({ files: changed, errors }))\n } else if (errors.length > 0) {\n io.warn('[dsh-skill-picker] ui-skill patch errors:', JSON.stringify(errors))\n }\n}\n\n/**\n * Shared picker state: the pinned list and the usage history, stored ONCE per\n * DSH home instead of per browser origin.\n *\n * Why it exists: `localStorage` is scoped to an origin, so the desktop app\n * (`dsh-app://app`), the web UI (`http://127.0.0.1:3080`) and a phone all kept\n * their own copy \u2014 pin a skill on the desktop and the web UI never sees it.\n * Both halves now read and write this one file.\n *\n * @returns the absolute path of the shared state file.\n */\nfunction sharedStateFile() {\n return path.join(dshHome(), 'dsh-skill-picker-state.json')\n}\n\n/** Coerce anything into `{ pinned: string[], usage: {name: {count, lastUsed}} }`. */\nfunction normalizeSharedState(value) {\n const source = value !== null && typeof value === 'object' ? value : {}\n const pinned = Array.isArray(source.pinned)\n ? [...new Set(source.pinned.filter((name) => typeof name === 'string' && name !== ''))]\n : []\n const usage = {}\n const rawUsage = source.usage !== null && typeof source.usage === 'object' ? source.usage : {}\n for (const [name, entry] of Object.entries(rawUsage)) {\n if (typeof name !== 'string' || name === '') continue\n const record = entry !== null && typeof entry === 'object' ? entry : {}\n const count = Number.isFinite(record.count) ? Math.max(0, Math.trunc(record.count)) : 0\n const lastUsed = Number.isFinite(record.lastUsed) ? Math.max(0, Math.trunc(record.lastUsed)) : 0\n if (count === 0 && lastUsed === 0) continue\n usage[name] = { count, lastUsed }\n }\n return { pinned, usage }\n}\n\n/** Read the shared state, or null when the file does not exist yet. */\nasync function readSharedState() {\n try {\n return normalizeSharedState(JSON.parse(await readFile(sharedStateFile(), 'utf8')))\n } catch {\n return null\n }\n}\n\n/** Write the shared state atomically (temp + rename), creating the home if needed. */\nasync function writeSharedState(state) {\n const normal = normalizeSharedState(state)\n const file = sharedStateFile()\n await mkdir(path.dirname(file), { recursive: true })\n const tmp = `${file}.tmp`\n await writeFile(tmp, `${JSON.stringify(normal, null, 2)}\\n`, 'utf8')\n await rename(tmp, file)\n return normal\n}\n\n/**\n * First-run migration: union both ends instead of letting whichever browser\n * happened to write first win, so nobody silently loses their pins.\n */\nfunction mergeSharedState(a, b) {\n const left = normalizeSharedState(a)\n const right = normalizeSharedState(b)\n const pinned = [...left.pinned]\n for (const name of right.pinned) if (!pinned.includes(name)) pinned.push(name)\n const usage = { ...left.usage }\n for (const [name, entry] of Object.entries(right.usage)) {\n const previous = usage[name]\n usage[name] = previous === undefined\n ? entry\n : { count: Math.max(previous.count, entry.count), lastUsed: Math.max(previous.lastUsed, entry.lastUsed) }\n }\n return normalizeSharedState({ pinned, usage })\n}\n\n/** Read a JSON request body with a hard cap, so a bad client cannot grow memory. */\nasync function readJsonBody(req) {\n const chunks = []\n let size = 0\n for await (const chunk of req) {\n size += chunk.length\n if (size > 1_000_000) throw new Error('shared state body too large')\n chunks.push(chunk)\n }\n if (chunks.length === 0) return {}\n return JSON.parse(Buffer.concat(chunks).toString('utf8'))\n}\n\n/**\n * `GET /dsh-skill-picker/state` \u2192 `{ ok, state }` (`state: null` before the\n * first write). `PUT` with `{ pinned, usage }` \u2192 the stored state; `?migrate=1`\n * unions the incoming state with the stored one and returns the result.\n */\nasync function handleSharedState(req, res, url) {\n const json = (code, payload) => {\n res.writeHead(code, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify(payload))\n }\n try {\n if (req.method === 'PUT' || req.method === 'POST') {\n const incoming = normalizeSharedState(await readJsonBody(req))\n const migrate = url.searchParams.get('migrate') === '1'\n const stored = migrate ? await readSharedState() : null\n const next = stored === null ? await writeSharedState(incoming) : await writeSharedState(mergeSharedState(stored, incoming))\n json(200, { ok: true, state: next })\n return\n }\n json(200, { ok: true, state: await readSharedState() })\n } catch (error) {\n json(500, { ok: false, error: String(error?.message ?? error) })\n }\n}\n\n/**\n * Every root a skill may live in, in the order the scanner reads them.\n * Single source of truth: `skill-ops` must not re-derive these.\n */\nfunction allSkillRoots(cwd) {\n const roots = [userAgentsSkillsDir(), userSkillsDir()]\n if (typeof cwd === 'string' && cwd !== '') {\n roots.push(path.join(cwd, '.agents', 'skills'), path.join(cwd, '.dsh', 'skills'))\n }\n return roots\n}\n\n/**\n * `POST /dsh-skill-picker/skill` \u2014 disable / enable / reveal / uninstall /\n * restore a skill, and list the uninstall backups.\n *\n * Three guards, in order:\n *\n * 1. The request must carry `x-dsh-skill-picker: 1`. Plugin routes bypass the\n * web server's auth gate (they are reachable without a cookie), and these\n * actions rename and move files. A cross-site `fetch` cannot set a custom\n * header without a CORS preflight the host does not answer, so this header\n * is what keeps an arbitrary web page out.\n * 2. The target path must resolve inside a known skill root. Anything else \u2014\n * the root itself, a file nested inside a skill, `../..`, or an absolute\n * path elsewhere \u2014 is refused before any filesystem call.\n * 3. Nothing is ever deleted. \"Uninstall\" moves the skill into\n * `$DSH_HOME/skill-backups/` beside a manifest, and `restore` moves it back.\n */\nasync function handleSkillOp(req, res, url) {\n const json = (code, payload) => {\n res.writeHead(code, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify(payload))\n }\n if (req.method !== 'POST') {\n json(405, { ok: false, error: 'POST only' })\n return\n }\n if (req.headers[CLIENT_HEADER] !== '1') {\n json(403, { ok: false, error: 'missing client header' })\n return\n }\n try {\n const body = await readJsonBody(req)\n const cwd = url.searchParams.get('cwd') ?? undefined\n const roots = allSkillRoots(cwd)\n const action = String(body?.action ?? '')\n\n if (action === 'backups') {\n json(200, { ok: true, backups: await listSkillBackups(roots) })\n return\n }\n if (action === 'restore') {\n json(200, { ok: true, ...(await restoreSkillBackup(body?.id, roots)) })\n return\n }\n\n // Resolve by NAME through the host's own scan, never by a path the browser\n // supplied: the panel's primary source is the official `skills/list` RPC,\n // whose DTO is not guaranteed to carry a filesystem path \u2014 and a name the\n // host looks up itself cannot point outside the skill roots at all.\n const name = String(body?.name ?? '')\n if (name === '') {\n json(400, { ok: false, error: 'name is required' })\n return\n }\n const known = (await scanSkills(cwd)).find((skill) => skill.name === name)\n if (known === undefined) {\n json(404, { ok: false, error: `no skill named \"${name}\"` })\n return\n }\n const target = resolveSkillPath(known.path, roots)\n if (target === undefined) {\n json(403, { ok: false, error: 'that skill lives outside the skill roots' })\n return\n }\n\n if (action === 'disable') {\n json(200, { ok: true, ...(await setSkillDisabled(target, true)) })\n return\n }\n if (action === 'enable') {\n json(200, { ok: true, ...(await setSkillDisabled(target, false)) })\n return\n }\n if (action === 'reveal') {\n json(200, { ok: true, revealed: revealSkill(target) })\n return\n }\n if (action === 'uninstall') {\n json(200, { ok: true, ...(await uninstallSkill(target)) })\n return\n }\n json(400, { ok: false, error: `unknown action: ${action}` })\n } catch (error) {\n json(500, { ok: false, error: String(error?.message ?? error) })\n }\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 const url = new URL(req.url ?? '/', 'http://dsh')\n if (url.pathname === '/dsh-skill-picker/state') {\n await handleSharedState(req, res, url)\n return\n }\n if (url.pathname === '/dsh-skill-picker/skill') {\n await handleSkillOp(req, res, url)\n return\n }\n // cwd query carries the active session's workspace root from the client.\n const cwd = url.searchParams.get('cwd') ?? 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 // The `/` enhancement no longer edits official files.\n //\n // Since v0.5.15 it takes over the LIVE trigger source at runtime, which does\n // everything the old file patch did (skill group first, fuzzy+pinyin matching,\n // usage tracking) without touching a single file. The patch is therefore\n // redundant \u2014 and a modified copy of an official package is confusing in its\n // own right: it made the desktop and web profiles disagree about one shared\n // file, and it is what made \"is this the original?\" impossible to answer.\n //\n // So the default is now to RESTORE our own patch (from the `.bak` it made).\n // Set DSH_SKILL_PICKER_FILE_PATCH=1 to fall back to the old behaviour on a\n // kernel whose trigger registry has no live sources to take over.\n ctx.effect(() => {\n const enabled = process.env.DSH_SKILL_PICKER_FILE_PATCH === '1'\n const task = enabled\n ? healUiSkillPatches().then((report) => reportUiSkillPatches(report))\n : revertUiSkillPatches().then((report) => {\n if (report.restored.length > 0) {\n console.log('[dsh-skill-picker] ui-skill patch retired; restored original file(s):',\n report.restored.join(', '))\n }\n if (report.errors.length > 0) {\n console.warn('[dsh-skill-picker] ui-skill restore errors:', JSON.stringify(report.errors))\n }\n })\n task.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 * Directory-entry helpers shared by the skill scan and the ui-skill patch scan.\n *\n * `readdir(dir, { withFileTypes: true })` describes the entry itself, not what\n * it points at: on Windows a symbolic link **and a junction** both come back as\n * `isDirectory() === false` / `isSymbolicLink() === true` (Node reports junction\n * through the same lstat semantics). A directory filter that only tests\n * `isDirectory()` therefore drops every linked skill silently \u2014 that is exactly\n * the `~/.agents/skills/neat` \u2192 repo case in issue #6.\n *\n * @module dsh-skill-picker/dir-entry\n */\n\nimport { stat } from 'node:fs/promises'\nimport path from 'node:path'\n\n/**\n * Decide whether a dirent points at a directory we may descend into.\n *\n * Real directories answer straight from the dirent (no extra syscall). Link\n * entries are resolved with `stat`, which follows the link, so junctions and\n * symlinks are treated exactly like the directories they point at. A broken\n * link (or a link into a permission wall) makes `stat` throw and is reported as\n * \"not a directory\" rather than taking the caller down.\n *\n * @param dir - the directory the entry was read from.\n * @param entry - the `Dirent` returned by `readdir`.\n * @returns whether the entry is, or points at, a directory.\n */\nexport async function isDirectoryEntry(dir, entry) {\n if (entry.isDirectory()) return true\n if (!entry.isSymbolicLink()) return false\n try {\n return (await stat(path.join(dir, entry.name))).isDirectory()\n } catch {\n return false\n }\n}\n", "/**\r\n * dsh-skill-picker \u2014 host-side self-healing patch for the official\r\n * `@deepseek-ai/dsh-client-ui-skill` package.\r\n *\r\n * The picker upgrades the official `/` completion in two ways that the\r\n * official package does not provide out of the box:\r\n *\r\n * 1. `order: 2 \u2192 -1` \u2014 the skill group sorts ABOVE the command group\r\n * (commands register with the default order 0; lower = higher in the\r\n * official menu).\r\n * 2. fuzzy+pinyin candidates \u2014 the official prefix-only matcher\r\n * (`skill.name.startsWith(query)`) is replaced by the picker's\r\n * `window.__dshSkillPickerFuzzy` matcher when the picker is mounted\r\n * (single source group, same list, upgraded matching).\r\n *\r\n * Every DSH boot this module scans every profile under `$DSH_HOME/profiles`\r\n * (default `~/.dsh/profiles`) for an installed ui-skill `lib/client.js` \u2014 the\r\n * shared core root (`profiles/node_modules/@deepseek-ai/\u2026`, used by global\r\n * installs), the user's local patched copy (`local/dsh-client-ui-skill`), or\r\n * the plain npm install (`node_modules/@deepseek-ai/dsh-client-ui-skill`) \u2014 and\r\n * re-applies both patches when DSH upgrades overwrote them. The original file\r\n * is backed up once as `<file>.dsh-skill-picker.bak` before the first write.\r\n * All operations are idempotent and never throw: a missing profile, package\r\n * or read failure is reported and skipped so a broken patch can never take\r\n * the host down.\r\n *\r\n * @module dsh-skill-picker/patch-ui-skill\r\n */\r\n\r\nimport { readFile, writeFile, copyFile, readdir, access, realpath, rename } from 'node:fs/promises'\r\nimport { createRequire } from 'node:module'\r\nimport os from 'node:os'\r\nimport path from 'node:path'\r\n\r\nimport { isDirectoryEntry } from './dir-entry.js'\r\n\r\n/** Marker that the candidates patch is already in place. */\r\nconst FUZZY_MARKER = '__dshSkillPickerFuzzy'\r\n\r\n/** Marker that the pick-tracking patch is already in place. */\r\nconst TRACK_MARKER = '__dshSkillPickerTrack'\r\n\r\n/** The self-healing patches, in application order. */\r\nexport const PATCHES = [\r\n {\r\n id: 'order',\r\n title: 'skill group order 2 \u2192 -1 (above commands)',\r\n isApplied(text) {\r\n return /name: \"skill\",[\\s\\S]*?order:\\s*-1,/.test(text)\r\n },\r\n apply(text) {\r\n return text.replace(/(name: \"skill\",\\s*order: )2,/, '$1-1,')\r\n },\r\n },\r\n {\r\n id: 'fuzzy-candidates',\r\n title: 'prefix/rank matcher \u2192 fuzzy+pinyin matcher',\r\n isApplied(text) {\r\n return text.includes(FUZZY_MARKER)\r\n },\r\n apply(text) {\r\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\r\n // 0.1.2-alpha.x : return skills.filter((skill) => skill.name.startsWith(query)).map(...)\r\n // 0.1.5-rc.x : return (0, _xxx.rankByName)(skills, query).map(...) \u2190 \u6539\u6210 async candidates\r\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\r\n const ANCHORS = [\r\n /(\\t*)return (\\(0, [\\w.$]+\\.rankByName\\)\\(skills, query\\)|rankByName\\(skills, query\\))\\.map\\(\\(skill\\) => \\(\\{/,\r\n /(\\t*)return (skills\\.filter\\(\\(skill\\) => skill\\.name\\.startsWith\\(query\\)\\))\\.map\\(\\(skill\\) => \\(\\{/,\r\n ]\r\n for (const re of ANCHORS) {\r\n const m = text.match(re)\r\n if (!m) continue\r\n const [, indent, official] = m\r\n return text.replace(\r\n re,\r\n `${indent}// dsh-skill-picker patch: fuzzy+pinyin matcher (self-healed)\\n` +\r\n `${indent}const officialMatcher = ${official};\\n` +\r\n `${indent}const matcher = typeof window.${FUZZY_MARKER} === \"function\" ? window.${FUZZY_MARKER}(skills, query) : officialMatcher;\\n` +\r\n `${indent}return matcher.map((skill) => ({`,\r\n )\r\n }\r\n return text\r\n },\r\n },\r\n {\r\n id: 'pick-tracking',\r\n title: 'record usage when picked from the official / menu',\r\n isApplied(text) {\r\n return text.includes(TRACK_MARKER)\r\n },\r\n apply(text) {\r\n return text.replace(\r\n /(\\t*)onPick\\(\\{ candidate \\}\\) \\{\\n(\\t*)return \\{ text: `\\/\\$\\{candidate\\.name\\} ` \\};\\n(\\t*)\\}/,\r\n (match, i1, i2, i3) =>\r\n `${i1}onPick({ candidate }) {\\n` +\r\n `${i2} // dsh-skill-picker patch: usage tracking (self-healed)\\n` +\r\n `${i2} try { window.${TRACK_MARKER}?.(candidate.name) } catch { /* best-effort */ }\\n` +\r\n `${i2} return { text: \\`/\\${candidate.name} \\` };\\n` +\r\n `${i3}}`,\r\n )\r\n },\r\n },\r\n]\r\n\r\n/** Resolve the DSH home directory, mirroring the official convention. */\r\nexport function dshHome() {\r\n return process.env.DSH_HOME ?? path.join(os.homedir(), '.dsh')\r\n}\r\n\r\n/** Subpath of the official skill UI inside any installation tree. */\r\nconst UI_SKILL_SUBPATH = ['node_modules', '@deepseek-ai', 'dsh-client-ui-skill', 'lib', 'client.js']\r\n\r\n/** Whether a path exists (any type). Never throws. */\r\nasync function pathExists(candidate) {\r\n try {\r\n await access(candidate)\r\n return true\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\n/**\r\n * Where the *running desktop build* keeps its own copy of the official skill UI.\r\n *\r\n * A packaged DSH Desktop ships that UI in one of two shapes (issue #9):\r\n *\r\n * - `resources/app/node_modules/\u2026` \u2014 an **unpacked** build. The app is the\r\n * installation-scoped owner, so this file is what gets served, and it is an\r\n * ordinary writable file.\r\n * - `resources/app.asar/dsh/node_modules/\u2026` \u2014 a **packed** build. Electron's\r\n * `fs` shim can *read* through the archive but cannot write into it, so this\r\n * path is evidence for diagnosis only and is never a patch target.\r\n *\r\n * `process.resourcesPath` only exists inside Electron; a plain `dsh` CLI or web\r\n * run returns empty lists and nothing changes for it.\r\n * @returns {{writable: string[], packed: string[]}} candidate paths.\r\n */\r\nexport function desktopUiSkillPaths() {\r\n const resources = process.resourcesPath\r\n if (typeof resources !== 'string' || resources === '') return { writable: [], packed: [] }\r\n return {\r\n writable: [path.join(resources, 'app', ...UI_SKILL_SUBPATH)],\r\n packed: [path.join(resources, 'app.asar', 'dsh', ...UI_SKILL_SUBPATH)],\r\n }\r\n}\r\n\r\n/**\r\n * Whether a profile manifest redirects the official skill UI to its own copy.\r\n *\r\n * This is the switch that decides which copy a desktop build serves: without a\r\n * declared dependency the profile resolves the package from the installation\r\n * (`resources/app` or `app.asar`), and every profile-local copy is dead weight.\r\n * @param profileDir - absolute profile directory.\r\n * @returns true when the manifest declares `@deepseek-ai/dsh-client-ui-skill`.\r\n */\r\nasync function profileRedirectsUiSkill(profileDir) {\r\n try {\r\n const manifest = JSON.parse(await readFile(path.join(profileDir, 'package.json'), 'utf8'))\r\n const declared = { ...manifest?.dependencies, ...manifest?.devDependencies }\r\n return typeof declared['@deepseek-ai/dsh-client-ui-skill'] === 'string'\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\n/**\r\n * Explain why the patch targets found (or not found) may not be what the host\r\n * actually serves, for a packed desktop build (issue #9).\r\n *\r\n * Silence here was the worst part of #9: patching a stale profile copy looks\r\n * exactly like success, so the `/` menu kept its official matcher with no\r\n * signal at all.\r\n * @returns a sentence to append to a warning, or '' when there is nothing to say.\r\n */\r\nasync function packedDesktopNote() {\r\n const { packed } = desktopUiSkillPaths()\r\n if (packed.length === 0 || !(await pathExists(packed[0]))) return ''\r\n const profilesDir = path.join(dshHome(), 'profiles')\r\n let redirected = false\r\n try {\r\n for (const entry of await readdir(profilesDir, { withFileTypes: true })) {\r\n if (entry.name === 'node_modules') continue\r\n if (await profileRedirectsUiSkill(path.join(profilesDir, entry.name))) {\r\n redirected = true\r\n break\r\n }\r\n }\r\n } catch {\r\n /* unreadable profile list: report the pessimistic case */\r\n }\r\n if (redirected) return ''\r\n return ' This desktop build keeps the official skill UI inside `app.asar`, which cannot be written in place,'\r\n + ' and no profile redirects the package to a local copy \u2014 so the served `/` menu keeps the official matcher.'\r\n + ' Install a profile-local override (`\"@deepseek-ai/dsh-client-ui-skill\": \"link:\u2026\"`) or run DSH from the CLI.'\r\n}\r\n\r\n/**\r\n * Enumerate every installed ui-skill `lib/client.js` across all profiles.\r\n *\r\n * Four layouts are covered (deduplicated by real path):\r\n * 1. the **shared core root** `profiles/node_modules/@deepseek-ai/\u2026` \u2014 used by\r\n * a global `npm i -g @deepseek-ai/dsh`, where the official package is NOT\r\n * below any single profile (issue #7 \u2014 missing this made the whole patch\r\n * silently no-op for those installs);\r\n * 2. the user's local patched copy (`profiles/<p>/local/dsh-client-ui-skill`)\r\n * \u2014 that is what a linked profile actually loads;\r\n * 3. the plain npm install inside a profile\r\n * (`profiles/<p>/node_modules/@deepseek-ai/\u2026`);\r\n * 4. the **running desktop build's own unpacked tree**\r\n * (`resources/app/node_modules/@deepseek-ai/\u2026`) \u2014 issue #9: a desktop\r\n * install owns the package at installation scope, so its copy is the one\r\n * served whenever no profile redirects the name.\r\n *\r\n * As a defensive extra, each profile is also asked through Node's own resolver\r\n * (`createRequire().resolve()`), so a layout we did not enumerate still works.\r\n * Never throws \u2014 a missing profiles dir yields [].\r\n * @returns {Promise<string[]>} candidate file paths.\r\n */\r\nexport async function uiSkillClientPaths() {\r\n const profilesDir = path.join(dshHome(), 'profiles')\r\n let profiles\r\n try {\r\n profiles = await readdir(profilesDir, { withFileTypes: true })\r\n } catch {\r\n // A desktop build can still be patchable through its own resource tree\r\n // even when $DSH_HOME/profiles is absent or unreadable.\r\n profiles = []\r\n }\r\n const seen = new Set()\r\n const found = []\r\n\r\n /** Add one candidate if it exists; deduplicate by real path. */\r\n const collect = async (candidate) => {\r\n try {\r\n await access(candidate)\r\n const real = await realpath(candidate)\r\n if (seen.has(real)) return\r\n seen.add(real)\r\n found.push(candidate)\r\n } catch {\r\n /* not present at this location */\r\n }\r\n }\r\n\r\n /** Ask Node's resolver where this profile would load the package from. */\r\n const collectByResolve = async (profileDir) => {\r\n try {\r\n const require = createRequire(path.join(profileDir, 'package.json'))\r\n await collect(require.resolve('@deepseek-ai/dsh-client-ui-skill/lib/client.js'))\r\n } catch {\r\n /* not resolvable from this profile */\r\n }\r\n }\r\n\r\n // 1. shared core root (global installs) \u2014 see the doc comment above.\r\n await collect(path.join(profilesDir, 'node_modules', '@deepseek-ai', 'dsh-client-ui-skill', 'lib', 'client.js'))\r\n\r\n // 4. the running desktop build's own unpacked tree (issue #9): this is the\r\n // copy a packaged Electron app serves when no profile redirects the name.\r\n for (const candidate of desktopUiSkillPaths().writable) await collect(candidate)\r\n\r\n for (const entry of profiles) {\r\n // `node_modules` is a sibling of the profiles, not a profile itself.\r\n if (entry.name === 'node_modules') continue\r\n // A profile directory may itself be reached through a link; follow it\r\n // instead of relying on the lstat-based dirent (see dir-entry.js).\r\n if (!(await isDirectoryEntry(profilesDir, entry))) continue\r\n // 2. the user's local patched copy (what a linked profile actually loads).\r\n await collect(path.join(profilesDir, entry.name, 'local', 'dsh-client-ui-skill', 'lib', 'client.js'))\r\n // 3. the plain npm install inside the profile.\r\n await collect(path.join(profilesDir, entry.name, 'node_modules', '@deepseek-ai', 'dsh-client-ui-skill', 'lib', 'client.js'))\r\n // Defensive: whatever Node itself would resolve (dupes removed above).\r\n await collectByResolve(path.join(profilesDir, entry.name))\r\n }\r\n return found\r\n}\r\n\r\n/**\r\n * Apply both patches to one ui-skill client.js. Idempotent: already-applied\r\n * patches are reported as skipped; the original file is backed up once before\r\n * the first modification. Never throws for a patch that does not match (it is\r\n * reported as `noop`), only for actual I/O failures.\r\n * @param {string} file - absolute path to the target client.js.\r\n * @returns {Promise<{file: string, patched: string[], skipped: string[], noop: string[]}>}\r\n */\r\nexport async function patchUiSkillFile(file) {\r\n const text = await readFile(file, 'utf8')\r\n const result = { file, patched: [], skipped: [], noop: [] }\r\n let next = text\r\n for (const patch of PATCHES) {\r\n if (patch.isApplied(next)) {\r\n result.skipped.push(patch.id)\r\n continue\r\n }\r\n const candidate = patch.apply(next)\r\n if (candidate === next) {\r\n result.noop.push(patch.id)\r\n continue\r\n }\r\n next = candidate\r\n result.patched.push(patch.id)\r\n }\r\n if (result.patched.length === 0) return result\r\n const backup = `${file}.dsh-skill-picker.bak`\r\n try {\r\n await access(backup)\r\n } catch {\r\n await copyFile(file, backup)\r\n }\r\n // Write through a temp file + rename instead of an in-place `writeFile`:\r\n // - pnpm installs are HARDLINKED to a shared content-addressable store, so\r\n // writing in place would mutate that shared inode and silently change\r\n // every other project using the same package version. rename() swaps the\r\n // directory entry instead, leaving the shared inode untouched.\r\n // - rename is also atomic, so an interrupted boot cannot leave a torn file.\r\n const tmp = `${file}.dsh-skill-picker.tmp`\r\n await writeFile(tmp, next, 'utf8')\r\n await rename(tmp, file)\r\n return result\r\n}\r\n\r\n/**\r\n * Self-heal entry point: scan all profiles, patch every ui-skill copy found,\r\n * and return one combined report. Never throws \u2014 each failure is collected\r\n * into `errors` so the host boot is never taken down by a broken patch.\r\n * @returns {Promise<{files: Array, errors: string[]}>}\r\n */\r\n/**\r\n * Undo our own file patch: restore every `<file>.dsh-skill-picker.bak` we made.\r\n *\r\n * The `/` enhancement has not needed this patch since v0.5.15, which takes over\r\n * the LIVE trigger source at runtime instead. Keeping the patch meant shipping a\r\n * modified copy of an official package \u2014 confusing on its own, and it made the\r\n * desktop and web profiles disagree about the same file.\r\n *\r\n * Restores only a file that still carries OUR marker, so a file edited by\r\n * something else since is left alone. Idempotent: after one pass there is\r\n * nothing left to restore.\r\n *\r\n * @returns {Promise<{restored: string[], errors: string[]}>}\r\n */\r\nexport async function revertUiSkillPatches() {\r\n const files = await uiSkillClientPaths()\r\n const restored = []\r\n const errors = []\r\n for (const file of files) {\r\n const backup = `${file}.dsh-skill-picker.bak`\r\n try {\r\n let text\r\n try {\r\n text = await readFile(file, 'utf8')\r\n } catch {\r\n continue\r\n }\r\n if (!PATCHES.some((patch) => patch.isApplied(text))) continue\r\n let original\r\n try {\r\n original = await readFile(backup, 'utf8')\r\n } catch {\r\n continue\r\n }\r\n // Same temp+rename dance as the patch itself: never write a hardlinked\r\n // package file in place (that would mutate the shared pnpm store).\r\n const tmp = `${file}.dsh-skill-picker.tmp`\r\n await writeFile(tmp, original, 'utf8')\r\n await rename(tmp, file)\r\n restored.push(file)\r\n } catch (error) {\r\n errors.push(`${file}: ${String(error?.message ?? error)}`)\r\n }\r\n }\r\n return { restored, errors }\r\n}\r\n\r\nexport async function healUiSkillPatches() {\r\n const files = await uiSkillClientPaths()\r\n const filesReport = []\r\n const errors = []\r\n for (const file of files) {\r\n try {\r\n filesReport.push(await patchUiSkillFile(file))\r\n } catch (error) {\r\n errors.push(`${file}: ${String(error?.message ?? error)}`)\r\n }\r\n }\r\n // Loud on the empty case. Silence here was the most confusing part of\r\n // issue #7: \"found 0 targets\" and \"everything already applied\" used to print\r\n // identically, so nobody could tell the patch had never run at all.\r\n const note = await packedDesktopNote()\r\n if (files.length === 0) {\r\n console.warn('[dsh-skill-picker] ui-skill patch: 0 target client.js found under '\r\n + `${path.join(dshHome(), 'profiles')} \u2014 fuzzy+pinyin matching will NOT be applied.`\r\n + `${note} See https://github.com/a735624258/dsh-skill-picker/issues/14`)\r\n } else if (note !== '') {\r\n // Targets existed, so the old code stayed silent \u2014 but none of them is the\r\n // copy the host serves. That is issue #9's silent failure: a \"successful\"\r\n // patch run that changes nothing the user can see.\r\n console.warn(`[dsh-skill-picker] ui-skill patch: patched ${files.length} profile copy/copies,`\r\n + ` but none of them is the one this host serves.${note}`)\r\n }\r\n return { files: filesReport, errors }\r\n}\r\n", "/**\n * Skill operations for the \u26A1 panel: enable / disable, reveal in the file\n * manager, and uninstall-with-undo.\n *\n * Three rules this module exists to enforce:\n *\n * 1. **Never delete anything.** \"Uninstall\" MOVES the skill into\n * `$DSH_HOME/skill-backups/<stamp>-<name>/` next to a manifest, and the panel\n * can move it back. This module makes no destructive filesystem call at all;\n * a move that cannot be done with `rename()` (i.e. across volumes) fails\n * loudly instead of falling back to copy-then-remove.\n *\n * 2. **Only inside the caller's skill roots.** The roots are passed in \u2014 this\n * module must NOT re-derive them, or the two copies drift apart (which is\n * exactly how the picker once ended up patching one file while DSH served\n * another). Every path the browser sends is resolved and checked against the\n * given roots; a path that escapes them, is the root itself, or is nested\n * deeper than one level is refused. The route is reachable without\n * authentication \u2014 plugin routes sit outside the web server's auth gate \u2014 so\n * this is not optional.\n *\n * 3. **Reversible, and honest about state.** Disabling renames the skill's entry\n * file, which is exactly what the official `dsh-skill-filesystem` provider\n * keys on, so a disabled skill really does stop loading for agents too \u2014 it\n * is not hidden only in our own UI. Re-enabling renames it back.\n *\n * Entry-file shape comes from the provider: a skill is a directory containing\n * `SKILL.md`, or a top-level `.md` file inside a skill root.\n *\n * @module dsh-skill-picker/skill-ops\n */\n\nimport { spawn } from 'node:child_process'\nimport { mkdir, readFile, readdir, rename, stat, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\n\nimport { dshHome } from './patch-ui-skill.js'\n\n/** The entry file the official provider looks for. */\nexport const ENTRY = 'SKILL.md'\n\n/** What a disabled entry file is called. The provider ignores it entirely. */\nexport const DISABLED_ENTRY = `${ENTRY}.disabled`\n\n/** Set on every mutating request, so a cross-site form POST cannot reach these. */\nexport const CLIENT_HEADER = 'x-dsh-skill-picker'\n\n/** Where uninstalled skills go, newest first. */\nexport function skillBackupDir() {\n return path.join(dshHome(), 'skill-backups')\n}\n\n/**\n * Resolve a browser-supplied skill path against the caller's roots.\n *\n * Requires exactly one path segment below a root, so a request for the root\n * itself, for a file nested inside a skill, or for `../../` is rejected.\n *\n * @param candidate - the path the browser sent.\n * @param roots - absolute skill roots (from the host's own helpers).\n * @returns the resolved absolute path, or undefined when it is not a skill slot.\n */\nexport function resolveSkillPath(candidate, roots) {\n if (typeof candidate !== 'string' || candidate === '') return undefined\n const list = Array.isArray(roots) ? roots : []\n const resolved = path.resolve(candidate)\n for (const root of list) {\n const base = path.resolve(root)\n const relative = path.relative(base, resolved)\n if (relative === '' || relative.startsWith('..') || path.isAbsolute(relative)) continue\n if (relative.split(path.sep).filter(Boolean).length !== 1) continue\n return resolved\n }\n return undefined\n}\n\n/** Whether the path is a directory (a skill folder) or a file (a root-level .md). */\nasync function pathKind(target) {\n try {\n return (await stat(target)).isDirectory() ? 'dir' : 'file'\n } catch {\n return undefined\n }\n}\n\n/** Whether anything exists at `candidate` (used to refuse overwrites). */\nasync function exists(candidate) {\n try {\n await stat(candidate)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Turn a skill on or off by renaming its entry file.\n *\n * @param target - an already-validated skill path.\n * @param disabled - the state to move to.\n * @returns `{ entry, disabled }` \u2014 the entry file name now in place.\n */\nexport async function setSkillDisabled(target, disabled) {\n const kind = await pathKind(target)\n if (kind === undefined) throw new Error(`skill not found: ${target}`)\n const dir = kind === 'dir' ? target : path.dirname(target)\n const base = kind === 'dir' ? ENTRY : path.basename(target)\n const plain = path.join(dir, base)\n const off = path.join(dir, `${base}.disabled`)\n const hasPlain = await exists(plain)\n const hasOff = await exists(off)\n\n if (disabled) {\n if (hasOff && !hasPlain) throw new Error('already disabled')\n if (!hasPlain) throw new Error(`cannot disable: ${base} is not there`)\n if (hasOff) throw new Error(`cannot disable: ${base} and ${base}.disabled both exist`)\n await rename(plain, off)\n return { entry: `${base}.disabled`, disabled: true }\n }\n\n if (hasPlain && !hasOff) throw new Error('already enabled')\n if (!hasOff) throw new Error(`cannot enable: ${base}.disabled is not there`)\n if (hasPlain) throw new Error(`cannot enable: ${base} and ${base}.disabled both exist`)\n await rename(off, plain)\n return { entry: base, disabled: false }\n}\n\n/**\n * Move a skill into the backup directory (nothing is deleted).\n *\n * Layout: `skill-backups/<stamp>-<name>/` holds `manifest.json` plus the moved\n * skill directory (or file) under its original name, so it moves straight back.\n *\n * @returns `{ backupPath, restoreTarget }`.\n */\nexport async function uninstallSkill(target) {\n const kind = await pathKind(target)\n if (kind === undefined) throw new Error(`skill not found: ${target}`)\n const name = path.basename(target)\n const stamp = new Date().toISOString().replace(/[:.]/g, '-')\n const backupPath = path.join(skillBackupDir(), `${stamp}-${name}`)\n await mkdir(backupPath, { recursive: true })\n await rename(target, path.join(backupPath, name))\n await writeFile(path.join(backupPath, 'manifest.json'), `${JSON.stringify({\n name,\n kind,\n originalPath: target,\n movedAt: new Date().toISOString(),\n }, null, 2)}\\n`, 'utf8')\n return { backupPath, restoreTarget: target }\n}\n\n/** List backups, newest first, so the panel can offer an undo. */\nexport async function listSkillBackups(roots) {\n let entries\n try {\n entries = await readdir(skillBackupDir(), { withFileTypes: true })\n } catch {\n return []\n }\n const backups = []\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue\n const dir = path.join(skillBackupDir(), entry.name)\n try {\n const manifest = JSON.parse(await readFile(path.join(dir, 'manifest.json'), 'utf8'))\n backups.push({\n id: entry.name,\n name: typeof manifest.name === 'string' ? manifest.name : entry.name,\n originalPath: typeof manifest.originalPath === 'string' ? manifest.originalPath : undefined,\n movedAt: typeof manifest.movedAt === 'string' ? manifest.movedAt : undefined,\n restorable: resolveSkillPath(manifest.originalPath, roots) !== undefined,\n })\n } catch {\n /* not one of ours (or a damaged manifest) \u2014 leave it alone */\n }\n }\n return backups.sort((a, b) => String(b.movedAt ?? '').localeCompare(String(a.movedAt ?? '')))\n}\n\n/**\n * Move a backup back where it came from.\n *\n * Refuses when the destination is occupied, so a restore can never overwrite a\n * skill installed after the uninstall. The emptied backup folder is kept under\n * `skill-backups/.restored/` rather than deleted.\n */\nexport async function restoreSkillBackup(id, roots) {\n if (typeof id !== 'string' || id === '' || id.includes('/') || id.includes('\\\\') || id.includes('..')) {\n throw new Error('invalid backup id')\n }\n const dir = path.join(skillBackupDir(), id)\n let manifest\n try {\n manifest = JSON.parse(await readFile(path.join(dir, 'manifest.json'), 'utf8'))\n } catch {\n throw new Error('that backup is gone')\n }\n const original = resolveSkillPath(manifest.originalPath, roots)\n if (original === undefined) throw new Error('that backup no longer points inside a skill root')\n if (await exists(original)) {\n throw new Error(`${path.basename(original)} is occupied again \u2014 move it away first`)\n }\n await rename(path.join(dir, path.basename(original)), original)\n const done = path.join(skillBackupDir(), '.restored')\n await mkdir(done, { recursive: true })\n await rename(dir, path.join(done, id)).catch(() => {})\n return { restored: original }\n}\n\n/**\n * Reveal a skill in the OS file manager.\n *\n * Best-effort and detached: the host must not wait on (or be taken down by) a\n * file-manager window.\n */\nexport function revealSkill(target, revealPath) {\n const show = typeof revealPath === 'string' && revealPath !== '' ? revealPath : target\n try {\n if (process.platform === 'win32') {\n spawn('explorer.exe', [`/select,${show}`], { detached: true, stdio: 'ignore' }).unref()\n return true\n }\n if (process.platform === 'darwin') {\n spawn('open', ['-R', show], { detached: true, stdio: 'ignore' }).unref()\n return true\n }\n spawn('xdg-open', [path.dirname(show)], { detached: true, stdio: 'ignore' }).unref()\n return true\n } catch {\n return false\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["mkdir", "readFile", "readdir", "rename", "writeFile", "os", "path", "path", "path", "require", "readFile", "readdir", "rename", "stat", "writeFile", "path", "path", "stat", "rename", "writeFile", "readdir", "readFile", "path", "os", "readdir", "readFile", "mkdir", "writeFile", "rename"]
|
|
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.23",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|