launchframe 0.4.6 → 0.4.8
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/.amazonq/cli-agents/launchframe.json +2 -2
- package/.amazonq/rules/project.md +4 -4
- package/.augment/commands/launchframe.md +14 -13
- package/.claude/skills/launchframe/SKILL.md +14 -12
- package/.clinerules +4 -4
- package/.codex/skills/launchframe/SKILL.md +14 -12
- package/.continue/commands/launchframe.md +14 -13
- package/.continue/rules/project.md +4 -4
- package/.cursor/commands/launchframe.md +13 -12
- package/.gemini/commands/launchframe.toml +14 -13
- package/.github/copilot-instructions.md +4 -4
- package/.github/skills/launchframe/SKILL.md +14 -12
- package/.opencode/commands/launchframe.md +14 -13
- package/.windsurf/workflows/launchframe.md +13 -12
- package/AGENTS.md +4 -4
- package/bin/launchframe.cjs +15 -0
- package/package.json +2 -2
- package/scripts/sync-skills.mjs +288 -117
package/scripts/sync-skills.mjs
CHANGED
|
@@ -1,117 +1,288 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Generates invokable skill/command files for all supported AI coding platforms.
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates invokable skill/command files for all supported AI coding platforms.
|
|
5
|
+
*
|
|
6
|
+
* Source of truth: every `.claude/skills/<skill-id>/SKILL.md` (YAML frontmatter + body).
|
|
7
|
+
*
|
|
8
|
+
* Required frontmatter: `description`, `argument-hint`
|
|
9
|
+
* Optional: `argument-substitution` (replaces `$ARGUMENTS`; default derives from skill `name`)
|
|
10
|
+
*
|
|
11
|
+
* Usage: node scripts/sync-skills.mjs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readdirSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
} from "node:fs";
|
|
21
|
+
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
|
|
26
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
27
|
+
|
|
28
|
+
const SKILLS_DIR = join(ROOT, ".claude/skills");
|
|
29
|
+
|
|
30
|
+
function parseFrontmatterBlock(block) {
|
|
31
|
+
/** @type {Record<string, string>} */
|
|
32
|
+
|
|
33
|
+
const fm = {};
|
|
34
|
+
|
|
35
|
+
for (const line of block.split("\n")) {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
|
|
38
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
39
|
+
|
|
40
|
+
const colon = trimmed.indexOf(":");
|
|
41
|
+
|
|
42
|
+
if (colon === -1) continue;
|
|
43
|
+
|
|
44
|
+
const key = trimmed.slice(0, colon).trim();
|
|
45
|
+
|
|
46
|
+
let val = trimmed.slice(colon + 1).trim();
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
(val.startsWith('"') && val.endsWith('"')) ||
|
|
50
|
+
(val.startsWith("'") && val.endsWith("'"))
|
|
51
|
+
) {
|
|
52
|
+
val = val.slice(1, -1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fm[key] = val;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return fm;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
|
|
63
|
+
* @param {string} sourceRel
|
|
64
|
+
|
|
65
|
+
* @returns {{ raw: string, body: string, fm: Record<string, string> }}
|
|
66
|
+
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
function readSkillSplits(sourceRel) {
|
|
70
|
+
const full = join(ROOT, sourceRel);
|
|
71
|
+
|
|
72
|
+
const raw = readFileSync(full, "utf8").replace(/\r\n/g, "\n");
|
|
73
|
+
|
|
74
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
75
|
+
|
|
76
|
+
if (!match) {
|
|
77
|
+
throw new Error(`Could not parse frontmatter: ${sourceRel}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const fm = parseFrontmatterBlock(match[1]);
|
|
81
|
+
|
|
82
|
+
const body = match[2];
|
|
83
|
+
|
|
84
|
+
return { raw, body, fm };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
|
|
89
|
+
* @param {string} id
|
|
90
|
+
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
function loadSkillMeta(id) {
|
|
94
|
+
const sourceRel = `.claude/skills/${id}/SKILL.md`.replace(/\\/g, "/");
|
|
95
|
+
|
|
96
|
+
const { raw, body, fm } = readSkillSplits(sourceRel);
|
|
97
|
+
|
|
98
|
+
const description = (fm.description ?? "").trim();
|
|
99
|
+
|
|
100
|
+
if (!description) {
|
|
101
|
+
throw new Error(`${sourceRel} must set YAML frontmatter "description:"`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const skillName = (fm.name ?? id).trim();
|
|
105
|
+
|
|
106
|
+
const augmentArgumentHint =
|
|
107
|
+
(fm["argument-hint"] ?? "").trim() || "(see skill SOURCE)";
|
|
108
|
+
|
|
109
|
+
let plainSubstitution = (fm["argument-substitution"] ?? "").trim();
|
|
110
|
+
|
|
111
|
+
if (!plainSubstitution) {
|
|
112
|
+
plainSubstitution = `the arguments you received with ${skillName.includes("/") ? skillName : `/${skillName}`}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
id,
|
|
117
|
+
|
|
118
|
+
sourceRel,
|
|
119
|
+
|
|
120
|
+
shortDesc: description,
|
|
121
|
+
|
|
122
|
+
augmentArgumentHint,
|
|
123
|
+
|
|
124
|
+
plainSubstitution,
|
|
125
|
+
|
|
126
|
+
raw,
|
|
127
|
+
|
|
128
|
+
body,
|
|
129
|
+
|
|
130
|
+
fm,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
|
|
136
|
+
* @returns {string[]}
|
|
137
|
+
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
function discoverSkillIds() {
|
|
141
|
+
if (!existsSync(SKILLS_DIR)) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return readdirSync(SKILLS_DIR)
|
|
146
|
+
.filter((name) => {
|
|
147
|
+
if (name.startsWith(".")) return false;
|
|
148
|
+
|
|
149
|
+
return existsSync(join(SKILLS_DIR, name, "SKILL.md"));
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
.sort((a, b) => a.localeCompare(b));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function write(relPath, content) {
|
|
156
|
+
const full = join(ROOT, relPath);
|
|
157
|
+
|
|
158
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
159
|
+
|
|
160
|
+
writeFileSync(full, content, "utf8");
|
|
161
|
+
|
|
162
|
+
console.log(` \u2713 ${relPath}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
|
|
167
|
+
* @param {ReturnType<typeof loadSkillMeta>} meta
|
|
168
|
+
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
function syncSkill(meta) {
|
|
172
|
+
const {
|
|
173
|
+
id,
|
|
174
|
+
|
|
175
|
+
sourceRel,
|
|
176
|
+
|
|
177
|
+
shortDesc,
|
|
178
|
+
|
|
179
|
+
augmentArgumentHint,
|
|
180
|
+
|
|
181
|
+
plainSubstitution,
|
|
182
|
+
|
|
183
|
+
raw,
|
|
184
|
+
|
|
185
|
+
body,
|
|
186
|
+
} = meta;
|
|
187
|
+
|
|
188
|
+
const noArgs = (text) => text.replace(/\$ARGUMENTS/g, plainSubstitution);
|
|
189
|
+
|
|
190
|
+
const header =
|
|
191
|
+
`<!-- AUTO-GENERATED from ${sourceRel} \u2014 do not edit directly.\n` +
|
|
192
|
+
` Run \`node scripts/sync-skills.mjs\` to regenerate. -->\n\n`;
|
|
193
|
+
|
|
194
|
+
console.log(`\nSyncing ${id}...\n Source: ${sourceRel}\n`);
|
|
195
|
+
|
|
196
|
+
write(`.codex/skills/${id}/SKILL.md`, raw);
|
|
197
|
+
|
|
198
|
+
write(`.github/skills/${id}/SKILL.md`, raw);
|
|
199
|
+
|
|
200
|
+
write(`.cursor/commands/${id}.md`, header + noArgs(body));
|
|
201
|
+
|
|
202
|
+
write(`.windsurf/workflows/${id}.md`, header + noArgs(body));
|
|
203
|
+
|
|
204
|
+
const geminiBody = body.replace(/\$ARGUMENTS/g, "{{args}}");
|
|
205
|
+
|
|
206
|
+
write(
|
|
207
|
+
`.gemini/commands/${id}.toml`,
|
|
208
|
+
|
|
209
|
+
`# AUTO-GENERATED from ${sourceRel}\n` +
|
|
210
|
+
`# Run \`node scripts/sync-skills.mjs\` to regenerate.\n\n` +
|
|
211
|
+
`description = ${JSON.stringify(shortDesc)}\n\n` +
|
|
212
|
+
`[prompt]\ntext = '''\n${geminiBody}\n'''\n`,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
write(
|
|
216
|
+
`.opencode/commands/${id}.md`,
|
|
217
|
+
|
|
218
|
+
`---\ndescription: ${JSON.stringify(shortDesc)}\n---\n${header}${body}`,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
write(
|
|
222
|
+
`.augment/commands/${id}.md`,
|
|
223
|
+
|
|
224
|
+
`---\ndescription: ${JSON.stringify(shortDesc)}\nargument-hint: ${JSON.stringify(augmentArgumentHint)}\n---\n${header}${body}`,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
write(
|
|
228
|
+
`.continue/commands/${id}.md`,
|
|
229
|
+
|
|
230
|
+
`---\nname: ${id}\ndescription: ${JSON.stringify(shortDesc)}\ninvokable: true\n---\n${header}${body}`,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
write(
|
|
234
|
+
`.amazonq/cli-agents/${id}.json`,
|
|
235
|
+
|
|
236
|
+
JSON.stringify(
|
|
237
|
+
{
|
|
238
|
+
name: id,
|
|
239
|
+
|
|
240
|
+
description: shortDesc,
|
|
241
|
+
|
|
242
|
+
prompt: noArgs(body),
|
|
243
|
+
|
|
244
|
+
fileContext: ["AGENTS.md", "docs/research/**"],
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
null,
|
|
248
|
+
|
|
249
|
+
2,
|
|
250
|
+
) + "\n",
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(
|
|
255
|
+
"Syncing skills to all platforms...\nSkills source: `.claude/skills/*/SKILL.md`",
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const skillIds = discoverSkillIds();
|
|
259
|
+
|
|
260
|
+
if (skillIds.length === 0) {
|
|
261
|
+
console.error("No skills found under .claude/skills/<id>/SKILL.md");
|
|
262
|
+
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** @type {ReturnType<typeof loadSkillMeta>[]} */
|
|
267
|
+
|
|
268
|
+
const metas = [];
|
|
269
|
+
|
|
270
|
+
for (const id of skillIds) {
|
|
271
|
+
try {
|
|
272
|
+
metas.push(loadSkillMeta(id));
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.error(e.message || e);
|
|
275
|
+
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const meta of metas) {
|
|
281
|
+
syncSkill(meta);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const totalFiles = metas.length * 9;
|
|
285
|
+
|
|
286
|
+
console.log(
|
|
287
|
+
`\nDone! ${totalFiles} platform files generated (${metas.length} skill(s) \u00d7 9 targets).`,
|
|
288
|
+
);
|