dsh-video-skills 0.1.0 → 0.1.2

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 CHANGED
@@ -46,6 +46,8 @@ then append `"dsh-video-skills"` to `dsh.profile.bundles` in the same profile `p
46
46
 
47
47
  ## Tools
48
48
 
49
+ Both tools work in any composition — CLI, TUI, or Web. Only the browse-and-install page needs the Web UI; without a web server the plugin simply registers the tools and mounts no HTTP route.
50
+
49
51
  ### `video_skill_search`
50
52
 
51
53
  | Parameter | Type | Default | Description |
package/README.zh-CN.md CHANGED
@@ -46,6 +46,8 @@ dsh plugin --profile <name> add file:/绝对路径/dsh-plugin-video-skills
46
46
 
47
47
  ## 工具
48
48
 
49
+ 两个工具在任何组合里都可用——CLI、TUI、Web 都行。只有浏览安装页面需要 Web UI;没有 Web 服务时插件只注册工具,不挂任何 HTTP 路由。
50
+
49
51
  ### `video_skill_search`
50
52
 
51
53
  | 参数 | 类型 | 默认值 | 说明 |
package/client/client.js CHANGED
@@ -82,8 +82,8 @@ window.__ModuleLoader__.load({
82
82
  "videoSkills.installing": "安装中…",
83
83
  "videoSkills.installed": "已安装",
84
84
  "videoSkills.target.label": "安装到",
85
- "videoSkills.target.claude": "Claude(~/.claude/skills)",
86
- "videoSkills.target.dsh": "DSH(~/.dsh/skills)",
85
+ "videoSkills.target.claude": "Claude",
86
+ "videoSkills.target.dsh": "DSH",
87
87
  "videoSkills.files": "{n} 个文件",
88
88
  "videoSkills.error.load": "目录加载失败:{message}",
89
89
  "videoSkills.error.refresh": "刷新失败:{message}",
@@ -130,8 +130,8 @@ window.__ModuleLoader__.load({
130
130
  "videoSkills.installing": "Installing…",
131
131
  "videoSkills.installed": "Installed",
132
132
  "videoSkills.target.label": "Install to",
133
- "videoSkills.target.claude": "Claude (~/.claude/skills)",
134
- "videoSkills.target.dsh": "DSH (~/.dsh/skills)",
133
+ "videoSkills.target.claude": "Claude",
134
+ "videoSkills.target.dsh": "DSH",
135
135
  "videoSkills.files": "{n} files",
136
136
  "videoSkills.error.load": "Catalog failed to load: {message}",
137
137
  "videoSkills.error.refresh": "Refresh failed: {message}",
@@ -152,33 +152,34 @@ window.__ModuleLoader__.load({
152
152
  "videoSkills.grade.caution": "Caution"
153
153
  };
154
154
 
155
- var STYLE = {
156
- page: { display: "flex", flexDirection: "column", gap: "16px", padding: "20px", font: "14px/1.5 system-ui, -apple-system, Segoe UI, sans-serif", color: "var(--dsh-text, #e6e6e6)" },
157
- header: { display: "flex", flexDirection: "column", gap: "4px" },
158
- title: { margin: 0, fontSize: "18px", fontWeight: 600 },
159
- subtitle: { margin: 0, fontSize: "12px", opacity: 0.7 },
160
- bar: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "8px" },
161
- row: { display: "flex", alignItems: "center", gap: "8px" },
162
- chip: { padding: "3px 10px", borderRadius: "999px", border: "1px solid rgba(127,127,127,0.45)", background: "transparent", color: "inherit", cursor: "pointer", fontSize: "12px" },
163
- chipOn: { padding: "3px 10px", borderRadius: "999px", border: "1px solid var(--dsh-accent, #4f8cff)", background: "var(--dsh-accent, #4f8cff)", color: "#fff", cursor: "pointer", fontSize: "12px" },
164
- input: { padding: "5px 8px", borderRadius: "6px", border: "1px solid rgba(127,127,127,0.45)", background: "transparent", color: "inherit", fontSize: "13px" },
165
- button: { padding: "5px 12px", borderRadius: "6px", border: "1px solid rgba(127,127,127,0.45)", background: "transparent", color: "inherit", cursor: "pointer", fontSize: "13px" },
166
- primary: { padding: "5px 14px", borderRadius: "6px", border: "1px solid var(--dsh-accent, #4f8cff)", background: "var(--dsh-accent, #4f8cff)", color: "#fff", cursor: "pointer", fontSize: "13px" },
155
+ var STYLE = {
156
+ page: { display: "flex", flexDirection: "column", gap: "10px", padding: "16px", font: "13px/1.45 system-ui, -apple-system, Segoe UI, sans-serif", color: "inherit" },
157
+ header: { display: "flex", flexDirection: "column", gap: "2px" },
158
+ title: { margin: 0, fontSize: "15px", fontWeight: 600 },
159
+ subtitle: { margin: 0, fontSize: "12px", color: "color-mix(in srgb, currentColor 74%, transparent)" },
160
+ bar: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "6px" },
161
+ row: { display: "flex", alignItems: "center", gap: "6px" },
162
+ chip: { padding: "2px 9px", borderRadius: "999px", border: "1px solid color-mix(in srgb, currentColor 26%, transparent)", background: "transparent", color: "inherit", cursor: "pointer", fontSize: "11.5px", lineHeight: 1.6 },
163
+ chipOn: { padding: "2px 9px", borderRadius: "999px", border: "1px solid var(--dsh-accent, #4f8cff)", background: "var(--dsh-accent, #4f8cff)", color: "#fff", cursor: "pointer", fontSize: "11.5px", lineHeight: 1.6 },
164
+ input: { padding: "3px 8px", borderRadius: "6px", border: "1px solid color-mix(in srgb, currentColor 26%, transparent)", background: "transparent", color: "inherit", fontSize: "12.5px" },
165
+ button: { padding: "3px 10px", borderRadius: "6px", border: "1px solid color-mix(in srgb, currentColor 26%, transparent)", background: "transparent", color: "inherit", cursor: "pointer", fontSize: "12.5px" },
166
+ primary: { padding: "3px 12px", borderRadius: "6px", border: "1px solid var(--dsh-accent, #4f8cff)", background: "var(--dsh-accent, #4f8cff)", color: "#fff", cursor: "pointer", fontSize: "12.5px", fontWeight: 500 },
167
167
  list: { display: "flex", flexDirection: "column", gap: "0" },
168
- item: { display: "flex", gap: "12px", padding: "12px 0", borderTop: "1px solid rgba(127,127,127,0.22)" },
169
- preview: { width: "112px", height: "63px", objectFit: "cover", borderRadius: "6px", flex: "0 0 auto", background: "rgba(127,127,127,0.12)" },
170
- body: { display: "flex", flexDirection: "column", gap: "4px", minWidth: 0, flex: "1 1 auto" },
171
- repo: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: "13px", overflowWrap: "anywhere" },
172
- desc: { fontSize: "12px", opacity: 0.78, margin: 0 },
173
- meta: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "6px", fontSize: "11px", opacity: 0.75 },
174
- badge: { padding: "1px 7px", borderRadius: "4px", background: "rgba(127,127,127,0.18)", fontSize: "11px" },
175
- badgeOn: { padding: "1px 7px", borderRadius: "4px", background: "rgba(60,190,120,0.22)", color: "#3cbe78", fontSize: "11px" },
176
- badgeStale: { padding: "1px 7px", borderRadius: "4px", background: "rgba(230,170,60,0.2)", color: "#e6aa3c", fontSize: "11px" },
177
- actions: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "6px", flex: "0 0 auto" },
178
- error: { color: "#e5534b", fontSize: "12px", margin: 0 },
179
- errorRow: { color: "#e5534b", fontSize: "11px", maxWidth: "220px", textAlign: "right" },
180
- hint: { fontSize: "12px", opacity: 0.7 },
181
- radio: { display: "flex", alignItems: "center", gap: "4px", fontSize: "11px", opacity: 0.85, cursor: "pointer" }
168
+ item: { display: "grid", gridTemplateColumns: "minmax(0, 1fr) auto", alignItems: "center", gap: "10px", padding: "6px 0", borderTop: "1px solid color-mix(in srgb, currentColor 13%, transparent)" },
169
+ body: { display: "flex", flexDirection: "column", gap: "1px", minWidth: 0 },
170
+ head: { display: "flex", alignItems: "baseline", gap: "0 8px", minWidth: 0 },
171
+ repo: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: "12.5px", fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", flex: "0 1 auto", minWidth: 0 },
172
+ desc: { margin: 0, fontSize: "12px", color: "color-mix(in srgb, currentColor 78%, transparent)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" },
173
+ meta: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "5px", fontSize: "10.5px", color: "color-mix(in srgb, currentColor 70%, transparent)", flex: "0 0 auto", marginLeft: "auto" },
174
+ badge: { padding: "0 5px", borderRadius: "4px", border: "1px solid color-mix(in srgb, currentColor 20%, transparent)", fontSize: "10.5px", lineHeight: "15px" },
175
+ badgeOn: { padding: "0 5px", borderRadius: "4px", border: "1px solid rgba(60,190,120,0.55)", background: "rgba(60,190,120,0.14)", color: "#2f9e5f", fontSize: "10.5px", lineHeight: "15px" },
176
+ badgeStale: { padding: "0 5px", borderRadius: "4px", border: "1px solid rgba(210,150,40,0.55)", background: "rgba(210,150,40,0.14)", color: "#a97614", fontSize: "10.5px", lineHeight: "15px" },
177
+ actions: { display: "flex", alignItems: "center", gap: "8px", flex: "0 0 auto" },
178
+ error: { color: "#d4382f", fontSize: "12px", margin: 0 },
179
+ errorRow: { color: "#d4382f", fontSize: "10.5px", maxWidth: "190px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
180
+ hint: { margin: 0, fontSize: "12px", color: "color-mix(in srgb, currentColor 74%, transparent)" },
181
+ radio: { display: "inline-flex", alignItems: "center", gap: "3px", fontSize: "11px", cursor: "pointer", color: "color-mix(in srgb, currentColor 88%, transparent)" },
182
+ radioInput: { margin: 0, accentColor: "var(--dsh-accent, #4f8cff)" }
182
183
  };
183
184
 
184
185
  /** Substitute {name} placeholders; an unknown placeholder is left verbatim. */
@@ -725,49 +726,36 @@ window.__ModuleLoader__.load({
725
726
 
726
727
  var rows = shown.map(function (entry, index) {
727
728
  var repo = String(entry.repo === undefined ? "" : entry.repo);
729
+ var descText = String(entry.description === undefined ? "" : entry.description);
728
730
  var targets = installed[repo];
729
731
  var isInstalled = Array.isArray(targets) && targets.length > 0;
730
732
  var isPending = pending[repo] === true;
731
733
  var chosen = targetChoice[repo] === undefined ? "claude" : targetChoice[repo];
732
- var preview = typeof entry.previewUrl === "string" && entry.previewUrl !== ""
733
- ? h("img", {
734
- key: "preview",
735
- style: STYLE.preview,
736
- src: entry.previewUrl,
737
- alt: "",
738
- loading: "lazy",
739
- onError: function (event) {
740
- try {
741
- if (event !== null && event !== undefined && event.target !== null && event.target !== undefined) event.target.style.visibility = "hidden";
742
- } catch (error) {
743
- /* a broken preview must never break the row */
744
- }
745
- }
746
- })
747
- : h("div", { key: "preview", style: STYLE.preview });
748
-
749
734
  var metaBits = [
750
735
  h("span", { key: "stars", style: STYLE.badge }, t("videoSkills.stars", { n: Number(entry.stars) || 0 })),
751
- h("span", { key: "kind", style: STYLE.badge }, t("videoSkills.kind." + String(entry.kind))),
752
736
  h("span", { key: "grade", style: entry.grade === "caution" ? STYLE.badgeStale : STYLE.badge }, t("videoSkills.grade." + String(entry.grade)))
753
737
  ];
754
- if (typeof entry.license === "string" && entry.license !== "") metaBits.push(h("span", { key: "license", style: STYLE.badge }, entry.license));
738
+ var detail = repo;
739
+ if (typeof entry.kind === "string" && entry.kind !== "") detail += " · " + t("videoSkills.kind." + entry.kind);
740
+ if (typeof entry.license === "string" && entry.license !== "") detail += " · " + entry.license;
755
741
  if (isInstalled) metaBits.push(h("span", { key: "installed", style: STYLE.badgeOn }, t("videoSkills.installed")));
756
742
 
757
743
  return h("div", { key: repo + "#" + String(index), style: STYLE.item }, [
758
- preview,
759
744
  h("div", { key: "body", style: STYLE.body }, [
760
- h("div", { key: "repo", style: STYLE.repo }, repo),
761
- h("p", { key: "desc", style: STYLE.desc }, String(entry.description === undefined ? "" : entry.description)),
762
- h("div", { key: "meta", style: STYLE.meta }, metaBits)
745
+ h("div", { key: "head", style: STYLE.head }, [
746
+ h("span", { key: "repo", style: STYLE.repo, title: detail }, repo),
747
+ h("span", { key: "meta", style: STYLE.meta }, metaBits)
748
+ ]),
749
+ h("p", { key: "desc", style: STYLE.desc, title: descText }, descText)
763
750
  ]),
764
751
  h("div", { key: "actions", style: STYLE.actions }, [
765
752
  h("div", { key: "targets", style: STYLE.row }, TARGETS.map(function (target) {
766
- return h("label", { key: target, style: STYLE.radio }, [
753
+ return h("label", { key: target, style: STYLE.radio, title: target === "claude" ? "~/.claude/skills" : "~/.dsh/skills" }, [
767
754
  h("input", {
768
755
  type: "radio",
769
756
  name: "target-" + repo,
770
757
  value: target,
758
+ style: STYLE.radioInput,
771
759
  checked: chosen === target,
772
760
  onChange: function () {
773
761
  try {
@@ -792,7 +780,7 @@ window.__ModuleLoader__.load({
792
780
  disabled: isPending,
793
781
  onClick: function () { try { install(entry); } catch (error) { } }
794
782
  }, isPending ? t("videoSkills.installing") : t("videoSkills.install")),
795
- rowErrors[repo] !== undefined ? h("div", { key: "error", style: STYLE.errorRow, role: "alert" }, String(rowErrors[repo])) : null
783
+ rowErrors[repo] !== undefined ? h("div", { key: "error", style: STYLE.errorRow, role: "alert", title: String(rowErrors[repo]) }, String(rowErrors[repo])) : null
796
784
  ])
797
785
  ]);
798
786
  });
package/locale/en-US.json CHANGED
@@ -23,8 +23,8 @@
23
23
  "videoSkills.installing": "Installing…",
24
24
  "videoSkills.installed": "Installed",
25
25
  "videoSkills.target.label": "Install to",
26
- "videoSkills.target.claude": "Claude (~/.claude/skills)",
27
- "videoSkills.target.dsh": "DSH (~/.dsh/skills)",
26
+ "videoSkills.target.claude": "Claude",
27
+ "videoSkills.target.dsh": "DSH",
28
28
  "videoSkills.files": "{n} files",
29
29
  "videoSkills.error.load": "Catalog failed to load: {message}",
30
30
  "videoSkills.error.refresh": "Refresh failed: {message}",
package/locale/zh-CN.json CHANGED
@@ -23,8 +23,8 @@
23
23
  "videoSkills.installing": "安装中…",
24
24
  "videoSkills.installed": "已安装",
25
25
  "videoSkills.target.label": "安装到",
26
- "videoSkills.target.claude": "Claude(~/.claude/skills)",
27
- "videoSkills.target.dsh": "DSH(~/.dsh/skills)",
26
+ "videoSkills.target.claude": "Claude",
27
+ "videoSkills.target.dsh": "DSH",
28
28
  "videoSkills.files": "{n} 个文件",
29
29
  "videoSkills.error.load": "目录加载失败:{message}",
30
30
  "videoSkills.error.refresh": "刷新失败:{message}",
package/package.json CHANGED
@@ -1,8 +1,23 @@
1
1
  {
2
2
  "name": "dsh-video-skills",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Browse the awesome-claude-video-skills catalog inside DSH and install a skill with one click.",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/1368546410qq/dsh-video-skills.git"
9
+ },
10
+ "homepage": "https://github.com/1368546410qq/dsh-video-skills#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/1368546410qq/dsh-video-skills/issues"
13
+ },
14
+ "keywords": [
15
+ "dsh",
16
+ "deepseek-harness",
17
+ "plugin",
18
+ "skills",
19
+ "video"
20
+ ],
6
21
  "type": "module",
7
22
  "main": "src/host/index.js",
8
23
  "exports": {
package/src/host/index.js CHANGED
@@ -9,8 +9,8 @@ import { buildVideoTools, resolveDefineTool } from './tool-schema.js';
9
9
 
10
10
  /** Cordis plugin name. */
11
11
  export const name = 'video-skills';
12
- /** The web server is required; the tool registry is optional. */
13
- export const inject = ['webServer'];
12
+ /** Nothing is required: the tools work in any composition, the route waits for a web server. */
13
+ export const inject = [];
14
14
  /** Single prefix route the client talks to. */
15
15
  export const API_BASE = '/video-skills/api/v1';
16
16
  /** Largest JSON body accepted on POST routes. */
@@ -127,14 +127,29 @@ async function registerTools(ctx) {
127
127
  return true;
128
128
  }
129
129
 
130
- /** Compose the plugin against the host context. */
131
- export function apply(ctx) {
132
- const webServer = ctx?.webServer ?? (typeof ctx?.get === 'function' ? ctx.get('webServer') : undefined);
130
+ /** Mount the prefix route once a web server is composed. */
131
+ function mountRoutes(scope) {
132
+ const webServer = scope?.webServer ?? (typeof scope?.get === 'function' ? scope.get('webServer') : undefined);
133
133
  if (!webServer || typeof webServer.register !== 'function') return;
134
-
135
134
  const registerRoute = () => webServer.register({ kind: 'prefix', path: API_BASE, handler });
136
- if (typeof ctx.effect === 'function') ctx.effect(registerRoute);
135
+ if (typeof scope.effect === 'function') scope.effect(registerRoute);
137
136
  else registerRoute();
137
+ }
138
138
 
139
+ /**
140
+ * Compose the plugin against the host context.
141
+ *
142
+ * The two halves have different requirements: the model tools are useful in
143
+ * every composition (CLI, TUI, web), while the HTTP route only exists where a
144
+ * web server is composed, so each waits for its own service instead of making
145
+ * the web server a precondition for the whole plugin.
146
+ */
147
+ export function apply(ctx) {
148
+ if (typeof ctx?.inject === 'function') {
149
+ ctx.inject(['tools'], (scope) => { registerTools(scope).catch(() => {}); });
150
+ ctx.inject(['webServer'], mountRoutes);
151
+ return;
152
+ }
153
+ mountRoutes(ctx);
139
154
  registerTools(ctx).catch(() => {});
140
155
  }