dsh-skill-importer 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +160 -38
- package/lib/types/client/index.d.ts +1 -1
- package/package.json +1 -1
- package/tests/update.test.mjs +1 -1
package/lib/client.js
CHANGED
|
@@ -2102,15 +2102,70 @@ window.__ModuleLoader__.load({
|
|
|
2102
2102
|
* with `/name ` — the native text gesture — highlighted via the plugin's
|
|
2103
2103
|
* lexicon contribution.
|
|
2104
2104
|
*/
|
|
2105
|
-
/** Find the one
|
|
2106
|
-
function
|
|
2105
|
+
/** Find the one legacy textarea or Lexical contenteditable owned by this picker. */
|
|
2106
|
+
function composerInputFor(root) {
|
|
2107
2107
|
let node = root?.parentElement;
|
|
2108
2108
|
while (node !== null && node !== void 0) {
|
|
2109
|
-
const
|
|
2110
|
-
if (
|
|
2109
|
+
const inputs = node.querySelectorAll("textarea, [data-composer-input]");
|
|
2110
|
+
if (inputs.length > 0) return inputs.length === 1 ? inputs[0] : void 0;
|
|
2111
2111
|
node = node.parentElement;
|
|
2112
2112
|
}
|
|
2113
2113
|
}
|
|
2114
|
+
function composerText(input) {
|
|
2115
|
+
return input instanceof HTMLTextAreaElement ? input.value : input.textContent ?? "";
|
|
2116
|
+
}
|
|
2117
|
+
function composerCaret(input) {
|
|
2118
|
+
if (input instanceof HTMLTextAreaElement) {
|
|
2119
|
+
if (input.selectionStart === null || input.selectionEnd === null) return void 0;
|
|
2120
|
+
return {
|
|
2121
|
+
start: input.selectionStart,
|
|
2122
|
+
end: input.selectionEnd
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
const selection = window.getSelection();
|
|
2126
|
+
if (selection === null || selection.rangeCount !== 1) return void 0;
|
|
2127
|
+
const range = selection.getRangeAt(0);
|
|
2128
|
+
if (!input.contains(range.startContainer) || !input.contains(range.endContainer)) return void 0;
|
|
2129
|
+
const offsetOf = (container, offset) => {
|
|
2130
|
+
const prefix = range.cloneRange();
|
|
2131
|
+
prefix.selectNodeContents(input);
|
|
2132
|
+
prefix.setEnd(container, offset);
|
|
2133
|
+
return prefix.toString().length;
|
|
2134
|
+
};
|
|
2135
|
+
return {
|
|
2136
|
+
start: offsetOf(range.startContainer, range.startOffset),
|
|
2137
|
+
end: offsetOf(range.endContainer, range.endOffset)
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
function setComposerCaret(input, caret) {
|
|
2141
|
+
input.focus({ preventScroll: true });
|
|
2142
|
+
if (input instanceof HTMLTextAreaElement) {
|
|
2143
|
+
input.setSelectionRange(caret, caret);
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
const range = document.createRange();
|
|
2147
|
+
let remaining = caret;
|
|
2148
|
+
const walker = document.createTreeWalker(input, NodeFilter.SHOW_TEXT);
|
|
2149
|
+
let text = walker.nextNode();
|
|
2150
|
+
while (text !== null) {
|
|
2151
|
+
const length = text.textContent?.length ?? 0;
|
|
2152
|
+
if (remaining <= length) {
|
|
2153
|
+
range.setStart(text, remaining);
|
|
2154
|
+
range.collapse(true);
|
|
2155
|
+
const selection = window.getSelection();
|
|
2156
|
+
selection?.removeAllRanges();
|
|
2157
|
+
selection?.addRange(range);
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
remaining -= length;
|
|
2161
|
+
text = walker.nextNode();
|
|
2162
|
+
}
|
|
2163
|
+
range.selectNodeContents(input);
|
|
2164
|
+
range.collapse(false);
|
|
2165
|
+
const selection = window.getSelection();
|
|
2166
|
+
selection?.removeAllRanges();
|
|
2167
|
+
selection?.addRange(range);
|
|
2168
|
+
}
|
|
2114
2169
|
/** One row of the composer skill picker. */
|
|
2115
2170
|
function SkillsPicker({ t, sessionId, useInput, inputActions, actions }) {
|
|
2116
2171
|
const input = useInput((value) => value.draft);
|
|
@@ -2164,13 +2219,12 @@ window.__ModuleLoader__.load({
|
|
|
2164
2219
|
draftRef.current = input;
|
|
2165
2220
|
skillNamesRef.current = usable.map((skill) => skill.name);
|
|
2166
2221
|
const scheduleComposerFocus = (expectedDraft, caret) => {
|
|
2167
|
-
const
|
|
2222
|
+
const input = composerInputFor(rootRef.current);
|
|
2168
2223
|
if (caretFrameRef.current !== void 0) window.cancelAnimationFrame(caretFrameRef.current);
|
|
2169
2224
|
caretFrameRef.current = window.requestAnimationFrame(() => {
|
|
2170
2225
|
caretFrameRef.current = void 0;
|
|
2171
|
-
if (
|
|
2172
|
-
|
|
2173
|
-
textarea.setSelectionRange(caret, caret);
|
|
2226
|
+
if (input === void 0 || !input.isConnected || composerText(input) !== expectedDraft) return;
|
|
2227
|
+
setComposerCaret(input, caret);
|
|
2174
2228
|
});
|
|
2175
2229
|
};
|
|
2176
2230
|
(0, react.useEffect)(() => {
|
|
@@ -2192,18 +2246,20 @@ window.__ModuleLoader__.load({
|
|
|
2192
2246
|
if (event.defaultPrevented || event.isComposing || event.keyCode === 229) return;
|
|
2193
2247
|
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
2194
2248
|
if (event.shiftKey && event.key !== "Backspace") return;
|
|
2195
|
-
const
|
|
2196
|
-
if (!(
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2249
|
+
const target = event.target;
|
|
2250
|
+
if (!(target instanceof Node)) return;
|
|
2251
|
+
const input = composerInputFor(rootRef.current);
|
|
2252
|
+
if (input === void 0 || document.activeElement !== input || !input.contains(target)) return;
|
|
2253
|
+
if (composerText(input) !== draftRef.current) return;
|
|
2254
|
+
const selection = composerCaret(input);
|
|
2255
|
+
if (selection === void 0 || selection.start !== selection.end) return;
|
|
2256
|
+
const start = selection.start;
|
|
2201
2257
|
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
2202
2258
|
const caret = atomicSkillArrow(draftRef.current, start, event.key === "ArrowLeft" ? "left" : "right", skillNamesRef.current);
|
|
2203
2259
|
if (caret === void 0) return;
|
|
2204
2260
|
event.preventDefault();
|
|
2205
2261
|
event.stopPropagation();
|
|
2206
|
-
|
|
2262
|
+
setComposerCaret(input, caret);
|
|
2207
2263
|
return;
|
|
2208
2264
|
}
|
|
2209
2265
|
const edit = atomicSkillBackspace(draftRef.current, start, skillNamesRef.current);
|
|
@@ -2214,8 +2270,8 @@ window.__ModuleLoader__.load({
|
|
|
2214
2270
|
if (caretFrameRef.current !== void 0) window.cancelAnimationFrame(caretFrameRef.current);
|
|
2215
2271
|
caretFrameRef.current = window.requestAnimationFrame(() => {
|
|
2216
2272
|
caretFrameRef.current = void 0;
|
|
2217
|
-
if (!
|
|
2218
|
-
|
|
2273
|
+
if (!input.isConnected || document.activeElement !== input || composerText(input) !== edit.draft) return;
|
|
2274
|
+
setComposerCaret(input, edit.caret);
|
|
2219
2275
|
});
|
|
2220
2276
|
};
|
|
2221
2277
|
document.addEventListener("keydown", onKeyDown, true);
|
|
@@ -2703,12 +2759,46 @@ window.__ModuleLoader__.load({
|
|
|
2703
2759
|
};
|
|
2704
2760
|
//#endregion
|
|
2705
2761
|
//#region src/client/index.ts
|
|
2706
|
-
/**
|
|
2762
|
+
/** Every client service used during apply is explicit; alpha DSH no longer tolerates load-order access. */
|
|
2707
2763
|
const inject = [
|
|
2708
2764
|
"slots",
|
|
2709
2765
|
"locale",
|
|
2710
|
-
"connection"
|
|
2766
|
+
"connection",
|
|
2767
|
+
"remote",
|
|
2768
|
+
"remote.skills",
|
|
2769
|
+
"remote.directoryPicker",
|
|
2770
|
+
"inputTriggers",
|
|
2771
|
+
"commandUi",
|
|
2772
|
+
"sessions",
|
|
2773
|
+
"conversation"
|
|
2711
2774
|
];
|
|
2775
|
+
const popupEscapeHandlers = /* @__PURE__ */ new WeakMap();
|
|
2776
|
+
/** Restore keyboard ownership when DSH opens this plugin's popup from the composer. */
|
|
2777
|
+
function focusSkillsPopupSearch(options) {
|
|
2778
|
+
const focus = () => {
|
|
2779
|
+
for (const input of document.querySelectorAll("input[type=\"text\"]")) {
|
|
2780
|
+
let node = input;
|
|
2781
|
+
while (node !== null) {
|
|
2782
|
+
if ((node.getAttribute("aria-label") ?? "").toLowerCase().includes("skills")) {
|
|
2783
|
+
const previous = popupEscapeHandlers.get(input);
|
|
2784
|
+
if (previous !== void 0) input.removeEventListener("keydown", previous);
|
|
2785
|
+
const onKeyDown = (event) => {
|
|
2786
|
+
if (event.key !== "Escape") return;
|
|
2787
|
+
window.setTimeout(options.onEscape, 0);
|
|
2788
|
+
};
|
|
2789
|
+
popupEscapeHandlers.set(input, onKeyDown);
|
|
2790
|
+
input.addEventListener("keydown", onKeyDown);
|
|
2791
|
+
input.focus({ preventScroll: true });
|
|
2792
|
+
options.resetActive();
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2795
|
+
node = node.parentElement;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
window.requestAnimationFrame(focus);
|
|
2800
|
+
window.setTimeout(focus, 0);
|
|
2801
|
+
}
|
|
2712
2802
|
/** Same-origin route literals (mirrors the host half's ROUTES). */
|
|
2713
2803
|
const ROUTES = {
|
|
2714
2804
|
list: "/skill-importer/list",
|
|
@@ -2762,12 +2852,10 @@ window.__ModuleLoader__.load({
|
|
|
2762
2852
|
console.error("[dsh-skill-importer] listener failed:", error);
|
|
2763
2853
|
}
|
|
2764
2854
|
};
|
|
2765
|
-
const
|
|
2766
|
-
const
|
|
2767
|
-
skillsApi
|
|
2768
|
-
skillsApi.list = (
|
|
2769
|
-
return Promise.reject(/* @__PURE__ */ new Error("skill group hidden: use the composer skill picker or /skills"));
|
|
2770
|
-
});
|
|
2855
|
+
const legacyApi = ctx.get("connection").api;
|
|
2856
|
+
const remote = ctx.remote ?? ctx.get("remote");
|
|
2857
|
+
const skillsApi = remote?.skills ?? legacyApi?.skills;
|
|
2858
|
+
if (skillsApi !== void 0) skillsApi.list = () => Promise.reject(/* @__PURE__ */ new Error("skill group hidden: use the composer skill picker or /skills"));
|
|
2771
2859
|
const usableNames = () => [...new Set([...sessionCatalogs.values()].flat().filter((skill) => skill.userInvocable).map((skill) => skill.name))];
|
|
2772
2860
|
ctx.effect(() => {
|
|
2773
2861
|
return ctx.get("inputTriggers").registerSource({
|
|
@@ -2792,8 +2880,7 @@ window.__ModuleLoader__.load({
|
|
|
2792
2880
|
}, "dsh-skill-importer: lexicon source");
|
|
2793
2881
|
const commandUi = ctx.get("commandUi");
|
|
2794
2882
|
const sessions = ctx.get("sessions");
|
|
2795
|
-
|
|
2796
|
-
if (commandUi !== void 0 && sessions !== void 0 && conversation !== void 0) ctx.effect(() => {
|
|
2883
|
+
if (commandUi !== void 0 && sessions !== void 0) ctx.effect(() => {
|
|
2797
2884
|
return commandUi.register({
|
|
2798
2885
|
name: "skills",
|
|
2799
2886
|
description: t("commandDescription"),
|
|
@@ -2801,24 +2888,53 @@ window.__ModuleLoader__.load({
|
|
|
2801
2888
|
ui: {
|
|
2802
2889
|
kind: "popupSelect",
|
|
2803
2890
|
async options(session, _signal) {
|
|
2804
|
-
|
|
2891
|
+
const rows = await refreshSkillsForSession(session.sessionId);
|
|
2892
|
+
const restoreComposerFocus = () => {
|
|
2893
|
+
const actx = sessions.scope(session.sessionId);
|
|
2894
|
+
if (actx === void 0) return;
|
|
2895
|
+
const conversation = actx.get("conversation");
|
|
2896
|
+
if (conversation === void 0) return;
|
|
2897
|
+
const draft = conversation.input.for(actx).state.getSnapshot().draft;
|
|
2898
|
+
requestComposerFocus({
|
|
2899
|
+
sessionId: session.sessionId,
|
|
2900
|
+
expectedDraft: draft,
|
|
2901
|
+
caret: draft.length
|
|
2902
|
+
});
|
|
2903
|
+
};
|
|
2904
|
+
const resetActive = () => {
|
|
2905
|
+
const actx = sessions.scope(session.sessionId);
|
|
2906
|
+
if (actx === void 0) return;
|
|
2907
|
+
commandUi.popupFor(actx).highlight?.(0);
|
|
2908
|
+
};
|
|
2909
|
+
focusSkillsPopupSearch({
|
|
2910
|
+
onEscape: restoreComposerFocus,
|
|
2911
|
+
resetActive
|
|
2912
|
+
});
|
|
2913
|
+
return rows.filter((skill) => skill.userInvocable).map((skill) => ({
|
|
2805
2914
|
id: skill.name,
|
|
2806
2915
|
label: `${skill.name} · ${sourceLabel(skill.source)}`
|
|
2807
2916
|
}));
|
|
2808
2917
|
},
|
|
2809
2918
|
onSelect(option, session) {
|
|
2810
2919
|
const actx = sessions.scope(session.sessionId);
|
|
2811
|
-
if (actx === void 0)
|
|
2812
|
-
const
|
|
2813
|
-
|
|
2920
|
+
if (actx === void 0) throw new Error("this session is no longer available");
|
|
2921
|
+
const conversation = actx.get("conversation");
|
|
2922
|
+
if (conversation === void 0) throw new Error("conversation input is unavailable");
|
|
2923
|
+
const rest = conversation.input.for(actx).state.getSnapshot().draft.replace(/^\s*\/[a-zA-Z][a-zA-Z0-9-]*\s*/, "");
|
|
2814
2924
|
const prefix = `/${option.id} `;
|
|
2815
2925
|
const nextDraft = `${prefix}${rest}`;
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2926
|
+
window.setTimeout(() => {
|
|
2927
|
+
const liveScope = sessions.scope(session.sessionId);
|
|
2928
|
+
if (liveScope === void 0) return;
|
|
2929
|
+
const liveConversation = liveScope.get("conversation");
|
|
2930
|
+
if (liveConversation === void 0) return;
|
|
2931
|
+
liveConversation.input.for(liveScope).setDraft(nextDraft);
|
|
2932
|
+
requestComposerFocus({
|
|
2933
|
+
sessionId: session.sessionId,
|
|
2934
|
+
expectedDraft: nextDraft,
|
|
2935
|
+
caret: prefix.length
|
|
2936
|
+
});
|
|
2937
|
+
}, 0);
|
|
2822
2938
|
}
|
|
2823
2939
|
}
|
|
2824
2940
|
});
|
|
@@ -2847,7 +2963,13 @@ window.__ModuleLoader__.load({
|
|
|
2847
2963
|
return (await call(ROUTES.delete, request)).removed;
|
|
2848
2964
|
};
|
|
2849
2965
|
const pickDirectory = async () => {
|
|
2850
|
-
|
|
2966
|
+
if (remote?.directoryPicker !== void 0) {
|
|
2967
|
+
const result = await remote.directoryPicker.pick();
|
|
2968
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
2969
|
+
return result.value;
|
|
2970
|
+
}
|
|
2971
|
+
if (legacyApi?.host === void 0) throw new Error("当前 DSH 未提供目录选择能力");
|
|
2972
|
+
const response = await legacyApi.host.pickDirectory({});
|
|
2851
2973
|
if (!response.result.ok) throw new Error(response.result.error.message);
|
|
2852
2974
|
return response.result.value.path;
|
|
2853
2975
|
};
|
|
@@ -43,7 +43,7 @@ export interface SkillImporterInjected {
|
|
|
43
43
|
checkForUpdates: () => Promise<UpdateStatusResponse>;
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
/**
|
|
46
|
+
/** Every client service used during apply is explicit; alpha DSH no longer tolerates load-order access. */
|
|
47
47
|
export declare const inject: string[];
|
|
48
48
|
/**
|
|
49
49
|
* Client plugin body: register the section and its dictionaries.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-skill-importer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Web UI plugin for DeepSeek Harness: import skills from local Markdown files or URLs into the skill roots the harness discovers automatically",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/tests/update.test.mjs
CHANGED
|
@@ -14,7 +14,7 @@ test('orders prereleases before stable releases', () => {
|
|
|
14
14
|
})
|
|
15
15
|
|
|
16
16
|
test('reads package version and produces an exact update command', () => {
|
|
17
|
-
assert.equal(currentVersion(), '0.2.
|
|
17
|
+
assert.equal(currentVersion(), '0.2.5')
|
|
18
18
|
assert.equal(
|
|
19
19
|
updateCommand('0.2.4'),
|
|
20
20
|
'npx @deepseek-ai/dsh plugin --profile web add dsh-skill-importer@0.2.4',
|