@kaydash9999/banker-plugins 0.10.0 → 0.12.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +39 -0
- package/README.md +73 -11
- package/codex/manifest.json +8 -1
- package/commands/setup.md +2 -0
- package/package.json +6 -1
- package/skills/3d-intro-build/SKILL.md +214 -0
- package/skills/3d-intro-build/references/LICENSE +21 -0
- package/skills/3d-intro-build/references/NOTICE.md +32 -0
- package/skills/3d-intro-build/references/assemble.mjs +252 -0
- package/skills/3d-intro-build/references/azure-adapter.mjs +495 -0
- package/skills/3d-intro-build/references/curate.mjs +301 -0
- package/skills/3d-intro-build/references/index-template.html +73 -0
- package/skills/3d-intro-build/references/prompts.md +171 -0
- package/skills/3d-intro-build/references/scrub-engine.js +448 -0
- package/skills/3d-intro-build/references/serve.mjs +123 -0
- package/skills/3d-intro-setup/SKILL.md +196 -0
- package/skills/3d-intro-setup/references/azure-adapter.mjs +495 -0
- package/skills/lineage/SKILL.md +126 -134
- package/skills/lineage/lineage.py +1041 -354
- package/skills/motion-graphic-make/SKILL.md +57 -0
- package/skills/motion-graphic-setup/SKILL.md +141 -0
- package/skills/ralph-qa/SKILL.md +59 -32
- package/skills/refresh-readme/SKILL.md +3 -3
- package/skills/vertical-pptx/SKILL.md +409 -0
- package/skills/vertical-pptx/build_a4p.js +923 -0
- package/skills/vertical-pptx/build_a4p.py +916 -0
- package/skills/vertical-pptx/verify_a4p.mjs +2168 -0
- package/skills/vertical-pptx-setup/SKILL.md +223 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* First-party assembler for the 3d-intro-build skill.
|
|
4
|
+
*
|
|
5
|
+
* Takes a project directory that already holds the generated stills + forward-chained clips
|
|
6
|
+
* (+ an intro.json manifest describing them) and produces a self-contained preview site:
|
|
7
|
+
* <outDir>/
|
|
8
|
+
* index.html (written from index-template.html, wired to the manifest)
|
|
9
|
+
* scrub-engine.js (copied verbatim from references/)
|
|
10
|
+
* assets/<id>.<ext> (scene posters/stills)
|
|
11
|
+
* assets/vid/<id>.mp4 (per-scene dive clips)
|
|
12
|
+
* assets/vid/connN.mp4 (optional connectors)
|
|
13
|
+
* Serve <outDir> with serve.mjs to preview.
|
|
14
|
+
*
|
|
15
|
+
* Runtime: Node >=18 builtins ONLY (node:fs / node:path / node:url). No external deps,
|
|
16
|
+
* no shell — cross-platform (path.join / path.sep; asset URLs are always posix '/').
|
|
17
|
+
*
|
|
18
|
+
* The manifest (intro.json) mirrors the mountScrollWorld config, but its `still` / `clip` /
|
|
19
|
+
* `connectors` values are paths to SOURCE files relative to projectDir; this assembler copies
|
|
20
|
+
* them into assets/ and rewrites the config to the copied relative URLs. Shape:
|
|
21
|
+
*
|
|
22
|
+
* {
|
|
23
|
+
* "pageTitle": "BRAND — the world of SUBJECT", // optional (else derived)
|
|
24
|
+
* "pageDescription": "Scroll to fly through ...", // optional (else derived)
|
|
25
|
+
* "brand": { "name": "BRAND", "href": "#top" }, // optional
|
|
26
|
+
* "cta": { "label": "Order now", "href": "#finale" }, // optional top-bar CTA
|
|
27
|
+
* "hint": "scroll to fly in", // optional
|
|
28
|
+
* "theme": { "bg":"#F5EDE0","ink":"#241d2b","inkSoft":"#6a6072","accent":"#9B7EBD" }, // optional
|
|
29
|
+
* "diveScroll": 1.3, "connScroll": 0.9, "crossfade": 0.12, // optional
|
|
30
|
+
* "nav": true, "atmosphere": true, // optional (only false is emitted)
|
|
31
|
+
* "sections": [ // required, >= 1, in order
|
|
32
|
+
* { "id":"sceneA", "label":"Scene A",
|
|
33
|
+
* "still":"still-1.png", "clip":"dive-1.mp4", // required (relative to projectDir)
|
|
34
|
+
* "stillMobile":"still-1-m.png", "clipMobile":"dive-1-m.mp4", // optional
|
|
35
|
+
* "accent":"#8FB98A",
|
|
36
|
+
* "eyebrow":"...", "title":"...", "body":"...", "tags":["...","..."],
|
|
37
|
+
* "scroll":1.6, "linger":0.45, // optional pacing
|
|
38
|
+
* "cta": { "primary":{"label":"","href":""}, "secondary":{"label":"","href":""} } } // last only
|
|
39
|
+
* ],
|
|
40
|
+
* "connectors": ["conn-1.mp4", null, ...], // optional, len = sections.length-1
|
|
41
|
+
* "connectorsMobile": ["conn-1-m.mp4", ...] // optional, same length
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* Usage:
|
|
45
|
+
* node assemble.mjs <projectDir> [outDir] [--manifest path]
|
|
46
|
+
* projectDir holds intro.json + the source stills/clips it references
|
|
47
|
+
* outDir output site dir (default: <projectDir>/site)
|
|
48
|
+
* --manifest manifest path (default: <projectDir>/intro.json)
|
|
49
|
+
*/
|
|
50
|
+
import fs from 'node:fs';
|
|
51
|
+
import path from 'node:path';
|
|
52
|
+
import { fileURLToPath } from 'node:url';
|
|
53
|
+
|
|
54
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
55
|
+
|
|
56
|
+
// ---- small helpers ----------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
// Convert a posix-style relative URL ("assets/vid/x.mp4") to an OS filesystem path.
|
|
59
|
+
const osPath = (base, posixRel) => path.join(base, ...posixRel.split('/'));
|
|
60
|
+
|
|
61
|
+
// Copy one source asset (path relative to projectDir) to a posix dest URL under outDir.
|
|
62
|
+
function copyAsset(projectDir, outDir, srcRel, destPosix) {
|
|
63
|
+
const src = path.join(projectDir, ...String(srcRel).split(/[\\/]/));
|
|
64
|
+
if (!fs.existsSync(src)) throw new Error(`assemble: asset not found: ${srcRel} (looked at ${src})`);
|
|
65
|
+
const dest = osPath(outDir, destPosix);
|
|
66
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
67
|
+
fs.copyFileSync(src, dest);
|
|
68
|
+
return destPosix;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Extension of a source path, lowercased, with the dot ('.png'); '' if none.
|
|
72
|
+
const extOf = (p) => path.extname(String(p)).toLowerCase();
|
|
73
|
+
|
|
74
|
+
// ---- config build -----------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build the exact mountScrollWorld config object from a manifest, copying every referenced
|
|
78
|
+
* asset into outDir/assets. Returns { config, copied:[destPosix,...] }.
|
|
79
|
+
*/
|
|
80
|
+
function buildConfig(manifest, projectDir, outDir) {
|
|
81
|
+
const sections = Array.isArray(manifest.sections) ? manifest.sections : [];
|
|
82
|
+
if (sections.length === 0) throw new Error('assemble: manifest.sections must have at least one section');
|
|
83
|
+
|
|
84
|
+
const copied = [];
|
|
85
|
+
const track = (destPosix) => { copied.push(destPosix); return destPosix; };
|
|
86
|
+
|
|
87
|
+
const sectionsOut = sections.map((s, i) => {
|
|
88
|
+
if (!s || !s.id) throw new Error(`assemble: section[${i}] missing "id"`);
|
|
89
|
+
if (!s.still) throw new Error(`assemble: section[${i}] (${s.id}) missing "still"`);
|
|
90
|
+
if (!s.clip) throw new Error(`assemble: section[${i}] (${s.id}) missing "clip"`);
|
|
91
|
+
|
|
92
|
+
const out = { id: s.id, label: s.label || s.id };
|
|
93
|
+
|
|
94
|
+
// poster still (keep source extension so png/webp/jpg all work)
|
|
95
|
+
out.still = track(copyAsset(projectDir, outDir, s.still, `assets/${s.id}${extOf(s.still)}`));
|
|
96
|
+
if (s.stillMobile) {
|
|
97
|
+
out.stillMobile = track(copyAsset(projectDir, outDir, s.stillMobile, `assets/${s.id}-m${extOf(s.stillMobile)}`));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// dive clip
|
|
101
|
+
out.clip = track(copyAsset(projectDir, outDir, s.clip, `assets/vid/${s.id}${extOf(s.clip) || '.mp4'}`));
|
|
102
|
+
if (s.clipMobile) {
|
|
103
|
+
out.clipMobile = track(copyAsset(projectDir, outDir, s.clipMobile, `assets/vid/${s.id}-m${extOf(s.clipMobile) || '.mp4'}`));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (s.accent) out.accent = s.accent;
|
|
107
|
+
if (s.eyebrow) out.eyebrow = s.eyebrow;
|
|
108
|
+
if (s.title) out.title = s.title;
|
|
109
|
+
if (s.body) out.body = s.body;
|
|
110
|
+
if (Array.isArray(s.tags) && s.tags.length) out.tags = s.tags;
|
|
111
|
+
if (s.scroll != null) out.scroll = s.scroll;
|
|
112
|
+
if (s.linger != null) out.linger = s.linger;
|
|
113
|
+
if (s.cta) out.cta = s.cta; // last section only (engine renders it wherever present)
|
|
114
|
+
return out;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// connectors: length === sections.length - 1; null entries mean "crossfade directly".
|
|
118
|
+
const srcConn = Array.isArray(manifest.connectors) ? manifest.connectors : [];
|
|
119
|
+
const srcConnM = Array.isArray(manifest.connectorsMobile) ? manifest.connectorsMobile : [];
|
|
120
|
+
const connectorsOut = [];
|
|
121
|
+
const connectorsMobileOut = [];
|
|
122
|
+
for (let i = 0; i < sectionsOut.length - 1; i++) {
|
|
123
|
+
const c = srcConn[i];
|
|
124
|
+
connectorsOut.push(c ? track(copyAsset(projectDir, outDir, c, `assets/vid/conn${i + 1}${extOf(c) || '.mp4'}`)) : null);
|
|
125
|
+
const cm = srcConnM[i];
|
|
126
|
+
connectorsMobileOut.push(cm ? track(copyAsset(projectDir, outDir, cm, `assets/vid/conn${i + 1}-m${extOf(cm) || '.mp4'}`)) : null);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Assemble in the shape mountScrollWorld reads (see scrub-engine.js).
|
|
130
|
+
const config = {};
|
|
131
|
+
if (manifest.brand) config.brand = manifest.brand;
|
|
132
|
+
if (manifest.cta) config.cta = manifest.cta;
|
|
133
|
+
config.hint = manifest.hint || 'scroll to fly in';
|
|
134
|
+
config.diveScroll = manifest.diveScroll != null ? manifest.diveScroll : 1.3;
|
|
135
|
+
config.connScroll = manifest.connScroll != null ? manifest.connScroll : 0.9;
|
|
136
|
+
if (manifest.crossfade != null) config.crossfade = manifest.crossfade;
|
|
137
|
+
if (manifest.nav === false) config.nav = false;
|
|
138
|
+
if (manifest.atmosphere === false) config.atmosphere = false;
|
|
139
|
+
config.sections = sectionsOut;
|
|
140
|
+
config.connectors = connectorsOut;
|
|
141
|
+
if (connectorsMobileOut.some(Boolean)) config.connectorsMobile = connectorsMobileOut;
|
|
142
|
+
|
|
143
|
+
return { config, copied };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---- index.html generation --------------------------------------------------
|
|
147
|
+
|
|
148
|
+
// Replace the value of a --sw-* custom property in the template's :root block, keeping comments.
|
|
149
|
+
function setToken(html, name, value) {
|
|
150
|
+
if (!value) return html;
|
|
151
|
+
const re = new RegExp(`(--${name}\\s*:\\s*)[^;]+;`);
|
|
152
|
+
return html.replace(re, `$1${value};`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Render index.html from index-template.html for the given config + manifest metadata. */
|
|
156
|
+
function renderIndexHtml(template, config, manifest) {
|
|
157
|
+
const brandName = manifest.brand?.name || 'BRAND';
|
|
158
|
+
const subject = manifest.subject || 'SUBJECT';
|
|
159
|
+
const title = manifest.pageTitle || `${brandName} — the world of ${subject}`;
|
|
160
|
+
const desc = manifest.pageDescription || `Scroll to fly through the world of ${brandName.replace(/\.+$/, '')}.`;
|
|
161
|
+
|
|
162
|
+
// Everything up to the engine <script> tag is the head+body-open; regenerate the tail.
|
|
163
|
+
const marker = '<script src="scrub-engine.js">';
|
|
164
|
+
const idx = template.indexOf(marker);
|
|
165
|
+
if (idx === -1) throw new Error('assemble: index-template.html missing the scrub-engine.js script tag');
|
|
166
|
+
let head = template.slice(0, idx).replace(/\s*$/, '\n');
|
|
167
|
+
|
|
168
|
+
head = head.replace(/<title>[^<]*<\/title>/, `<title>${escHtml(title)}</title>`);
|
|
169
|
+
head = head.replace(/(<meta name="description" content=")[^"]*(")/, `$1${escAttr(desc)}$2`);
|
|
170
|
+
const t = manifest.theme || {};
|
|
171
|
+
head = setToken(head, 'sw-bg', t.bg);
|
|
172
|
+
head = setToken(head, 'sw-ink', t.ink);
|
|
173
|
+
head = setToken(head, 'sw-ink-soft', t.inkSoft);
|
|
174
|
+
head = setToken(head, 'sw-accent', t.accent);
|
|
175
|
+
|
|
176
|
+
const configJson = JSON.stringify(config, null, 2).replace(/\n/g, '\n ');
|
|
177
|
+
const tail =
|
|
178
|
+
` <script src="scrub-engine.js"></script>
|
|
179
|
+
<script>
|
|
180
|
+
mountScrollWorld(document.getElementById('world'), ${configJson});
|
|
181
|
+
</script>
|
|
182
|
+
</body>
|
|
183
|
+
</html>
|
|
184
|
+
`;
|
|
185
|
+
return head + tail;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const escHtml = (s) => String(s).replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c]));
|
|
189
|
+
const escAttr = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
|
190
|
+
|
|
191
|
+
// ---- public API -------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Assemble a preview site. Copies scrub-engine.js + all referenced assets into outDir and
|
|
195
|
+
* writes index.html wired to the exact mountScrollWorld config.
|
|
196
|
+
* @returns {{ outDir:string, index:string, engine:string, assets:string[] }}
|
|
197
|
+
*/
|
|
198
|
+
export function assemble({ projectDir, outDir, manifestPath } = {}) {
|
|
199
|
+
if (!projectDir) throw new Error('assemble: projectDir is required');
|
|
200
|
+
const proj = path.resolve(projectDir);
|
|
201
|
+
const out = path.resolve(outDir || path.join(proj, 'site'));
|
|
202
|
+
const mPath = manifestPath ? path.resolve(manifestPath) : path.join(proj, 'intro.json');
|
|
203
|
+
if (!fs.existsSync(mPath)) {
|
|
204
|
+
throw new Error(`assemble: manifest not found: ${mPath}\n` +
|
|
205
|
+
'Write an intro.json in the project dir (see the header of assemble.mjs for its shape).');
|
|
206
|
+
}
|
|
207
|
+
const manifest = JSON.parse(fs.readFileSync(mPath, 'utf8'));
|
|
208
|
+
|
|
209
|
+
fs.mkdirSync(out, { recursive: true });
|
|
210
|
+
const { config, copied } = buildConfig(manifest, proj, out);
|
|
211
|
+
|
|
212
|
+
// Vendored engine sits next to this script; copy it beside index.html.
|
|
213
|
+
const engineSrc = path.join(HERE, 'scrub-engine.js');
|
|
214
|
+
if (!fs.existsSync(engineSrc)) throw new Error(`assemble: scrub-engine.js not found at ${engineSrc}`);
|
|
215
|
+
fs.copyFileSync(engineSrc, path.join(out, 'scrub-engine.js'));
|
|
216
|
+
|
|
217
|
+
const template = fs.readFileSync(path.join(HERE, 'index-template.html'), 'utf8');
|
|
218
|
+
const html = renderIndexHtml(template, config, manifest);
|
|
219
|
+
const indexPath = path.join(out, 'index.html');
|
|
220
|
+
fs.writeFileSync(indexPath, html);
|
|
221
|
+
|
|
222
|
+
return { outDir: out, index: indexPath, engine: path.join(out, 'scrub-engine.js'), assets: copied };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---- CLI --------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
// Run only when invoked directly (not when imported by a test/other module).
|
|
228
|
+
const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
229
|
+
if (invokedDirectly) {
|
|
230
|
+
const argv = process.argv.slice(2);
|
|
231
|
+
let projectDir = null, outDir = null, manifestPath = null;
|
|
232
|
+
for (let i = 0; i < argv.length; i++) {
|
|
233
|
+
const a = argv[i];
|
|
234
|
+
if (a === '--manifest') manifestPath = argv[++i];
|
|
235
|
+
else if (a.startsWith('--manifest=')) manifestPath = a.slice('--manifest='.length);
|
|
236
|
+
else if (!a.startsWith('--')) { if (!projectDir) projectDir = a; else if (!outDir) outDir = a; }
|
|
237
|
+
}
|
|
238
|
+
if (!projectDir) {
|
|
239
|
+
console.error('usage: node assemble.mjs <projectDir> [outDir] [--manifest path]');
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const r = assemble({ projectDir, outDir, manifestPath });
|
|
244
|
+
console.log(`assembled ${r.assets.length} asset(s) -> ${r.outDir}`);
|
|
245
|
+
console.log(`index: ${r.index}`);
|
|
246
|
+
console.log(`engine: ${r.engine}`);
|
|
247
|
+
console.log(`\npreview: node ${path.join(HERE, 'serve.mjs')} ${r.outDir}`);
|
|
248
|
+
} catch (e) {
|
|
249
|
+
console.error(String(e.message || e));
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
}
|