@impractical-ai/motion 0.1.16 → 0.1.18

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.
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ external_exports
4
+ } from "./chunk-QQ6NYC74.js";
5
+ import {
6
+ invoke
7
+ } from "./chunk-FTSZCIMV.js";
8
+
9
+ // src/mcp/template-source.ts
10
+ import { mkdirSync, writeFileSync } from "node:fs";
11
+ import { dirname, resolve, sep } from "node:path";
12
+ var SLUG = /^[a-z0-9][a-z0-9-]{0,119}$/;
13
+ async function fetchTemplate(slug, call) {
14
+ if (!SLUG.test(slug)) throw new Error(`"${slug}" isn't a template slug (lowercase letters, digits and dashes)`);
15
+ return call("atelier-library", { action: "film-source", slug });
16
+ }
17
+ var readingOrder = (paths) => [...paths].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
18
+ var rank = (path) => path === "direction.ts" ? 0 : path === "App.tsx" ? 1 : path.startsWith("kit/") ? 3 : 2;
19
+ var templateSourceTool = {
20
+ name: "template_source",
21
+ title: "Read a template\u2019s code",
22
+ description: "The source of a template from the Impractical template library (by its slug, as the Studio or a prompt names it): direction.ts (its choreography: timing, eases, camera, transitions), App.tsx (its set and components) and the rest of its files. Call it whenever someone gives you a template to work from, and study how it is built and moves before you plan. Adapt its structure and motion to the new video; never copy its content.",
23
+ inputSchema: {
24
+ slug: external_exports.string().describe('The template\u2019s slug, e.g. "framer-cms-list-replica".')
25
+ },
26
+ // read-only library browsing: admitted (unmetered) as the library's other lookups are
27
+ admitAs: "atelier_study",
28
+ handler: async (args, ctx) => {
29
+ const source = await fetchTemplate(String(args.slug ?? ""), ctx.invoke);
30
+ const paths = readingOrder(Object.keys(source.files));
31
+ const kits = paths.filter((p) => p.startsWith("kit/"));
32
+ const shown = paths.filter((p) => !p.startsWith("kit/"));
33
+ return {
34
+ text: `TEMPLATE ${args.slug}: ${paths.length} file(s). Read direction.ts first (how it moves), then App.tsx (what it shows).` + (kits.length ? ` Kits it uses (install_kits provides them): ${kits.join(", ")}.` : "") + "\n\n" + shown.map((p) => `=== ${p} ===
35
+ ${source.files[p]}`).join("\n\n")
36
+ };
37
+ }
38
+ };
39
+ function rankTemplates(items, query) {
40
+ const films = items.filter((item) => item.kind === "film");
41
+ const words = query.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 1);
42
+ if (!words.length) return films;
43
+ const score = (item) => {
44
+ const name = `${item.title} ${item.slug}`.toLowerCase();
45
+ const about = `${item.creator ?? ""} ${item.description ?? ""}`.toLowerCase();
46
+ return words.reduce((sum, word) => sum + (name.includes(word) ? 3 : 0) + (about.includes(word) ? 1 : 0), 0);
47
+ };
48
+ return films.map((item) => ({ item, score: score(item) })).filter((x) => x.score > 0).sort((a, b) => b.score - a.score).map((x) => x.item);
49
+ }
50
+ var templateSearchTool = {
51
+ name: "template_search",
52
+ title: "Find a template",
53
+ description: 'Search the Impractical template library (the Studio\u2019s templates: complete films, many of them replicas of real launch films such as Notion Mail, Attio or Framer) by name or words, and get each match\u2019s slug. Use it when someone mentions a template, a film in the library, or "the X one", then read the one they mean with template_source. Leave the query empty to list them all.',
54
+ inputSchema: {
55
+ query: external_exports.string().max(200).optional().describe('Words from the template\u2019s name or what it shows, e.g. "notion mail" or "attio sequences".')
56
+ },
57
+ // read-only library browsing: admitted (unmetered) as the library's other lookups are
58
+ admitAs: "atelier_study",
59
+ handler: async (args, ctx) => {
60
+ const { items } = await ctx.invoke("atelier-library", { action: "catalog" });
61
+ const query = String(args.query ?? "");
62
+ const found = rankTemplates(items ?? [], query).slice(0, 25);
63
+ if (!found.length) {
64
+ return { text: `No template matches "${query}". Call template_search with no query to see them all.` };
65
+ }
66
+ const lines = found.map(
67
+ (item) => `- ${item.slug}: ${item.title}${item.durationSeconds ? ` (${Math.round(item.durationSeconds)}s)` : ""}` + (item.description ? ` \u2014 ${item.description.slice(0, 200)}` : "")
68
+ );
69
+ return {
70
+ text: `${found.length} template(s)${query ? ` for "${query}"` : ""}, best match first:
71
+ ${lines.join("\n")}
72
+
73
+ Read the one meant with template_source({ slug }).`
74
+ };
75
+ }
76
+ };
77
+ async function templateSourceCommand(slug, dir) {
78
+ if (!slug || !dir) throw new Error("Usage: impractical template-source <slug> --dir <folder>");
79
+ const source = await fetchTemplate(slug, invoke);
80
+ const root = resolve(dir);
81
+ const written = [];
82
+ for (const path of readingOrder(Object.keys(source.files))) {
83
+ const target = resolve(root, path);
84
+ if (!target.startsWith(root + sep)) continue;
85
+ mkdirSync(dirname(target), { recursive: true });
86
+ writeFileSync(target, source.files[path]);
87
+ written.push(path);
88
+ }
89
+ return written.join("\n");
90
+ }
91
+
92
+ export {
93
+ templateSourceTool,
94
+ rankTemplates,
95
+ templateSearchTool,
96
+ templateSourceCommand
97
+ };