dsh-skill-picker 0.5.11 → 0.5.13
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 +9 -2
- package/lib/client.js +43 -14
- package/lib/client.js.map +3 -3
- package/lib/index.js +97 -5
- package/lib/index.js.map +2 -2
- package/package.json +12 -8
- package/src/client/index.jsx +758 -717
- package/src/client/session-view.js +68 -0
- package/src/index.js +91 -3
- package/src/patch-ui-skill.js +110 -5
package/lib/index.js
CHANGED
|
@@ -81,13 +81,57 @@ ${i3}}`
|
|
|
81
81
|
function dshHome() {
|
|
82
82
|
return process.env.DSH_HOME ?? path2.join(os.homedir(), ".dsh");
|
|
83
83
|
}
|
|
84
|
+
var UI_SKILL_SUBPATH = ["node_modules", "@deepseek-ai", "dsh-client-ui-skill", "lib", "client.js"];
|
|
85
|
+
async function pathExists(candidate) {
|
|
86
|
+
try {
|
|
87
|
+
await access(candidate);
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function desktopUiSkillPaths() {
|
|
94
|
+
const resources = process.resourcesPath;
|
|
95
|
+
if (typeof resources !== "string" || resources === "") return { writable: [], packed: [] };
|
|
96
|
+
return {
|
|
97
|
+
writable: [path2.join(resources, "app", ...UI_SKILL_SUBPATH)],
|
|
98
|
+
packed: [path2.join(resources, "app.asar", "dsh", ...UI_SKILL_SUBPATH)]
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function profileRedirectsUiSkill(profileDir) {
|
|
102
|
+
try {
|
|
103
|
+
const manifest = JSON.parse(await readFile(path2.join(profileDir, "package.json"), "utf8"));
|
|
104
|
+
const declared = { ...manifest?.dependencies, ...manifest?.devDependencies };
|
|
105
|
+
return typeof declared["@deepseek-ai/dsh-client-ui-skill"] === "string";
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function packedDesktopNote() {
|
|
111
|
+
const { packed } = desktopUiSkillPaths();
|
|
112
|
+
if (packed.length === 0 || !await pathExists(packed[0])) return "";
|
|
113
|
+
const profilesDir = path2.join(dshHome(), "profiles");
|
|
114
|
+
let redirected = false;
|
|
115
|
+
try {
|
|
116
|
+
for (const entry of await readdir(profilesDir, { withFileTypes: true })) {
|
|
117
|
+
if (entry.name === "node_modules") continue;
|
|
118
|
+
if (await profileRedirectsUiSkill(path2.join(profilesDir, entry.name))) {
|
|
119
|
+
redirected = true;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
if (redirected) return "";
|
|
126
|
+
return ' This desktop build keeps the official skill UI inside `app.asar`, which cannot be written in place, and no profile redirects the package to a local copy \u2014 so the served `/` menu keeps the official matcher. Install a profile-local override (`"@deepseek-ai/dsh-client-ui-skill": "link:\u2026"`) or run DSH from the CLI.';
|
|
127
|
+
}
|
|
84
128
|
async function uiSkillClientPaths() {
|
|
85
129
|
const profilesDir = path2.join(dshHome(), "profiles");
|
|
86
130
|
let profiles;
|
|
87
131
|
try {
|
|
88
132
|
profiles = await readdir(profilesDir, { withFileTypes: true });
|
|
89
133
|
} catch {
|
|
90
|
-
|
|
134
|
+
profiles = [];
|
|
91
135
|
}
|
|
92
136
|
const seen = /* @__PURE__ */ new Set();
|
|
93
137
|
const found = [];
|
|
@@ -109,6 +153,7 @@ async function uiSkillClientPaths() {
|
|
|
109
153
|
}
|
|
110
154
|
};
|
|
111
155
|
await collect(path2.join(profilesDir, "node_modules", "@deepseek-ai", "dsh-client-ui-skill", "lib", "client.js"));
|
|
156
|
+
for (const candidate of desktopUiSkillPaths().writable) await collect(candidate);
|
|
112
157
|
for (const entry of profiles) {
|
|
113
158
|
if (entry.name === "node_modules") continue;
|
|
114
159
|
if (!await isDirectoryEntry(profilesDir, entry)) continue;
|
|
@@ -158,8 +203,11 @@ async function healUiSkillPatches() {
|
|
|
158
203
|
errors.push(`${file}: ${String(error?.message ?? error)}`);
|
|
159
204
|
}
|
|
160
205
|
}
|
|
206
|
+
const note = await packedDesktopNote();
|
|
161
207
|
if (files.length === 0) {
|
|
162
|
-
console.warn(`[dsh-skill-picker] ui-skill patch: 0 target client.js found under ${path2.join(dshHome(), "profiles")} \u2014 fuzzy+pinyin matching will NOT be applied
|
|
208
|
+
console.warn(`[dsh-skill-picker] ui-skill patch: 0 target client.js found under ${path2.join(dshHome(), "profiles")} \u2014 fuzzy+pinyin matching will NOT be applied.${note} See https://github.com/a735624258/dsh-skill-picker/issues/9`);
|
|
209
|
+
} else if (note !== "") {
|
|
210
|
+
console.warn(`[dsh-skill-picker] ui-skill patch: patched ${files.length} profile copy/copies, but none of them is the one this host serves.${note}`);
|
|
163
211
|
}
|
|
164
212
|
return { files: filesReport, errors };
|
|
165
213
|
}
|
|
@@ -176,11 +224,15 @@ function userAgentsSkillsDir() {
|
|
|
176
224
|
const agentsHome = process.env.DSH_AGENTS_HOME ?? path3.join(os2.homedir(), ".agents");
|
|
177
225
|
return path3.join(agentsHome, "skills");
|
|
178
226
|
}
|
|
179
|
-
function
|
|
227
|
+
function frontmatterBlock(content) {
|
|
180
228
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
181
|
-
|
|
229
|
+
return match === null ? void 0 : match[1];
|
|
230
|
+
}
|
|
231
|
+
function parseFrontmatter(content) {
|
|
232
|
+
const block = frontmatterBlock(content);
|
|
233
|
+
if (block === void 0) return {};
|
|
182
234
|
const out = {};
|
|
183
|
-
for (const line of
|
|
235
|
+
for (const line of block.split(/\r?\n/)) {
|
|
184
236
|
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
185
237
|
if (!kv) continue;
|
|
186
238
|
const value = kv[2].trim().replace(/^["']|["']$/g, "");
|
|
@@ -188,6 +240,45 @@ function parseFrontmatter(content) {
|
|
|
188
240
|
}
|
|
189
241
|
return out;
|
|
190
242
|
}
|
|
243
|
+
function hasFrontmatterKey(content, key) {
|
|
244
|
+
const block = frontmatterBlock(content);
|
|
245
|
+
if (block === void 0) return false;
|
|
246
|
+
return block.split(/\r?\n/).some((line) => {
|
|
247
|
+
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
248
|
+
return kv !== null && kv[1] === key;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
var LEGACY_INVOCATION_KEYS = ["disableModelInvocation", "modelInvocable", "userInvocable"];
|
|
252
|
+
function frontmatterBoolean(meta, key) {
|
|
253
|
+
if (!Object.hasOwn(meta, key)) return void 0;
|
|
254
|
+
const value = meta[key];
|
|
255
|
+
if (typeof value === "boolean") return value;
|
|
256
|
+
if (value === 1 || value === "1") return true;
|
|
257
|
+
if (value === 0 || value === "0") return false;
|
|
258
|
+
if (typeof value === "string") {
|
|
259
|
+
switch (value.toLowerCase()) {
|
|
260
|
+
case "true":
|
|
261
|
+
case "yes":
|
|
262
|
+
case "on":
|
|
263
|
+
return true;
|
|
264
|
+
case "false":
|
|
265
|
+
case "no":
|
|
266
|
+
case "off":
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
throw new TypeError(`frontmatter field "${key}" must be a boolean`);
|
|
271
|
+
}
|
|
272
|
+
function isUserInvocableSkill(meta, content) {
|
|
273
|
+
for (const legacy of LEGACY_INVOCATION_KEYS) {
|
|
274
|
+
if (hasFrontmatterKey(content, legacy)) return false;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
return frontmatterBoolean(meta, "user-invocable") !== false;
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
191
282
|
async function scanSkillsDirInto(map, dir) {
|
|
192
283
|
let entries;
|
|
193
284
|
try {
|
|
@@ -205,6 +296,7 @@ async function scanSkillsDirInto(map, dir) {
|
|
|
205
296
|
continue;
|
|
206
297
|
}
|
|
207
298
|
const meta = parseFrontmatter(content);
|
|
299
|
+
if (!isUserInvocableSkill(meta, content)) continue;
|
|
208
300
|
map.set(meta.name ?? entry.name, {
|
|
209
301
|
name: meta.name ?? entry.name,
|
|
210
302
|
description: meta.description ?? "",
|
package/lib/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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 { readFile, readdir } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport { isDirectoryEntry } from './dir-entry.js'\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 // 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 // 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 * 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 //\n // Logging is gated on \"did this boot change anything / fail\" rather than\n // \"was a target found\" \u2014 see `reportUiSkillPatches` (issue #8).\n ctx.effect(() => {\n healUiSkillPatches().then((report) => {\n reportUiSkillPatches(report)\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 * 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/**\r\n * Enumerate every installed ui-skill `lib/client.js` across all profiles.\r\n *\r\n * Three 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 *\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 return []\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 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\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 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 + 'See https://github.com/a735624258/dsh-skill-picker/issues/7')\r\n }\r\n return { files: filesReport, errors }\r\n}\r\n"],
|
|
5
|
-
"mappings": ";AAoBA,SAAS,YAAAA,WAAU,WAAAC,gBAAe;AAClC,OAAOC,SAAQ;AACf,OAAOC,WAAU;;;ACTjB,SAAS,YAAY;AACrB,OAAO,UAAU;AAejB,eAAsB,iBAAiB,KAAK,OAAO;AACjD,MAAI,MAAM,YAAY,EAAG,QAAO;AAChC,MAAI,CAAC,MAAM,eAAe,EAAG,QAAO;AACpC,MAAI;AACF,YAAQ,MAAM,KAAK,KAAK,KAAK,KAAK,MAAM,IAAI,CAAC,GAAG,YAAY;AAAA,EAC9D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACRA,SAAS,UAAU,WAAW,UAAU,SAAS,QAAQ,UAAU,cAAc;AACjF,SAAS,qBAAqB;AAC9B,OAAO,QAAQ;AACf,OAAOC,WAAU;AAKjB,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,YAAYC,MAAK,KAAK,GAAG,QAAQ,GAAG,MAAM;AAC/D;
|
|
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 { isDirectoryEntry } from './dir-entry.js'\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/** 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 * 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 //\n // Logging is gated on \"did this boot change anything / fail\" rather than\n // \"was a target found\" \u2014 see `reportUiSkillPatches` (issue #8).\n ctx.effect(() => {\n healUiSkillPatches().then((report) => {\n reportUiSkillPatches(report)\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 * 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\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/9`)\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
6
|
"names": ["readFile", "readdir", "os", "path", "path", "path", "require", "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.13",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -38,7 +38,11 @@
|
|
|
38
38
|
"0.1.2-alpha.5": "compatible",
|
|
39
39
|
"0.1.2-rc.1": "compatible",
|
|
40
40
|
"0.1.5-rc.1": "compatible",
|
|
41
|
-
"0.1.5-rc.2": "compatible"
|
|
41
|
+
"0.1.5-rc.2": "compatible",
|
|
42
|
+
"0.1.7-rc.1": "unknown",
|
|
43
|
+
"0.1.7-rc.2": "unknown",
|
|
44
|
+
"0.2.0-rc.1": "compatible",
|
|
45
|
+
"0.2.0-rc.2": "compatible"
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
},
|
|
@@ -47,12 +51,12 @@
|
|
|
47
51
|
},
|
|
48
52
|
"peerDependencies": {
|
|
49
53
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
50
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
51
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
52
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
53
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
54
|
-
"@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
|
|
55
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
|
|
54
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6 || >=0.2.0-rc.1 <1.0.0-0",
|
|
55
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6 || >=0.2.0-rc.1 <1.0.0-0",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6 || >=0.2.0-rc.1 <1.0.0-0",
|
|
57
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6 || >=0.2.0-rc.1 <1.0.0-0",
|
|
58
|
+
"@deepseek-ai/dsh-skill": "^0.1.0-rc.6 || >=0.2.0-rc.1 <1.0.0-0",
|
|
59
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6 || >=0.2.0-rc.1 <1.0.0-0",
|
|
56
60
|
"react": "^18.2.0"
|
|
57
61
|
},
|
|
58
62
|
"files": [
|