@x1a0f3n9/dsh-client-ui-skill 0.1.5-rc.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/ui-skill/README.md
5
+ README.md: a1e9e7047f102f2610160905727450833b540082
6
+ README.zh.md: d7669b11fc7249c857e20f3c52f93b8bfd214281
package/README.md ADDED
@@ -0,0 +1,112 @@
1
+ ---
2
+ description: "Web skill references and the dedicated skill tool row for the dsh web client: the /-triggered skill source and the skill call card."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-client-ui-skill
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ `dsh-client-ui-skill` lets users invoke a skill by choosing it from the `/` suggestions or typing `/name` directly. The same literal command loads the skill consistently from the Web composer, TUI, and ACP, while a name shared with a host command continues to resolve as that command. Skill calls appear in the conversation as expandable `Instructions` cards whose settled contents remain stable when the installed skill catalog changes.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Type `/` in the composer and pick a skill from the suggestions, or type `/name` directly; the sent message carries the literal text, and the host loads the skill the same way for a menu pick or a hand-typed token. A name shared with a host command still resolves to the command — adjudication claims the line client-side before it ever becomes a prompt.
29
+
30
+ ### What the source offers
31
+
32
+ Ordinary-session candidates come from the `skills/list` Remote; the host serves every user-invocable skill, and a `modelInvocable: false` entry (a `disable-model-invocation` skill, whose only entry point is this path) wears the user-only marker as a description prefix in the active language. Results rank through the `/` menu's shared name ranker, `rankByName` from ui-primitives: the query matches a case-insensitive ordered subsequence of the skill name, prefix hits rank first, and ties keep the host order ([ranking decision](../../../.agents/notes/archived/feature/2026-08-04-web-slash-command-fuzzy-discovery.md)). A failed `skills/list` call is logged and folded into a silent menu-group drop — the menu shows only pending/ready states.
33
+
34
+ ### The skill tool row
35
+
36
+ A collapsed row renders the skill glyph, `Skill` title, and requested skill name; running calls carry the transcript shimmer, failures replace the name with the first error line, and interrupted calls use the warning state. A settled row expands into a bounded `Instructions` card containing the exact durable tool output, with the standard trajectory `Inspect` affordance when available. The row derives its name, lifecycle, and body only from the frozen call/result slice supplied by ui-tool, never from the current catalog, so replay stays stable when installed skills or their descriptions change.
37
+
38
+ Hovering over `/name` highlights the entire reference. Clicking a known skill opens its provider-supplied `SKILL.md` path in the right Sidebar while keeping the token editable. An uncached click shares the per-Session catalog fetch and opens when it completes, retaining the clicked Session address. Preset changes, connection resets, and plugin disposal cancel pending previews; a later click fetches the current catalog again. Skills without a file path remain invocable but have no file preview.
39
+
40
+ -----
41
+
42
+ <a id="understand-the-implementation"></a>
43
+ ## Understand the implementation
44
+
45
+ <details>
46
+ <summary>Implementation internals — click to expand</summary>
47
+
48
+ The source implements no adjudication hooks and no reference codec: the pick lands literal text and the prompt ships the same literal, so determinism lives host-side ([slash pipeline note](../../../.agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md)).
49
+
50
+ ### Candidate flow
51
+
52
+ Catalogs cache per ordinary session with a single-flight fetch; the scope-birth `warm` hook prewarms the session's entry, the forwarded `agent-preset/selected` owner event drops that one session's entry (the catalog belongs to the preset, and a blank session may switch after the warm), and `connection/reset` clears everything. Catalog-addressed continuable children resolve no skill candidates locally because the existing skill RPC requires an attached session; viewing their persisted history must not activate them. The list RPC rides the plugin's root-context connection captured at registration; draft chip visuals derive from the `lexicon` scan.
53
+
54
+ ### Registration
55
+
56
+ The `/client` exports are the plugin body (`apply`/`inject`) only; the source object is internal to the registration effect. The tool row registers the `skill` wire name in ui-tool's keyed `tool.call.toolview` slot.
57
+
58
+ </details>
59
+
60
+ -----
61
+
62
+ <a id="further-exploration"></a>
63
+ ## Further Exploration
64
+
65
+ These pages cover the input machinery, the tool row host, and the host-side skill tool.
66
+
67
+ - [ui-input-trigger](../ui-input-trigger/README.md) — the inline suggestion machinery the source registers into.
68
+ - [ui-tool](../ui-tool/README.md) — the tool-call presentation layer hosting the `tool.call.toolview` slot.
69
+ - [tool-skill](../../skill/tool-skill/README.md) — the host-side `skill` tool owning the pre-step gesture boundary.
70
+ - [Web input machine and slash pipeline](../../../.agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md) — how references and commands share the input machine.
71
+
72
+ -----
73
+
74
+ <a id="model-experience"></a>
75
+ ## Model Experience
76
+
77
+ ### User-explicit skill invocation
78
+
79
+ #### What the model sees
80
+
81
+ The user's message reaches the model verbatim, `/name` literal included. The host's pre-step boundary (`dsh-tool-skill`) then appends the canonical `<skill_content>` block — the same `renderSkillContent` output the `skill` tool returns — as injected instructions context at the end of that step's injections, closest to the model's answer. Loading is deterministic: the model receives the full body without being asked to call the `skill` tool, and the catalog tells it not to re-load an inline-injected skill.
82
+
83
+ #### Token effect
84
+
85
+ One invocation adds the rendered skill body to that turn as injected context — the same cost as the model loading the skill through the tool, paid unconditionally instead of at the model's discretion. Menu browsing and the candidate fetch add zero model tokens.
86
+
87
+ #### KV Cache effect
88
+
89
+ Append-only: the injected message lands after the reusable history prefix. This package never edits earlier request tokens.
90
+
91
+ ## Known Limitations and Deferred Work
92
+
93
+ <a id="known-limitations-and-deferred-work"></a>
94
+
95
+
96
+ These limits define where the reference and the row fall back to generic behavior; they are current package constraints.
97
+
98
+ - **Result-only history pages use the generic row** — keyed dispatch needs the paired call in the runtime window; pagination that leaves the call outside has no tool identity. This client presentation feature does not extend the history wire contract to recover it.
99
+ - **Text is the truth** — the reference is plain draft text; a hand-typed identical token is the same reference, and the host gesture boundary judges the sent text, not the menu interaction. Chip visuals derive from the lexicon scan; no occurrence identity, position tracking, or structured reference payload exists on the prompt wire.
100
+ - **A menu opened before the prewarm settles** shows no skill candidates for that keystroke; the next keystroke re-polls the settled cache.
101
+
102
+ <a id="dev-note"></a>
103
+ ### Dev Note
104
+
105
+ <details>
106
+ <summary>Working context for maintainers — click to expand</summary>
107
+
108
+ None.
109
+
110
+ </details>
111
+
112
+ **Runtime invariant:** No companion is published. The slash source, locale dictionaries, and keyed toolview are registry-owned registrations whose disposal is proven by the HMR-safety spec. They emit no Cordis events and own no cross-plugin mutable state.
package/README.zh.md ADDED
@@ -0,0 +1,112 @@
1
+ ---
2
+ description: "dsh Web 客户端的 skill 引用与专属 skill 工具行:/ 触发的 skill source 与 skill 调用卡片。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-client-ui-skill
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ `dsh-client-ui-skill` 让用户通过 `/` 建议选择或直接键入 `/name` 来调用 skill(技能)。同一条字面命令可以从 Web 编辑器、TUI 和 ACP(Agent Client Protocol)一致地加载 skill;如果名称与宿主命令相同,它仍会解析为该命令。skill 调用在对话中显示为可展开的 `Instructions` 卡片;即使已安装的 skill 目录发生变化,卡片落定后的内容仍保持稳定。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 在编辑器中输入 `/` 并从建议中选择 skill,或直接键入 `/name`;发出的消息携带字面文本,宿主对菜单 pick 与手动键入的 token 以同样方式加载 skill。与宿主命令同名的名称仍解析为命令——裁决在客户端把该行认领走,它根本不会成为提示词。
29
+
30
+ ### source 提供什么
31
+
32
+ 普通会话的候选来自 `skills/list` Remote;宿主提供每一个用户可调用的 skill,`modelInvocable: false` 的条目(即 `disable-model-invocation` skill,此路径是其唯一入口)会以当前语言把仅限用户标记作为描述前缀带上。结果经 `/` 菜单共享的名字排序器(ui-primitives 的 `rankByName`)排名:查询作为不区分大小写的有序子序列匹配 skill 名,前缀命中排最前,同分保持宿主顺序([排名决策](../../../.agents/notes/archived/feature/2026-08-04-web-slash-command-fuzzy-discovery.md))。`skills/list` 调用失败时会被记录并静默丢弃该菜单组——菜单只显示 pending/ready 状态。
33
+
34
+ ### skill 工具行
35
+
36
+ 收起的行显示 skill 图标、`Skill` 标题与请求加载的 skill 名称;运行中的调用带有 transcript(文本记录)的扫光效果,失败时用错误首行替换名称,中断的调用使用警告状态。已结算的行展开为一个尺寸受限的 `Instructions` 卡片,其中原样呈现持久化的工具输出;可用时还会提供标准轨迹的 `Inspect` 入口。该行的名称、生命周期与正文只派生自 ui-tool 提供的冻结调用/结果切片,绝不读取当前目录,因此即使已安装的 skill 或其描述发生变化,回放仍保持稳定。
37
+
38
+ 鼠标移入 `/name` 时,背景覆盖整个引用。点击已知 skill 会在右侧栏打开提供方给出的 `SKILL.md` 路径,同时保留文本的可编辑性。缓存未就绪时,点击复用该 Session 的目录请求,在完成后按点击时的 Session 地址打开预览。切换预设、重置连接和插件释放会取消待处理的预览;后续点击重新获取当前目录。没有文件路径的 skill 仍可调用,但没有文件预览。
39
+
40
+ -----
41
+
42
+ <a id="understand-the-implementation"></a>
43
+ ## 理解实现
44
+
45
+ <details>
46
+ <summary>实现细节——点击展开</summary>
47
+
48
+ source 不实现任何裁决钩子,也没有引用 codec:pick 落下字面文本,发出的提示词中也是同一段字面文本,因此确定性在宿主侧([slash 流水线笔记](../../../.agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md))。
49
+
50
+ ### 候选流程
51
+
52
+ 目录按普通会话缓存,拉取走 single-flight;scope 创建时的 `warm` 钩子预热该会话的缓存项,转发的 owner 事件 `agent-preset/selected` 丢弃该会话这一项(目录属于 preset,而空会话可能在预热之后才切换),`connection/reset` 清空全部缓存。由目录寻址的可继续 subagent 在客户端解析为没有 skill 候选,因为现有 skill RPC 要求会话已挂载;查看其持久化历史不得激活它。列表 RPC 使用插件注册时捕获的根上下文连接;草稿 chip 视觉由 `lexicon` 扫描派生。
53
+
54
+ ### 注册
55
+
56
+ `/client` 导出接口只有插件主体(`apply`/`inject`);source 对象是注册 effect 的内部实现。工具行把 `skill` wire 名称注册进 ui-tool 的 keyed `tool.call.toolview` slot。
57
+
58
+ </details>
59
+
60
+ -----
61
+
62
+ <a id="further-exploration"></a>
63
+ ## 进一步探索
64
+
65
+ 以下页面覆盖输入机制、工具行宿主与宿主侧 skill 工具。
66
+
67
+ - [ui-input-trigger](../ui-input-trigger/README.zh.md)——该 source 注册进的行内建议机制。
68
+ - [ui-tool](../ui-tool/README.zh.md)——承载 `tool.call.toolview` slot 的工具调用展示层。
69
+ - [tool-skill](../../skill/tool-skill/README.zh.md)——拥有 pre-step 手势边界的宿主侧 `skill` 工具。
70
+ - [Web 输入机器与 slash 流水线](../../../.agents/notes/archived/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md)——引用与命令如何共享输入机器。
71
+
72
+ -----
73
+
74
+ <a id="model-experience"></a>
75
+ ## 模型体验
76
+
77
+ ### 用户显式 skill 调用
78
+
79
+ #### 模型看到的内容
80
+
81
+ 用户消息原样到达模型,字面文本 `/name` 也包含在内。随后宿主的 pre-step 边界(`dsh-tool-skill`)把规范的 `<skill_content>` 块——与 `skill` 工具返回的 `renderSkillContent` 输出相同——作为注入的指令上下文追加在该步骤各项注入的末尾,最贴近模型的回答。加载是确定性的:模型无需被要求调用 `skill` 工具就能收到完整正文,目录也会告诉它不要重新加载已内联注入的 skill。
82
+
83
+ #### Token 影响
84
+
85
+ 一次调用会把渲染后的 skill 正文作为注入上下文加进该轮次——成本与模型经由工具加载该 skill 相同,只是无条件支付,而非由模型自行裁量。浏览菜单和拉取候选不会增加任何模型 token。
86
+
87
+ #### KV Cache 影响
88
+
89
+ 仅追加:注入的消息落在可复用历史前缀之后。该包绝不改写较早的请求 token。
90
+
91
+ ## 已知限制与延期工作
92
+
93
+ <a id="known-limitations-and-deferred-work"></a>
94
+
95
+
96
+ 这些限制定义引用与工具行何时回退到通用行为;它们是当前包约束。
97
+
98
+ - **仅含工具结果的 history 页使用通用行**:键控分派要求配对的工具调用位于运行时窗口内;分页将工具调用留在窗口外时,工具结果没有工具身份。这项客户端呈现功能不会为了恢复该身份而扩展 history 协议约定。
99
+ - **文本是唯一依据**:引用是普通的草稿文本;手动键入的相同 token 就是同一个引用,宿主手势边界评判的是发出的文本,而不是菜单交互。chip 视觉由 lexicon 扫描派生;提示词协议上没有 occurrence 身份、位置跟踪或结构化引用载荷。
100
+ - **预热落定之前打开的菜单**:在那次击键下不显示 skill 候选;下一次击键会重新轮询已落定的缓存。
101
+
102
+ <a id="dev-note"></a>
103
+ ### 开发备注
104
+
105
+ <details>
106
+ <summary>维护者的工作上下文——点击展开</summary>
107
+
108
+ 无。
109
+
110
+ </details>
111
+
112
+ **运行时不变式:** 不发布伴生入口。slash source、locale dictionary 与 keyed toolview 都是由注册表持有的注册项,其释放行为已由 HMR(热模块替换)安全规范证明;它们不发出 Cordis 事件或持有跨插件可变状态。
package/lib/client.js ADDED
@@ -0,0 +1,411 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@x1a0f3n9/dsh-client-ui-skill",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _x1a0f3n9_dsh_client_ui_primitives = require("@x1a0f3n9/dsh-client-ui-primitives");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ let react = require("react");
10
+ //#region ../../util/workspace-path/src/file-address.ts
11
+ /** The scheme and type every file address opens with. */
12
+ const FILE_ADDRESS_PREFIX = "dsh-resource://file/";
13
+ /** Component-encode one id or path segment, keeping `:` literal for drive letters. */
14
+ function encodeSegment(segment) {
15
+ return encodeURIComponent(segment).replace(/%3A/gi, ":");
16
+ }
17
+ /** Encode a `/`-separated path segment by segment. */
18
+ function encodePath(path) {
19
+ return path.split("/").map(encodeSegment).join("/");
20
+ }
21
+ /**
22
+ * Build the address of a file read through one Session.
23
+ * @param sessionId - the Session whose Host workspace resolves the path.
24
+ * @param path - absolute or workspace-relative path; backslashes are normalized to `/`, and leading `./` prefixes are dropped.
25
+ * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
26
+ */
27
+ function sessionFileAddress(sessionId, path) {
28
+ const normalized = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
29
+ return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(normalized)}`;
30
+ }
31
+ //#endregion
32
+ //#region ../../util/workspace-path/src/index.ts
33
+ /**
34
+ * Browser-safe Workspace path and display helpers.
35
+ * @module @x1a0f3n9/dsh-util-workspace-path
36
+ */
37
+ /** Whether a path uses a Windows drive or UNC prefix. */
38
+ function isWindowsStylePath(value) {
39
+ return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
40
+ }
41
+ /**
42
+ * Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
43
+ * @param path - the path to classify.
44
+ * @returns `true` for an absolute path; `false` for a Workspace-relative one.
45
+ */
46
+ function isAbsoluteWorkspacePath(path) {
47
+ return path.startsWith("/") || isWindowsStylePath(path);
48
+ }
49
+ /**
50
+ * The address for a path as a caller holds it: a relative path, or an absolute
51
+ * path inside the Session's workspace, becomes a `session`-scoped address; an
52
+ * absolute path outside it, or one whose workspace root is unknown, keeps its
53
+ * absolute path in that Session's address.
54
+ * @param sessionId - the Session the path is read in.
55
+ * @param cwd - that Session's workspace root, when known.
56
+ * @param path - absolute or workspace-relative path, in either separator spelling.
57
+ * @returns the `dsh-resource://file/…` address.
58
+ */
59
+ function fileAddressFor(sessionId, cwd, path) {
60
+ const normalized = path.replace(/\\/g, "/");
61
+ if (!isAbsoluteWorkspacePath(normalized)) return sessionFileAddress(sessionId, normalized);
62
+ const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
63
+ if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
64
+ if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
65
+ return sessionFileAddress(sessionId, normalized);
66
+ }
67
+ //#endregion
68
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-skill/src/client/SkillRow.module.css.mjs
69
+ const css = ".iWrAna_card{flex-direction:column;display:flex}.iWrAna_row{align-items:center;min-width:0;height:24px;display:flex;position:relative;overflow:hidden}.iWrAna_row[data-expandable]{cursor:pointer}.iWrAna_card[data-state=running] .iWrAna_row:after{content:\"\";background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite iWrAna_dsh-skill-row-sweep;position:absolute;inset:0 auto 0 0}@keyframes iWrAna_dsh-skill-row-sweep{0%{left:-300px}90%,to{left:100%}}.iWrAna_leading{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex;position:relative}.iWrAna_chevron{color:var(--dsw-alias-label-secondary)}.iWrAna_iconIdle{opacity:1;transition:opacity .1s;display:inline-flex}.iWrAna_chevronHover{opacity:0;margin:auto;transition:opacity .1s;position:absolute;inset:0}.iWrAna_row:hover .iWrAna_iconIdle{opacity:0}.iWrAna_row:hover .iWrAna_chevronHover{opacity:1}.iWrAna_title{color:var(--dsw-alias-label-secondary);flex:none;font-size:14px;line-height:24px}.iWrAna_separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.iWrAna_summary{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-tertiary);flex:auto;font-size:14px;line-height:24px;overflow:hidden}.iWrAna_errorSummary{color:var(--dsw-alias-state-error-primary)}.iWrAna_bodyWrap{flex-direction:column;display:flex}.iWrAna_instructionsCard{border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-markdown-code-block);border-radius:12px;flex-direction:column;max-height:260px;margin:4px 0 4px 4px;display:flex;overflow:hidden}.iWrAna_instructionsHeader{border-bottom:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-markdown-code-block-banner);color:var(--dsw-alias-label-caption);text-transform:uppercase;letter-spacing:.04em;flex:none;padding:8px 12px;font-size:11px;font-weight:500;line-height:16px}.iWrAna_instructions{white-space:pre-wrap;overflow-wrap:anywhere;min-height:0;font:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-secondary);margin:0;padding:10px 12px 12px;overflow:auto}.iWrAna_instructions[data-error]{color:var(--dsw-alias-state-error-primary)}.iWrAna_instructions::-webkit-scrollbar-thumb{background-clip:padding-box;border:2px solid #0000;border-radius:6px}.iWrAna_instructions::-webkit-scrollbar-track{margin:6px 0}.iWrAna_inspectButton{border:.5px solid var(--dsw-alias-border-l4);corner-shape:round;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary);cursor:pointer;opacity:0;border-radius:999px;align-self:flex-start;align-items:center;gap:4px;margin:4px 0 2px 4px;padding:2px 8px;font-size:11px;line-height:16px;transition:opacity .1s;display:inline-flex}.iWrAna_card:hover .iWrAna_inspectButton,.iWrAna_inspectButton:focus-visible{opacity:1}.iWrAna_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.iWrAna_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (prefers-reduced-motion:reduce){.iWrAna_card[data-state=running] .iWrAna_row:after{animation:none;display:none}.iWrAna_iconIdle,.iWrAna_chevronHover,.iWrAna_inspectButton{transition:none}}";
70
+ const tagId = "@x1a0f3n9/dsh-client-ui-skill/SkillRow.module.css";
71
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
72
+ const tag = document.createElement("style");
73
+ tag.dataset.plugin = "@x1a0f3n9/dsh-client-ui-skill";
74
+ tag.dataset.pluginCss = tagId;
75
+ tag.textContent = css;
76
+ document.head.appendChild(tag);
77
+ }
78
+ var SkillRow_module_css_default = {
79
+ "bodyWrap": "iWrAna_bodyWrap",
80
+ "card": "iWrAna_card",
81
+ "chevron": "iWrAna_chevron",
82
+ "chevronHover": "iWrAna_chevronHover",
83
+ "dsh-skill-row-sweep": "iWrAna_dsh-skill-row-sweep",
84
+ "errorSummary": "iWrAna_errorSummary",
85
+ "iconIdle": "iWrAna_iconIdle",
86
+ "inspectButton": "iWrAna_inspectButton",
87
+ "instructions": "iWrAna_instructions",
88
+ "instructionsCard": "iWrAna_instructionsCard",
89
+ "instructionsHeader": "iWrAna_instructionsHeader",
90
+ "leading": "iWrAna_leading",
91
+ "row": "iWrAna_row",
92
+ "separator": "iWrAna_separator",
93
+ "summary": "iWrAna_summary",
94
+ "title": "iWrAna_title",
95
+ "visuallyHidden": "iWrAna_visuallyHidden"
96
+ };
97
+ //#endregion
98
+ //#region lib/types/client/SkillRow.js
99
+ /** First physical line for the collapsed error summary and malformed-args fallback. */
100
+ function firstLine(text) {
101
+ const newline = text.indexOf("\n");
102
+ return newline === -1 ? text : text.slice(0, newline);
103
+ }
104
+ /** Skill names are the only call argument the compact row presents. */
105
+ function skillName(argsRaw, callId) {
106
+ try {
107
+ const parsed = JSON.parse(argsRaw);
108
+ if (typeof parsed === "object" && parsed !== null) {
109
+ const name = parsed.name;
110
+ if (typeof name === "string" && name !== "") return firstLine(name);
111
+ }
112
+ } catch {}
113
+ return argsRaw === "" ? callId : firstLine(argsRaw);
114
+ }
115
+ /** Flatten durable result blocks under the generic Tool-row text contract.
116
+ * Keep aligned with ui-tool's models/tool-call-model.ts `resultText`. */
117
+ function resultText(block) {
118
+ if (!("kind" in block)) return null;
119
+ const parts = [];
120
+ for (const item of block.content) parts.push(item.type === "text" ? item.text : JSON.stringify(item, null, 2));
121
+ if (parts.length === 0 && block.error !== void 0) parts.push(`${block.error.name}: ${block.error.code}`);
122
+ return parts.join("\n") || null;
123
+ }
124
+ /** Derive display state without consulting the live skill catalog. */
125
+ function skillRowModel(block) {
126
+ const settled = "kind" in block;
127
+ const argsRaw = (settled ? block.call?.argsRaw : block.argsRaw) ?? "";
128
+ const state = !settled ? "running" : block.error?.code === "interrupted" ? "stopped" : block.isError ? "error" : "ok";
129
+ const output = resultText(block);
130
+ return {
131
+ name: skillName(argsRaw, block.callId),
132
+ output,
133
+ errorSummary: state === "error" && output !== null ? firstLine(output) : null,
134
+ state
135
+ };
136
+ }
137
+ /** State substitution for the collapsed leading slot. */
138
+ function leadingFor(state) {
139
+ switch (state) {
140
+ case "error": return (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.StateDot, { state: "error" });
141
+ case "stopped": return (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.StateDot, { state: "warning" });
142
+ default: return (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconSkillOutline16, { size: 14 });
143
+ }
144
+ }
145
+ /** Leading disclosure slot: state icon at rest, chevron on hover or while open. */
146
+ function disclosureLeading(state, open, expandable) {
147
+ if (open) return (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconChevronDownOutline14, { className: SkillRow_module_css_default.chevron });
148
+ const icon = leadingFor(state);
149
+ if (!expandable) return icon;
150
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
151
+ className: SkillRow_module_css_default.iconIdle,
152
+ children: icon
153
+ }), (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconChevronDownOutline14, { className: `${SkillRow_module_css_default.chevron} ${SkillRow_module_css_default.chevronHover}` })] });
154
+ }
155
+ /** Visually hidden state copy for the colour-only lifecycle cues. */
156
+ function stateStatus(state, t) {
157
+ switch (state) {
158
+ case "running": return t("row.running");
159
+ case "error": return t("row.failed");
160
+ case "stopped": return t("row.stopped");
161
+ default: return null;
162
+ }
163
+ }
164
+ /**
165
+ * Render one `skill` tool call as an accent summary and instructions disclosure.
166
+ * @param props - keyed toolview payload plus the skill locale seat.
167
+ * @returns the dedicated skill row.
168
+ */
169
+ function SkillRow({ block, inspect, t }) {
170
+ const model = skillRowModel(block);
171
+ const [expanded, setExpanded] = (0, react.useState)(false);
172
+ const expandable = model.output !== null;
173
+ const open = expanded && expandable;
174
+ const status = stateStatus(model.state, t);
175
+ const summary = model.errorSummary ?? model.name;
176
+ const toggleExpand = () => {
177
+ setExpanded((value) => !value);
178
+ };
179
+ const toggleFromKeyboard = (event) => {
180
+ if (!expandable || event.key !== "Enter" && event.key !== " ") return;
181
+ event.preventDefault();
182
+ toggleExpand();
183
+ };
184
+ const disclosureProps = expandable ? {
185
+ role: "button",
186
+ tabIndex: 0,
187
+ "aria-expanded": open,
188
+ onClick: toggleExpand,
189
+ onKeyDown: toggleFromKeyboard
190
+ } : {};
191
+ const leading = disclosureLeading(model.state, open, expandable);
192
+ return (0, react_jsx_runtime.jsxs)("div", {
193
+ className: SkillRow_module_css_default.card,
194
+ "data-tool": "skill",
195
+ "data-state": model.state,
196
+ children: [(0, react_jsx_runtime.jsxs)("div", {
197
+ className: SkillRow_module_css_default.row,
198
+ "data-expandable": expandable || void 0,
199
+ ...disclosureProps,
200
+ children: [
201
+ (0, react_jsx_runtime.jsx)("span", {
202
+ className: SkillRow_module_css_default.leading,
203
+ children: leading
204
+ }),
205
+ status !== null ? (0, react_jsx_runtime.jsx)("span", {
206
+ className: SkillRow_module_css_default.visuallyHidden,
207
+ children: status
208
+ }) : null,
209
+ (0, react_jsx_runtime.jsx)("span", {
210
+ className: SkillRow_module_css_default.title,
211
+ children: t("row.title")
212
+ }),
213
+ (0, react_jsx_runtime.jsx)("span", {
214
+ className: SkillRow_module_css_default.separator,
215
+ "aria-hidden": true
216
+ }),
217
+ (0, react_jsx_runtime.jsx)("span", {
218
+ className: model.errorSummary === null ? SkillRow_module_css_default.summary : `${SkillRow_module_css_default.summary} ${SkillRow_module_css_default.errorSummary}`,
219
+ children: summary
220
+ })
221
+ ]
222
+ }), open ? (0, react_jsx_runtime.jsxs)("div", {
223
+ className: SkillRow_module_css_default.bodyWrap,
224
+ children: [(0, react_jsx_runtime.jsxs)("section", {
225
+ className: SkillRow_module_css_default.instructionsCard,
226
+ "aria-label": t("row.instructions"),
227
+ children: [(0, react_jsx_runtime.jsx)("div", {
228
+ className: SkillRow_module_css_default.instructionsHeader,
229
+ children: t("row.instructions")
230
+ }), (0, react_jsx_runtime.jsx)("pre", {
231
+ className: SkillRow_module_css_default.instructions,
232
+ "data-error": model.state === "error" || void 0,
233
+ children: model.output
234
+ })]
235
+ }), inspect !== void 0 ? (0, react_jsx_runtime.jsxs)("button", {
236
+ type: "button",
237
+ className: SkillRow_module_css_default.inspectButton,
238
+ onClick: inspect,
239
+ children: [(0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconInspectOutline12, {}), t("row.inspect")]
240
+ }) : null]
241
+ }) : null]
242
+ });
243
+ }
244
+ //#endregion
245
+ //#region lib/types/client/locales.js
246
+ /** `skill` namespace dictionaries for the dedicated tool row. */
247
+ /** Dictionary namespace owned by this plugin. */
248
+ const NS = "skill";
249
+ /** Simplified Chinese dictionary (the key-set source of truth). */
250
+ const zh = {
251
+ "row.title": "Skill",
252
+ "row.running": "正在加载 skill",
253
+ "row.failed": "skill 加载失败",
254
+ "row.stopped": "skill 加载已中止",
255
+ "row.instructions": "说明",
256
+ "row.inspect": "查看",
257
+ "menu.userOnly": "仅用户"
258
+ };
259
+ /** English dictionary, checked complete against the zh key set. */
260
+ const en = {
261
+ "row.title": "Skill",
262
+ "row.running": "Loading skill",
263
+ "row.failed": "Skill load failed",
264
+ "row.stopped": "Skill load stopped",
265
+ "row.instructions": "Instructions",
266
+ "row.inspect": "Inspect",
267
+ "menu.userOnly": "user-only"
268
+ };
269
+ //#endregion
270
+ //#region lib/types/client/index.js
271
+ /** Required services: reference source faces plus the tool-row and locale registries. */
272
+ const inject = [
273
+ "inputTriggers",
274
+ "sessions",
275
+ "slots",
276
+ "locale",
277
+ "remote",
278
+ "remote.skills",
279
+ "sidebarRight"
280
+ ];
281
+ /**
282
+ * Client plugin body: register the '/' source, dictionaries, and keyed tool row.
283
+ * @param ctx - client root context.
284
+ */
285
+ function apply(ctx) {
286
+ ctx.effect(() => ctx.locale.register(NS, {
287
+ zh,
288
+ en
289
+ }), "ui-skill: dictionaries");
290
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
291
+ name: "tool.call.toolview",
292
+ key: "skill",
293
+ locale: NS
294
+ }, SkillRow));
295
+ const skills = ctx.remote.skills;
296
+ const sessions = ctx.sessions;
297
+ const fetches = /* @__PURE__ */ new Map();
298
+ const lexiconListeners = /* @__PURE__ */ new Map();
299
+ const notifyLexicon = (sessionId) => {
300
+ for (const listener of [...lexiconListeners.get(sessionId) ?? []]) try {
301
+ listener();
302
+ } catch (error) {
303
+ console.error("[ui-skill] lexicon listener failed:", error);
304
+ }
305
+ };
306
+ const fetchCatalog = (sessionId) => {
307
+ const existing = fetches.get(sessionId);
308
+ if (existing !== void 0) return existing;
309
+ const abort = new AbortController();
310
+ const promise = (async () => {
311
+ const result = await skills.list({ sessionId }, abort.signal);
312
+ abort.signal.throwIfAborted();
313
+ if (!result.ok) throw new Error(`skills/list failed: ${result.error.code}: ${result.error.message}`);
314
+ return result.value.skills;
315
+ })();
316
+ const entry = {
317
+ promise,
318
+ abort
319
+ };
320
+ fetches.set(sessionId, entry);
321
+ promise.then((skills) => {
322
+ entry.settled = skills;
323
+ notifyLexicon(sessionId);
324
+ }, () => {
325
+ if (fetches.get(sessionId) === entry) fetches.delete(sessionId);
326
+ });
327
+ return entry;
328
+ };
329
+ const invalidate = (key) => {
330
+ const entry = fetches.get(key);
331
+ if (entry === void 0) return;
332
+ fetches.delete(key);
333
+ entry.abort.abort();
334
+ notifyLexicon(key);
335
+ };
336
+ const clearAll = () => {
337
+ for (const key of [...fetches.keys()]) invalidate(key);
338
+ };
339
+ const t = ctx.locale.bind(NS);
340
+ const source = {
341
+ trigger: "/",
342
+ name: "skill",
343
+ order: 2,
344
+ async candidates(session, { query, signal }) {
345
+ if (sessions.subagentAddress(session.sessionId) !== void 0) return [];
346
+ const skills = await fetchCatalog(session.sessionId).promise;
347
+ if (signal.aborted) return [];
348
+ return (0, _x1a0f3n9_dsh_client_ui_primitives.rankByName)(skills, query).map((skill) => ({
349
+ name: skill.name,
350
+ description: skill.modelInvocable ? skill.description : `${t("menu.userOnly")} · ${skill.description}`
351
+ }));
352
+ },
353
+ warm(session) {
354
+ if (sessions.subagentAddress(session.sessionId) !== void 0) return;
355
+ fetchCatalog(session.sessionId).promise.catch(() => {});
356
+ },
357
+ lexicon(session) {
358
+ return fetches.get(session.sessionId)?.settled?.map((skill) => skill.name);
359
+ },
360
+ subscribeLexicon(session, listener) {
361
+ const key = session.sessionId;
362
+ const listeners = lexiconListeners.get(key) ?? /* @__PURE__ */ new Set();
363
+ listeners.add(listener);
364
+ lexiconListeners.set(key, listeners);
365
+ return () => {
366
+ listeners.delete(listener);
367
+ if (listeners.size === 0) lexiconListeners.delete(key);
368
+ };
369
+ },
370
+ openReference(session, { ref }) {
371
+ if (sessions.subagentAddress(session.sessionId) !== void 0) return false;
372
+ const cwd = sessions.list.getSnapshot().byId[session.sessionId]?.cwd;
373
+ const open = (catalog) => {
374
+ const path = catalog.find((skill) => `/${skill.name}` === ref)?.path;
375
+ if (path === void 0) return false;
376
+ ctx.sidebarRight.openResource(fileAddressFor(session.sessionId, cwd, path));
377
+ return true;
378
+ };
379
+ const settled = fetches.get(session.sessionId)?.settled;
380
+ if (settled !== void 0) return open(settled);
381
+ const entry = fetchCatalog(session.sessionId);
382
+ entry.promise.then((catalog) => {
383
+ if (!entry.abort.signal.aborted) open(catalog);
384
+ }).catch((error) => {
385
+ if (!entry.abort.signal.aborted) console.error("[ui-skill] reference preview failed:", error);
386
+ });
387
+ return true;
388
+ },
389
+ onPick({ candidate }) {
390
+ return { text: `/${candidate.name} ` };
391
+ }
392
+ };
393
+ const inputTriggers = ctx.get("inputTriggers");
394
+ ctx.remote.$on("agent-preset/selected", invalidate);
395
+ ctx.on("connection/reset", clearAll);
396
+ ctx.effect(() => {
397
+ const unregister = inputTriggers.registerSource(source);
398
+ return () => {
399
+ unregister();
400
+ clearAll();
401
+ };
402
+ }, "ui-skill: source");
403
+ }
404
+ //#endregion
405
+ exports.apply = apply;
406
+ exports.inject = inject;
407
+ return module.exports;
408
+ }
409
+ });
410
+
411
+ //# sourceMappingURL=client.js.map
package/lib/index.js ADDED
@@ -0,0 +1,11 @@
1
+ //#region lib/types/index.js
2
+ /**
3
+ * Skill reference plugin, node half. Pure UI plugin: the empty apply
4
+ * exists so the plugin appears in the host cordis.yml / Loader; the browser
5
+ * half ships via exports["./client"], discovered through the package.json
6
+ * dsh.client declaration.
7
+ */
8
+ /** Host plugin body — no host-side behavior for this source plugin. */
9
+ function apply() {}
10
+ //#endregion
11
+ export { apply };
@@ -0,0 +1,11 @@
1
+ import type { ToolCallViewProps } from '@x1a0f3n9/dsh-client-ui-tool/client';
2
+ import type { PropsLocale } from '@x1a0f3n9/dsh-client-ui-slots';
3
+ type SkillRowProps = ToolCallViewProps & PropsLocale<'skill'>;
4
+ /**
5
+ * Render one `skill` tool call as an accent summary and instructions disclosure.
6
+ * @param props - keyed toolview payload plus the skill locale seat.
7
+ * @returns the dedicated skill row.
8
+ */
9
+ export declare function SkillRow({ block, inspect, t }: SkillRowProps): import("react").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=SkillRow.d.ts.map
@@ -0,0 +1,16 @@
1
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
+ import { type SkillKey } from './locales.ts';
3
+ declare module '@x1a0f3n9/dsh-client-ui-slots' {
4
+ interface LocaleNamespaceMap {
5
+ /** The dedicated skill tool row's copy. */
6
+ skill: SkillKey;
7
+ }
8
+ }
9
+ /** Required services: reference source faces plus the tool-row and locale registries. */
10
+ export declare const inject: string[];
11
+ /**
12
+ * Client plugin body: register the '/' source, dictionaries, and keyed tool row.
13
+ * @param ctx - client root context.
14
+ */
15
+ export declare function apply(ctx: ClientContext): void;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,26 @@
1
+ /** `skill` namespace dictionaries for the dedicated tool row. */
2
+ /** Dictionary namespace owned by this plugin. */
3
+ export declare const NS = "skill";
4
+ /** Simplified Chinese dictionary (the key-set source of truth). */
5
+ export declare const zh: {
6
+ 'row.title': string;
7
+ 'row.running': string;
8
+ 'row.failed': string;
9
+ 'row.stopped': string;
10
+ 'row.instructions': string;
11
+ 'row.inspect': string;
12
+ 'menu.userOnly': string;
13
+ };
14
+ /** The skill namespace key union. */
15
+ export type SkillKey = keyof typeof zh;
16
+ /** English dictionary, checked complete against the zh key set. */
17
+ export declare const en: {
18
+ 'row.title': string;
19
+ 'row.running': string;
20
+ 'row.failed': string;
21
+ 'row.stopped': string;
22
+ 'row.instructions': string;
23
+ 'row.inspect': string;
24
+ 'menu.userOnly': string;
25
+ };
26
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Skill reference plugin, node half. Pure UI plugin: the empty apply
3
+ * exists so the plugin appears in the host cordis.yml / Loader; the browser
4
+ * half ships via exports["./client"], discovered through the package.json
5
+ * dsh.client declaration.
6
+ */
7
+ /** Host plugin body — no host-side behavior for this source plugin. */
8
+ export declare function apply(): void;
9
+ //# sourceMappingURL=index.d.ts.map
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@x1a0f3n9/dsh-client-ui-skill",
3
+ "description": "Web skill references and the dedicated skill tool row",
4
+ "version": "0.1.5-rc.3",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/client/ui-skill"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./client": {
22
+ "types": "./lib/types/client/index.d.ts",
23
+ "default": "./lib/client.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "dsh": {
29
+ "client": {
30
+ "inject": [
31
+ "@x1a0f3n9/dsh-api-session-controller",
32
+ "@x1a0f3n9/dsh-client-locale",
33
+ "@x1a0f3n9/dsh-client-ui-renderer",
34
+ "@x1a0f3n9/dsh-client-ui-tool",
35
+ "@x1a0f3n9/dsh-client-ui-input-trigger",
36
+ "@x1a0f3n9/dsh-api-remotes",
37
+ "@x1a0f3n9/dsh-client-ui-sidebar-right"
38
+ ],
39
+ "platform": "web"
40
+ }
41
+ },
42
+ "license": "MIT",
43
+ "peerDependencies": {
44
+ "@deepseek-ai/cordis": "^4.0.2"
45
+ },
46
+ "devDependencies": {
47
+ "@testing-library/react": "^16.1.0",
48
+ "@types/react": "~18.3.1",
49
+ "react": "^18.2.0",
50
+ "react-dom": "^18.2.0",
51
+ "@x1a0f3n9/dsh-api-remotes": "^0.1.5-rc.3",
52
+ "@x1a0f3n9/dsh-api-session-controller": "^0.1.5-rc.3",
53
+ "@x1a0f3n9/dsh-client-connection": "^0.1.5-rc.3",
54
+ "@x1a0f3n9/dsh-client-locale": "^0.1.5-rc.3",
55
+ "@x1a0f3n9/dsh-client-test-runtime": "^0.1.5-rc.3",
56
+ "@x1a0f3n9/dsh-client-ui-primitives": "^0.1.5-rc.3",
57
+ "@x1a0f3n9/dsh-client-ui-input-trigger": "^0.1.5-rc.3",
58
+ "@x1a0f3n9/dsh-client-ui-renderer": "^0.1.5-rc.3",
59
+ "@x1a0f3n9/dsh-client-ui-slots": "^0.1.5-rc.3",
60
+ "@x1a0f3n9/dsh-client-ui-tool": "^0.1.5-rc.3",
61
+ "@x1a0f3n9/dsh-session": "^0.1.5-rc.3",
62
+ "@deepseek-ai/cordis": "^4.0.2",
63
+ "@x1a0f3n9/dsh-client-ui-sidebar-right": "^0.1.5-rc.3",
64
+ "@x1a0f3n9/dsh-util-workspace-path": "^0.1.5-rc.3"
65
+ },
66
+ "files": [
67
+ "lib/index.js",
68
+ "lib/client.js",
69
+ "lib/types/**/*.d.ts"
70
+ ],
71
+ "scripts": {
72
+ "bundle": "tsdown",
73
+ "watch": "tsdown --watch"
74
+ }
75
+ }