@ninkch/dsh-artcraft 0.2.3 → 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/README.md CHANGED
@@ -281,9 +281,11 @@ GitHub: this repository is self-contained; `README.md` is English-first with a
281
281
  [简体中文](README.zh-CN.md) companion.
282
282
 
283
283
  **Releases run through npm Trusted Publishing** (GitHub Actions OIDC — no tokens, no OTP):
284
- `.github/workflows/publish.yml` fires on `v*` tags; the trusted publisher on the npm package
285
- points at `NINKCH/dsh-artcraft` / `publish.yml`. To cut a release: bump `version`, commit,
286
- `git tag v0.x.y && git push origin v0.x.y`.
284
+ `.github/workflows/publish.yml` fires on `v*` tags **on `main` only** (a guard step rejects tags
285
+ from other branches); the trusted publisher on the npm package points at `NINKCH/dsh-artcraft` /
286
+ `publish.yml`. Release flow and rules live in [RELEASING.md](RELEASING.md) — in short: changes
287
+ land on `dev`, get tested there, and are only merged to `main` and tagged with the maintainer's
288
+ explicit approval.
287
289
 
288
290
  ## License
289
291
 
package/README.zh-CN.md CHANGED
@@ -264,8 +264,9 @@ GitHub 上传本仓库即可:`README.md` 英文优先,附
264
264
  [简体中文](README.zh-CN.md) 对照。
265
265
 
266
266
  **发版走 npm Trusted Publishing**(GitHub Actions OIDC —— 无令牌、无 OTP):
267
- `.github/workflows/publish.yml` 在 `v*` 标签推送时触发;npm 包侧的可信发布指向
268
- `NINKCH/dsh-artcraft` / `publish.yml`。发版流程:改 `version` → 提交 → `git tag v0.x.y && git push origin v0.x.y`。
267
+ `.github/workflows/publish.yml` 仅在 **main 分支**的 `v*` 标签推送时触发(守卫步骤会拒绝其他分支
268
+ 的标签);npm 包侧的可信发布指向 `NINKCH/dsh-artcraft` / `publish.yml`。发版流程与红线见
269
+ [RELEASING.md](RELEASING.md):改动先进 `dev` 测试,维护者明确批准后才合并 main 并打标签。
269
270
 
270
271
  ## 许可
271
272
 
package/client.js CHANGED
@@ -1,6 +1,11 @@
1
1
  // @ninkch/dsh-artcraft — client half.
2
- // 不占侧栏/主面板:状态与「检查更新」挂在 插件页 → 本插件详情卡 上
3
- // (plugins.detail.badge / plugins.detail.section)。
2
+ // 不占侧栏/主面板:状态与「检查更新 / 一键更新」挂在
3
+ // 插件页 → 本插件详情卡(plugins.bundle.config,按包名键控分发)。
4
+ //
5
+ // 更新语义(吸取画布侧教训):
6
+ // - 只做版本号对比(npm latest vs 本地),本地领先不算待更新;
7
+ // - 更新完成后只显示提示文字,绝不自动刷新/重启页面 —— 何时生效由你决定
8
+ // (本插件是 Host 半侧代码,生效需要重启 Harness 进程,刷新页面不够)。
4
9
  window.__ModuleLoader__.load({
5
10
  id: '@ninkch/dsh-artcraft',
6
11
  factory(require) {
@@ -10,6 +15,7 @@ window.__ModuleLoader__.load({
10
15
 
11
16
  const NS = 'artcraft';
12
17
  const API = '/artcraft/api';
18
+ const LABELS = { photocraft: 'PhotoCraft', vectorcraft: 'VectorCraft', filmcraft: 'FilmCraft', lightcraft: 'LightCraft', pdfcraft: 'PdfCraft', effectcraft: 'EffectCraft', designcraft: 'DesignCraft' };
13
19
 
14
20
  const CSS = `
15
21
  .dshac-card { border: 1px solid var(--dsw-border-primary, rgba(128,128,128,.35)); border-radius: 10px; padding: 12px; font-size: 13px; line-height: 1.5; }
@@ -28,8 +34,6 @@ window.__ModuleLoader__.load({
28
34
  background: rgba(128,128,128,.12); border-radius: 7px; padding: 8px; margin-top: 8px; max-height: 160px; overflow: auto; }
29
35
  .dshac-note { margin-top: 8px; color: var(--dsw-alias-state-warning-primary, #d69e2e); font-size: 12px; }
30
36
  .dshac-ok { color: var(--dsw-alias-state-success-primary, #34c759); }
31
- .dshac-chip { display: inline-flex; align-items: center; border: 1px solid rgba(80,120,255,.5); color: inherit;
32
- background: rgba(80,120,255,.18); border-radius: 999px; padding: 1px 9px; font-size: 11px; }
33
37
  `;
34
38
 
35
39
  async function api(path, opts) {
@@ -43,87 +47,70 @@ window.__ModuleLoader__.load({
43
47
  } finally { if (timer) clearTimeout(timer); }
44
48
  }
45
49
 
46
- /** 详情页 subject 兼容提取:判断当前详情页是否本插件 */
47
- function isOurPage(props) {
48
- const sub = props && props.subject ? props.subject : props;
49
- if (!sub) return false;
50
- const name = sub.packageName ?? sub.name ?? sub.moduleName ?? sub.id ?? (typeof sub === 'string' ? sub : '');
51
- return typeof name === 'string' && name.includes('dsh-artcraft');
52
- }
53
-
54
50
  function Dot({ ok }) {
55
51
  return h('span', { className: 'dshac-dot ' + (ok === true ? 'ok' : ok === false ? 'bad' : 'unknown') });
56
52
  }
57
53
 
58
- /** 详情页标题旁的徽标:有新版本时出现 */
59
- function UpdateBadge(props) {
60
- const [latest, setLatest] = useState(null);
61
- useEffect(() => {
62
- if (!isOurPage(props)) return;
63
- let ok = true;
64
- api('/update/check').then((u) => { if (ok && u?.updateAvailable) setLatest(u.latest); }).catch(() => {});
65
- return () => { ok = false; };
66
- }, []);
67
- if (!isOurPage(props) || !latest) return null;
68
- return h('span', { className: 'dshac-chip', title: 'npm 上有新版本,到本页一键更新' }, '有新版本 v' + latest);
69
- }
70
-
71
- /** 详情页内容区:工具链状态 + 更新 */
72
- function StatusSection(props) {
54
+ function StatusCard() {
73
55
  const [status, setStatus] = useState(null);
74
56
  const [statusErr, setStatusErr] = useState(null);
75
57
  const [upd, setUpd] = useState(null);
76
58
  const [busy, setBusy] = useState('');
77
59
  const [out, setOut] = useState('');
78
- const [needRestart, setNeedRestart] = useState(false);
79
- const ours = isOurPage(props);
60
+ const [done, setDone] = useState(false);
80
61
  const alive = useRef(true);
81
62
  useEffect(() => () => { alive.current = false; }, []);
82
63
 
83
64
  const loadStatus = useCallback(async () => {
84
65
  try {
85
66
  const s = await api('/status');
86
- if (!alive.current) return;
87
- setStatus(s); setStatusErr(null);
67
+ if (alive.current) { setStatus(s); setStatusErr(null); }
88
68
  } catch (e) { if (alive.current) setStatusErr(String(e.message || e)); }
89
69
  }, []);
70
+
90
71
  const checkUpdate = useCallback(async () => {
91
- setBusy('check'); setOut('');
72
+ setBusy('check'); setOut(''); setDone(false);
92
73
  try {
93
74
  const u = await api('/update/check');
94
75
  if (!alive.current) return;
95
76
  setUpd(u);
96
77
  setOut(u.updateAvailable
97
- ? `有新版本:${u.latest}(当前 ${u.current}),安装方式:${u.mode === 'git' ? 'git 仓库' : 'npm 安装'}`
98
- : `已是最新${u.latest ? '(npm latest ' + u.latest + ')' : ''}${u.note ? ' · ' + u.note : ''}`);
78
+ ? `有新版本:v${u.latest}(当前 v${u.current}),安装方式:${u.mode === 'git' ? 'git 仓库' : 'npm 安装'}`
79
+ : `已是最新${u.latest ? '(npm latest v' + u.latest + ')' : ''}${u.note ? ' · ' + u.note : ''}`);
99
80
  } catch (e) { if (alive.current) setOut('检查失败:' + String(e.message || e)); }
100
81
  finally { if (alive.current) setBusy(''); }
101
82
  }, []);
83
+
102
84
  const applyUpdate = useCallback(async () => {
103
- setBusy('apply'); setOut('');
85
+ setBusy('apply'); setOut(''); setDone(false);
104
86
  try {
105
87
  const r = await api('/update/apply', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' });
106
88
  if (!alive.current) return;
107
- setOut((r.output || String(r.message || '')) + (r.needRestart ? '\n\n✅ 更新完成 —— 请重启 DeepSeek Harness 后生效。' : ''));
108
- if (r.needRestart) setNeedRestart(true);
89
+ setOut((r.output || String(r.message || '')));
90
+ // 不自动刷新/重启任何东西:本插件是 Host 半侧代码,生效 = 重启 Harness 进程
91
+ if (r.needRestart) {
92
+ setDone(true);
93
+ setOut((r.output || '') + '\n\n✅ 更新已写入磁盘。生效方式:重启 DeepSeek Harness(进程级重启,页面刷新不够)。何时重启由你决定。');
94
+ }
109
95
  } catch (e) { if (alive.current) setOut('更新失败:' + String(e.message || e)); }
110
96
  finally { if (alive.current) setBusy(''); }
111
97
  }, []);
112
98
 
113
- useEffect(() => {
114
- if (!ours) return;
115
- loadStatus(); checkUpdate();
116
- }, [ours, loadStatus, checkUpdate]);
117
-
118
- if (!ours) return null;
99
+ useEffect(() => { loadStatus(); checkUpdate(); }, [loadStatus, checkUpdate]);
119
100
 
120
101
  const bins = status?.bins ?? {};
121
- const binRow = (key, label) => {
102
+ const binRow = (key) => {
122
103
  const b = bins[key];
104
+ const label = LABELS[key] || key;
105
+ if (!b) return h('div', { className: 'dshac-row' }, h(Dot, { ok: null }), h('span', null, label), h('span', { className: 'dshac-muted' }, '未配置'));
106
+ const unconfigured = b.configured === key + '-cli' && !b.found; // 从未配置过的默认裸名
107
+ const ok = unconfigured ? null : b.found;
123
108
  return h('div', { className: 'dshac-row' },
124
- h(Dot, { ok: b ? b.found : null }),
109
+ h(Dot, { ok }),
125
110
  h('span', null, label),
126
- b?.found ? null : h('span', { className: 'dshac-muted' }, b?.note || '未配置'),
111
+ b.found ? h('span', { className: 'dshac-ok' }, '就绪')
112
+ : unconfigured ? h('span', { className: 'dshac-muted' }, '未安装(可选家族成员)')
113
+ : h('span', { className: 'dshac-muted' }, b.note || '未找到'),
127
114
  );
128
115
  };
129
116
 
@@ -132,10 +119,7 @@ window.__ModuleLoader__.load({
132
119
  h('button', { className: 'dshac-btn', onClick: loadStatus, disabled: busy !== '' }, '刷新')),
133
120
  statusErr
134
121
  ? h('div', { className: 'dshac-muted' }, '未连接:' + statusErr + '(插件可能未注册或 Harness 需要重启)')
135
- : h('div', null,
136
- binRow('photocraft', 'PhotoCraft (CLI)'),
137
- binRow('filmcraft', 'FilmCraft (CLI)'),
138
- binRow('effectcraft', 'EffectCraft (CLI)'),
122
+ : h('div', null, Object.keys(LABELS).map(binRow),
139
123
  status?.dataRoot ? h('div', { className: 'dshac-row dshac-muted' },
140
124
  '数据目录:', h('span', { className: 'dshac-mono' }, status.dataRoot.path)) : null),
141
125
  h('div', { className: 'dshac-row', style: { marginTop: '10px' } },
@@ -151,7 +135,8 @@ window.__ModuleLoader__.load({
151
135
  disabled: busy !== '' || !upd?.updateAvailable,
152
136
  title: upd?.updateAvailable ? '' : '有新版本时才可应用',
153
137
  }, busy === 'apply' ? '更新中…' : '一键更新')),
154
- needRestart ? h('div', { className: 'dshac-note' }, '更新已写入 —— 重启 DeepSeek Harness 后生效。') : null,
138
+ done ? h('div', { className: 'dshac-note' },
139
+ '更新已写入磁盘。生效方式:重启 DeepSeek Harness(进程级重启,页面刷新不够)。何时重启由你决定 —— 更新不会打断正在运行的任务。') : null,
155
140
  out ? h('div', { className: 'dshac-out' }, out) : null,
156
141
  );
157
142
  }
@@ -167,22 +152,12 @@ window.__ModuleLoader__.load({
167
152
  document.head.appendChild(el);
168
153
  return () => el.remove();
169
154
  }, 'artcraft: styles');
170
- // 插件页 → 本插件详情卡:标题旁徽标(有新版本时)
171
- ctx.slots.inject('plugins.detail.badge', () => ctx.slots.register({
172
- name: 'plugins.detail.badge',
173
- id: 'artcraft-update-badge',
174
- order: 50,
175
- label: () => 'ArtCraft 更新',
176
- locale: NS,
177
- }, UpdateBadge));
178
- // 插件页 → 本插件详情卡:内容区(工具链状态 + 检测更新 + 一键更新)
179
- ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
180
- name: 'plugins.detail.section',
181
- id: 'artcraft-status',
182
- order: 50,
183
- label: () => 'ArtCraft 工具链与更新',
155
+ // 键控分发:key = 包名 → 只渲染在 @ninkch/dsh-artcraft 自己的插件页详情卡上
156
+ ctx.slots.inject('plugins.bundle.config', () => ctx.slots.register({
157
+ name: 'plugins.bundle.config',
158
+ key: '@ninkch/dsh-artcraft',
184
159
  locale: NS,
185
- }, StatusSection));
160
+ }, StatusCard));
186
161
  },
187
162
  };
188
163
  },
package/index.js CHANGED
@@ -39,10 +39,10 @@ import crypto from 'node:crypto';
39
39
  import { fileURLToPath } from 'node:url';
40
40
 
41
41
  const NAME = 'artcraft';
42
- const VERSION = '0.2.3';
42
+ const VERSION = '0.2.4';
43
43
  const HERE = path.dirname(fileURLToPath(import.meta.url));
44
44
 
45
- const BINS = ['photocraft', 'filmcraft', 'effectcraft'];
45
+ const BINS = ['photocraft', 'vectorcraft', 'filmcraft', 'lightcraft', 'pdfcraft', 'effectcraft', 'designcraft'];
46
46
  const PKG_NAME = '@ninkch/dsh-artcraft';
47
47
  const DEFAULT_PORTS = { filmcraft: 9876, effectcraft: 9877 };
48
48
 
@@ -58,8 +58,12 @@ const DEFAULT_CONFIG = {
58
58
  dataRoot: path.join(os.homedir(), 'craft-workspace'),
59
59
  bins: {
60
60
  photocraft: 'photocraft-cli',
61
+ vectorcraft: 'vectorcraft-cli',
61
62
  filmcraft: 'filmcraft-cli',
63
+ lightcraft: 'lightcraft-cli',
64
+ pdfcraft: 'pdfcraft-cli',
62
65
  effectcraft: 'effectcraft-cli',
66
+ designcraft: 'designcraft-cli',
63
67
  },
64
68
  control: { ...DEFAULT_PORTS },
65
69
  };
@@ -558,9 +562,9 @@ async function updateApply() {
558
562
  const tools = await findRuntimeTools();
559
563
  let cmd, args;
560
564
  if (tools.pnpmCmd) {
561
- cmd = tools.pnpmCmd; args = ['add', `${PKG_NAME}@latest`, '--dir', info.profileDir];
565
+ cmd = tools.pnpmCmd; args = ['add', `${PKG_NAME}@latest`, '--dir', info.profileDir, '--registry=https://registry.npmjs.org'];
562
566
  } else if (tools.pnpmMjs) {
563
- cmd = tools.nodeExe; args = [tools.pnpmMjs, 'add', `${PKG_NAME}@latest`, '--dir', info.profileDir];
567
+ cmd = tools.nodeExe; args = [tools.pnpmMjs, 'add', `${PKG_NAME}@latest`, '--dir', info.profileDir, '--registry=https://registry.npmjs.org'];
564
568
  } else {
565
569
  throw new Error('未找到 pnpm(PATH 与 ~/.dsh/dsh-runtimes 均未命中),请手动在 profile 目录执行 pnpm add');
566
570
  }
@@ -580,7 +584,10 @@ export function apply(ctx) {
580
584
  ctx.effect(() => {
581
585
  const disposers = [];
582
586
  const attempt = (def) => {
583
- const output = def.output ?? { type: 'object', additionalProperties: true };
587
+ const output = def.output ?? {
588
+ schema: { type: 'object', additionalProperties: true },
589
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
590
+ };
584
591
  for (const shape of [
585
592
  { name: def.name, description: def.description, parameters: def.schema, execute: def.execute },
586
593
  { name: def.name, description: def.description, inputSchema: def.schema, handler: def.execute },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninkch/dsh-artcraft",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "description": "Drive the open-source ArtCraft studio apps (PhotoCraft / FilmCraft / EffectCraft) from DeepSeek Harness — agent tools, a same-origin HTTP API, and plugins-page status with self-update.",
6
6
  "keywords": [