@ninkch/dsh-infinite-canvas 0.17.13 → 0.17.15
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/assets/canvas-craft/SKILL.md +58 -6
- package/assets/canvas-studio/SKILL.md +17 -8
- package/assets/support/README.md +21 -0
- package/assets/support/wechat.png +0 -0
- package/client.js +489 -126
- package/index.js +188 -19
- package/package.json +56 -55
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { spawn } from 'node:child_process';
|
|
10
10
|
import fs from 'node:fs/promises';
|
|
11
|
-
import { createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, renameSync,
|
|
11
|
+
import { createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
@@ -110,6 +110,23 @@ async function ensureBundleEntry(profileDir, pkg) {
|
|
|
110
110
|
// 配置存在 ~/.dsh/infinite-canvas/config.json:跨重装/重启稳定,绝不放进包目录。
|
|
111
111
|
const CONFIG_PATH = process.env.DSH_IC_CONFIG_PATH || path.join(os.homedir(), '.dsh', 'infinite-canvas', 'config.json');
|
|
112
112
|
|
|
113
|
+
/** 安全递归删除目录。
|
|
114
|
+
* **禁止用 fs.rmSync(recursive)**:本机 Node(24.x / Windows)在**中文路径**上会原生崩溃
|
|
115
|
+
* (STATUS_STACK_BUFFER_OVERRUN,直接把宿主进程带走——删一个中文名项目就能打崩 DSH)。
|
|
116
|
+
* 这里逐层 readdir + unlink/rmdir,走的是不同的系统调用路径,实测稳定。 */
|
|
117
|
+
function rmrf(target) {
|
|
118
|
+
let st;
|
|
119
|
+
try { st = lstatSync(target); } catch { return; } // 不存在 = 已删干净
|
|
120
|
+
if (!st.isDirectory()) {
|
|
121
|
+
try { unlinkSync(target); } catch { /* 被占用则残留,无碍 */ }
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
let names = [];
|
|
125
|
+
try { names = readdirSync(target); } catch { return; }
|
|
126
|
+
for (const name of names) rmrf(path.join(target, name));
|
|
127
|
+
try { rmdirSync(target); } catch { /* 被占用则残留,无碍 */ }
|
|
128
|
+
}
|
|
129
|
+
|
|
113
130
|
function readIcConfig() {
|
|
114
131
|
try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf8')) || {}; } catch { return {}; }
|
|
115
132
|
}
|
|
@@ -284,9 +301,7 @@ function removeDataRootProject(pid) {
|
|
|
284
301
|
const entry = meta.projects.find((p) => p.id === pid);
|
|
285
302
|
meta.projects = meta.projects.filter((p) => p.id !== pid);
|
|
286
303
|
writeDataRootMeta(meta);
|
|
287
|
-
if (entry)
|
|
288
|
-
try { rmSync(path.join(root, 'projects', entry.folder), { recursive: true, force: true }); } catch { /* 占用时残留,无碍 */ }
|
|
289
|
-
}
|
|
304
|
+
if (entry) rmrf(path.join(root, 'projects', entry.folder));
|
|
290
305
|
}
|
|
291
306
|
/** 一次性迁移/对齐数据目录布局:旧 pid 文件夹 → 项目名文件夹,并镜像全部项目文档。 */
|
|
292
307
|
function dataRootMigrate() {
|
|
@@ -321,7 +336,7 @@ function dataRootMigrate() {
|
|
|
321
336
|
if (!existsSync(to)) { renameSync(from, to); filesMoved++; }
|
|
322
337
|
}
|
|
323
338
|
}
|
|
324
|
-
|
|
339
|
+
rmrf(oldDir);
|
|
325
340
|
} catch { /* 目录被占用:保留旧目录,下轮再试 */ }
|
|
326
341
|
}
|
|
327
342
|
mirrorProjectToDataRoot(id, doc);
|
|
@@ -597,6 +612,17 @@ async function projectsPut(args) {
|
|
|
597
612
|
if (!dead.has(nid)) merged[nid] = n;
|
|
598
613
|
else delete merged[nid];
|
|
599
614
|
}
|
|
615
|
+
// 竞态防护:客户端推来的分镜表没有 shots,而 Host 上已有 shots → 保留 Host 的 shots
|
|
616
|
+
// (客户端旧快照推送会覆盖刚生成的分镜,导致"跑下一个时上一个被清空")
|
|
617
|
+
for (const [nid, n] of Object.entries(merged)) {
|
|
618
|
+
if (!n || n.type !== 'storyboard') continue;
|
|
619
|
+
const hasNew = Array.isArray(n.data && n.data.shots) && n.data.shots.length;
|
|
620
|
+
if (hasNew) continue;
|
|
621
|
+
const prevN = (prev.nodes || {})[nid];
|
|
622
|
+
if (prevN && prevN.type === 'storyboard' && Array.isArray(prevN.data && prevN.data.shots) && prevN.data.shots.length) {
|
|
623
|
+
n.data = { ...(n.data || {}), shots: prevN.data.shots, warnings: prevN.data.warnings || [] };
|
|
624
|
+
}
|
|
625
|
+
}
|
|
600
626
|
const edgeOk = (e) => e && !deadEdges.has(e.id) && merged[e.fromNode] && merged[e.toNode];
|
|
601
627
|
const clientEdges = (doc.edges || []).filter(edgeOk);
|
|
602
628
|
const prevEdges = (prev.edges || []).filter(edgeOk);
|
|
@@ -749,11 +775,42 @@ function agentNode(kind, text, x, y, label) {
|
|
|
749
775
|
if (label) data.label = String(label).slice(0, 40);
|
|
750
776
|
return { id, type: isText ? 'text' : 'script', x, y, w: isText ? 260 : 320, h: 100, data };
|
|
751
777
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
778
|
+
/** 新节点布局(用户设计):
|
|
779
|
+
* 1) 有 connectFrom 源 → 放在源节点右侧同一行(串联节点横向成对);
|
|
780
|
+
* 2) 无连线 → 同类型竖向成列(排在该类型最后一个节点下方,新起一行);
|
|
781
|
+
* 3) 碰撞检测:候选位置与任何现有节点(含最小间距 28px)相交 → 自动下移到该节点下方,绝不重叠。 */
|
|
782
|
+
function nextNodePos(doc, type, connectFromId) {
|
|
783
|
+
const GAP_X = 60, GAP_Y = 40, MIN_GAP = 28;
|
|
784
|
+
const H = (n) => Number(n && n.h) || 160;
|
|
785
|
+
const W = (n) => Number(n && n.w) || 280;
|
|
786
|
+
const nodes = Object.values(doc.nodes || {});
|
|
787
|
+
const EST_H = { storyboard: 560, 'video-gen': 620, 'image-gen': 560, script: 420, finalcut: 420, text: 220 };
|
|
788
|
+
const estH = EST_H[type] || 300;
|
|
789
|
+
const estW = type === 'finalcut' ? 300 : 280;
|
|
790
|
+
let x = 80;
|
|
791
|
+
let y = 40;
|
|
792
|
+
if (connectFromId && doc.nodes && doc.nodes[connectFromId]) {
|
|
793
|
+
const src = doc.nodes[connectFromId];
|
|
794
|
+
x = (src.x || 0) + W(src) + GAP_X;
|
|
795
|
+
y = src.y || 40;
|
|
796
|
+
} else {
|
|
797
|
+
const same = nodes.filter((n) => n.type === type);
|
|
798
|
+
if (same.length) {
|
|
799
|
+
// 同类型竖向成列:跟在同类型最后一个节点下方
|
|
800
|
+
x = Math.min(...same.map((n) => n.x || 0));
|
|
801
|
+
y = Math.max(...same.map((n) => (n.y || 0) + H(n))) + GAP_Y;
|
|
802
|
+
} else if (nodes.length) {
|
|
803
|
+
// 该类型还没有节点 → 在最右侧另起一列(绝不插进已有列里跟别的类型混排)
|
|
804
|
+
x = Math.max(...nodes.map((n) => (n.x || 0) + W(n))) + GAP_X;
|
|
805
|
+
y = Math.min(...nodes.map((n) => n.y || 0));
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const hit = (yy) => nodes.find((n) => (n.x || 0) < x + estW + MIN_GAP && (n.x || 0) + W(n) + MIN_GAP > x
|
|
809
|
+
&& (n.y || 0) < yy + estH + MIN_GAP && (n.y || 0) + H(n) + MIN_GAP > yy);
|
|
810
|
+
let blocker = hit(y);
|
|
811
|
+
let guard = 0;
|
|
812
|
+
while (blocker && guard++ < 80) { y = (blocker.y || 0) + H(blocker) + MIN_GAP; blocker = hit(y); }
|
|
813
|
+
return { x, y };
|
|
757
814
|
}
|
|
758
815
|
// ---------- 内置技能(操作规程 + 创作方法论,约束并提升对话的画布创作)----------
|
|
759
816
|
// canvas-studio=操作硬约束;canvas-craft=剧本/分镜/提示词创作方法论
|
|
@@ -1042,10 +1099,10 @@ function canvasCreateNode(args) {
|
|
|
1042
1099
|
if (!['script', 'text', 'image-gen', 'video-gen', 'storyboard', 'finalcut'].includes(type)) throw new Error('不支持的节点类型: ' + type);
|
|
1043
1100
|
const label = args && args.name ? String(args.name).slice(0, 40) : '';
|
|
1044
1101
|
const prompt = String((args && args.prompt) || '');
|
|
1045
|
-
const
|
|
1102
|
+
const connectFrom = String((args && args.connectFrom) || '');
|
|
1103
|
+
const pos = nextNodePos(doc, type, connectFrom);
|
|
1046
1104
|
let connected = null;
|
|
1047
1105
|
let autoConnected = null;
|
|
1048
|
-
const connectFrom = String((args && args.connectFrom) || '');
|
|
1049
1106
|
let node;
|
|
1050
1107
|
if (type === 'image-gen') {
|
|
1051
1108
|
node = agentNode('text', '', pos.x, pos.y, label || '图片生成');
|
|
@@ -1099,11 +1156,14 @@ function canvasCreateNode(args) {
|
|
|
1099
1156
|
const explicitConnect = Boolean(connectFrom);
|
|
1100
1157
|
if (connectFrom) {
|
|
1101
1158
|
if (!doc.nodes[connectFrom]) throw new Error('connectFrom 指向的节点不存在: ' + connectFrom);
|
|
1102
|
-
|
|
1159
|
+
// 分镜表默认连「剧本来源」口(script),而不是输入队列里的第一个口(prev)——场景文本是剧本来源,不是承接来源
|
|
1160
|
+
const srcType = doc.nodes[connectFrom] && doc.nodes[connectFrom].type;
|
|
1161
|
+
const defaultToPort = (type === 'storyboard' && (srcType === 'text' || srcType === 'script')) ? 'script' : undefined;
|
|
1162
|
+
const edge = connectEdge(doc, connectFrom, node.id, args && args.connectFromPort, (args && args.connectToPort) || defaultToPort);
|
|
1103
1163
|
connected = edge.id;
|
|
1104
1164
|
} else if (autoConnected && (type === 'storyboard' || type === 'video-gen')) {
|
|
1105
1165
|
// 未指定连线但上游已存在(分镜表→剧本 / 视频生成→分镜表)→ 默认自动连线(工作流的默认形态,不依赖 Agent 记得)
|
|
1106
|
-
const edge = connectEdge(doc, autoConnected.from, node.id);
|
|
1166
|
+
const edge = connectEdge(doc, autoConnected.from, node.id, undefined, type === 'storyboard' ? 'script' : undefined);
|
|
1107
1167
|
connected = edge.id;
|
|
1108
1168
|
}
|
|
1109
1169
|
doc.updatedAt = Date.now();
|
|
@@ -1119,6 +1179,62 @@ function canvasCreateNode(args) {
|
|
|
1119
1179
|
return { ok: true, projectId: id, nodeId: node.id, type: node.type, label: (node.data && node.data.label) || '', edgeId: connected || undefined, note: note || undefined };
|
|
1120
1180
|
}
|
|
1121
1181
|
|
|
1182
|
+
/** 一键重排:按「行」布局——横向数据流决定列,竖向端口(prev/frame)不参与分列;
|
|
1183
|
+
* 每行 = 一个横向连通分量(如「场N文本 ↔ 场N分镜表」),行内同 y 横向配对,
|
|
1184
|
+
* 行高取行内最高节点、行间保留间距,绝不重叠。 */
|
|
1185
|
+
function canvasRelayout(args) {
|
|
1186
|
+
const id = String((args && args.projectId) || '');
|
|
1187
|
+
const doc = readDoc(id);
|
|
1188
|
+
const nodes = Object.values(doc.nodes || {});
|
|
1189
|
+
if (!nodes.length) return { ok: true, projectId: id, moved: 0, rows: 0 };
|
|
1190
|
+
const VERT = new Set(['prev', 'frame']);
|
|
1191
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
1192
|
+
const depth = new Map(nodes.map((n) => [n.id, 0]));
|
|
1193
|
+
for (let i = 0; i < 12; i++) {
|
|
1194
|
+
for (const e of (doc.edges || [])) {
|
|
1195
|
+
if (VERT.has(String(e.toPort))) continue;
|
|
1196
|
+
if (!byId.has(e.fromNode) || !byId.has(e.toNode)) continue;
|
|
1197
|
+
const d = (depth.get(e.fromNode) || 0) + 1;
|
|
1198
|
+
if ((depth.get(e.toNode) || 0) < d) depth.set(e.toNode, d);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
const parent = new Map(nodes.map((n) => [n.id, n.id]));
|
|
1202
|
+
const find = (x) => { let y = x; while (parent.get(y) !== y) y = parent.get(y); return y; };
|
|
1203
|
+
for (const e of (doc.edges || [])) {
|
|
1204
|
+
if (VERT.has(String(e.toPort))) continue;
|
|
1205
|
+
if (!byId.has(e.fromNode) || !byId.has(e.toNode)) continue;
|
|
1206
|
+
const a = find(e.fromNode); const b = find(e.toNode);
|
|
1207
|
+
if (a !== b) parent.set(b, a);
|
|
1208
|
+
}
|
|
1209
|
+
const rows = new Map();
|
|
1210
|
+
for (const n of nodes) {
|
|
1211
|
+
const r = find(n.id);
|
|
1212
|
+
if (!rows.has(r)) rows.set(r, []);
|
|
1213
|
+
rows.get(r).push(n);
|
|
1214
|
+
}
|
|
1215
|
+
const rowList = [...rows.values()]
|
|
1216
|
+
.map((ids) => ({ ids, minY: Math.min(...ids.map((n) => Number(n.y) || 0)) }))
|
|
1217
|
+
.sort((a, b) => a.minY - b.minY);
|
|
1218
|
+
const colW = new Map();
|
|
1219
|
+
for (const n of nodes) {
|
|
1220
|
+
const d = depth.get(n.id) || 0;
|
|
1221
|
+
colW.set(d, Math.max(colW.get(d) || 280, Number(n.w) || 280));
|
|
1222
|
+
}
|
|
1223
|
+
const colX = new Map();
|
|
1224
|
+
let cx = 80;
|
|
1225
|
+
for (const d of [...colW.keys()].sort((a, b) => a - b)) { colX.set(d, cx); cx += (colW.get(d) || 280) + 60; }
|
|
1226
|
+
let cy = 40;
|
|
1227
|
+
let moved = 0;
|
|
1228
|
+
for (const row of rowList) {
|
|
1229
|
+
const rowH = Math.max(200, ...row.ids.map((n) => Number(n.h) || 200));
|
|
1230
|
+
for (const n of row.ids) { n.x = colX.get(depth.get(n.id) || 0); n.y = cy; moved++; }
|
|
1231
|
+
cy += rowH + 40;
|
|
1232
|
+
}
|
|
1233
|
+
doc.updatedAt = Date.now();
|
|
1234
|
+
writeIcDoc(id, doc);
|
|
1235
|
+
markDocChanged(id);
|
|
1236
|
+
return { ok: true, projectId: id, moved, rows: rowList.length };
|
|
1237
|
+
}
|
|
1122
1238
|
async function canvasDeleteProject(args) {
|
|
1123
1239
|
const id = String((args && args.projectId) || '');
|
|
1124
1240
|
const f = projectFile(id);
|
|
@@ -1198,10 +1314,14 @@ function storyboardConsistencyWarnings(bible, shots, extraNames) {
|
|
|
1198
1314
|
for (const nm of (sh.characters || [])) check(title, '角色', nm);
|
|
1199
1315
|
check(title, '场景', sh && sh.scene);
|
|
1200
1316
|
for (const nm of (sh.props || [])) check(title, '道具', nm);
|
|
1201
|
-
// visual/motion 文本里的
|
|
1317
|
+
// visual/motion 文本里的 @资产名@ 提及同样纳入检查(未登记的提示补充——与客户端 parseMentions 词法一致)
|
|
1202
1318
|
const text = String((sh && sh.visual) || '') + ' ' + String((sh && sh.motion) || '');
|
|
1203
|
-
|
|
1204
|
-
|
|
1319
|
+
const seenMention = new Set();
|
|
1320
|
+
for (const m of (text.match(/@([^@\s,。;:、,.;:!?!?()()[\]【】"'“”<>《》/\\|]{1,64}?)@/g) || [])) {
|
|
1321
|
+
const nm = m.replace(/^@/, '').replace(/@$/, '');
|
|
1322
|
+
if (seenMention.has(nm)) continue;
|
|
1323
|
+
seenMention.add(nm);
|
|
1324
|
+
check(title, '提及', nm);
|
|
1205
1325
|
}
|
|
1206
1326
|
}
|
|
1207
1327
|
return warnings;
|
|
@@ -1302,8 +1422,15 @@ function connectEdge(doc, fromId, toId, fromPort, toPort) {
|
|
|
1302
1422
|
if (fromId === toId) throw new Error('不能把节点连到自己');
|
|
1303
1423
|
const outs = (PORT_TABLE[from.type] && PORT_TABLE[from.type].outputs) || [];
|
|
1304
1424
|
const ins = (PORT_TABLE[to.type] && PORT_TABLE[to.type].inputs) || [];
|
|
1305
|
-
const fp = String(fromPort || outs[0].id);
|
|
1306
|
-
|
|
1425
|
+
const fp = String(fromPort || (outs[0] && outs[0].id) || '');
|
|
1426
|
+
// 目标端口缺省时按语义选择,而不是死板取第一个输入口:
|
|
1427
|
+
// 剧本/文本 → 分镜表 = 剧本来源(script);分镜表 → 分镜表 = 承接上一镜(prev);其余取第一个输入口
|
|
1428
|
+
let tp = String(toPort || '').trim();
|
|
1429
|
+
if (!tp) {
|
|
1430
|
+
if (to.type === 'storyboard' && (from.type === 'text' || from.type === 'script')) tp = 'script';
|
|
1431
|
+
else if (to.type === 'storyboard' && from.type === 'storyboard') tp = 'prev';
|
|
1432
|
+
else tp = String((ins[0] && ins[0].id) || '');
|
|
1433
|
+
}
|
|
1307
1434
|
const outDef = outs.find((p) => p.id === fp);
|
|
1308
1435
|
const inDef = ins.find((p) => p.id === tp);
|
|
1309
1436
|
if (!outDef) throw new Error('源节点没有输出端口 ' + fp);
|
|
@@ -1924,6 +2051,22 @@ export function apply(ctx) {
|
|
|
1924
2051
|
return { ok: true, projectId: r.projectId, nodeId: r.nodeId, text: '🗑 已删除节点 ' + r.nodeId + '。' };
|
|
1925
2052
|
},
|
|
1926
2053
|
});
|
|
2054
|
+
reg({
|
|
2055
|
+
name: 'canvas_relayout',
|
|
2056
|
+
description: '一键重排画布节点布局(消除重叠):按「行」排列——横向数据流决定列(文本→分镜表→视频→资产),竖向关系(衔接 prev / 首帧 frame)不参与分列;每行 = 一个横向连通组(如「场N文本 ↔ 场N分镜表」同行对齐),行高取行内最高节点、行间保留间距。节点重叠、或批量建点后布局混乱时调用。',
|
|
2057
|
+
parameters: {
|
|
2058
|
+
type: 'object',
|
|
2059
|
+
properties: { projectId: { type: 'string', description: '项目 id' } },
|
|
2060
|
+
required: ['projectId'],
|
|
2061
|
+
additionalProperties: false,
|
|
2062
|
+
},
|
|
2063
|
+
output: textOutput,
|
|
2064
|
+
timeoutMs: 30000,
|
|
2065
|
+
async execute(args) {
|
|
2066
|
+
const r = canvasRelayout(args);
|
|
2067
|
+
return { ok: true, projectId: r.projectId, text: '✅ 已重排 ' + r.moved + ' 个节点(' + r.rows + ' 行,无重叠)。' };
|
|
2068
|
+
},
|
|
2069
|
+
});
|
|
1927
2070
|
reg({
|
|
1928
2071
|
name: 'canvas_delete_project',
|
|
1929
2072
|
description: '删除无限画布上的一个项目(含其全部节点)。用户明确要求删除项目、或清理误创建的空项目时使用;删除前请先用 canvas_list_projects 确认 id。',
|
|
@@ -2249,6 +2392,32 @@ export function apply(ctx) {
|
|
|
2249
2392
|
}
|
|
2250
2393
|
},
|
|
2251
2394
|
}),
|
|
2395
|
+
// 插件自带静态资源(帮助与反馈用的微信群二维码等):白名单 assets/ 目录,拒绝路径穿越
|
|
2396
|
+
ctx.webServer.register({
|
|
2397
|
+
kind: 'exact',
|
|
2398
|
+
path: `/${NAME}/api/plugin-asset`,
|
|
2399
|
+
async handler(req, res) {
|
|
2400
|
+
try {
|
|
2401
|
+
if (req.method !== 'GET') { res.statusCode = 405; res.end('仅接受 GET'); return; }
|
|
2402
|
+
const u = new URL(req.url, 'http://localhost');
|
|
2403
|
+
const rel = String(u.searchParams.get('rel') || '').replace(/\\/g, '/').replace(/^\/+/, '');
|
|
2404
|
+
if (rel.includes('..') || !/^assets\/[\w.\u4e00-\u9fa5/-]+$/.test(rel)) { res.statusCode = 400; res.end('非法路径'); return; }
|
|
2405
|
+
const assetsDir = path.join(HERE, 'assets');
|
|
2406
|
+
const abs = path.resolve(path.join(HERE, rel));
|
|
2407
|
+
if (!abs.startsWith(assetsDir)) { res.statusCode = 400; res.end('非法路径'); return; }
|
|
2408
|
+
let st;
|
|
2409
|
+
try { st = statSync(abs); } catch { res.statusCode = 404; res.end('not found'); return; }
|
|
2410
|
+
if (!st.isFile()) { res.statusCode = 404; res.end('not found'); return; }
|
|
2411
|
+
res.setHeader('content-type', mimeOf(abs));
|
|
2412
|
+
res.setHeader('cache-control', 'no-cache');
|
|
2413
|
+
res.setHeader('content-length', String(st.size));
|
|
2414
|
+
pipeFile(abs, {}, res);
|
|
2415
|
+
} catch (e) {
|
|
2416
|
+
res.statusCode = 500;
|
|
2417
|
+
res.end(String((e && e.message) || e));
|
|
2418
|
+
}
|
|
2419
|
+
},
|
|
2420
|
+
}),
|
|
2252
2421
|
];
|
|
2253
2422
|
return () => {
|
|
2254
2423
|
for (const off of registrations) { try { if (off) off(); } catch { /* 忽略 */ } }
|
package/package.json
CHANGED
|
@@ -1,56 +1,57 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
2
|
+
"name": "@ninkch/dsh-infinite-canvas",
|
|
3
|
+
"version": "0.17.15",
|
|
4
|
+
"description": "Node-based short-drama studio for DeepSeek Harness: script → storyboard → assets → shots → voiced, subtitled final cut on an infinite canvas.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"deepseek-harness",
|
|
8
|
+
"plugin",
|
|
9
|
+
"infinite-canvas",
|
|
10
|
+
"storyboard",
|
|
11
|
+
"short-drama",
|
|
12
|
+
"ai-video",
|
|
13
|
+
"image-generation",
|
|
14
|
+
"video-generation",
|
|
15
|
+
"seedance",
|
|
16
|
+
"photocraft",
|
|
17
|
+
"filmcraft"
|
|
18
|
+
],
|
|
19
|
+
"license": "Apache-2.0",
|
|
20
|
+
"author": "NINKCH",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"registry": "https://registry.npmjs.org/"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/NINKCH/dsh-infinite_canvas.git"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"icon": "./icon.svg",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./index.js",
|
|
32
|
+
"./client": "./client.js",
|
|
33
|
+
"./package.json": "./package.json",
|
|
34
|
+
"./locale/*.json": "./locale/*.json"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"index.js",
|
|
38
|
+
"client.js",
|
|
39
|
+
"icon.svg",
|
|
40
|
+
"cordis.patch.yml",
|
|
41
|
+
"locale/*.json",
|
|
42
|
+
"assets/*/SKILL.md",
|
|
43
|
+
"assets/support/*",
|
|
44
|
+
"README.md",
|
|
45
|
+
"README.zh-CN.md",
|
|
46
|
+
"docs/*.md"
|
|
47
|
+
],
|
|
48
|
+
"dsh": {
|
|
49
|
+
"bundle": {
|
|
50
|
+
"patch": "./cordis.patch.yml"
|
|
51
|
+
},
|
|
52
|
+
"client": {
|
|
53
|
+
"platform": "web",
|
|
54
|
+
"immediately": true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|