rahman-resources 0.3.0 → 0.4.1
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/bin/cli.js +281 -54
- package/lib/manifest.json +245 -6
- package/lib/post-init.mjs +102 -0
- package/lib/rr-schema.json +94 -0
- package/lib/rr.mjs +134 -0
- package/lib/skills.json +154 -0
- package/package.json +4 -2
package/bin/cli.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// rahman-resources — installer for the Rahman kitab.
|
|
3
3
|
// Usage:
|
|
4
|
-
// npx rahman-resources init <app-name> [--
|
|
4
|
+
// npx rahman-resources init <app-name> [--template <slug>] [--features a,b] [--skills x,y]
|
|
5
5
|
// npx rahman-resources add <slug> [target-dir]
|
|
6
|
-
// npx rahman-resources
|
|
6
|
+
// npx rahman-resources add-skill <slug> [target-dir]
|
|
7
|
+
// npx rahman-resources list [layouts|recipes|features|skills]
|
|
7
8
|
// npx rahman-resources info <slug>
|
|
9
|
+
// npx rahman-resources doctor
|
|
10
|
+
// npx rahman-resources mcp # not implemented in CLI; install @rahman-resources/mcp
|
|
8
11
|
|
|
9
12
|
import { createRequire } from "node:module";
|
|
10
13
|
import { spawn } from "node:child_process";
|
|
@@ -15,19 +18,29 @@ import { fileURLToPath } from "node:url";
|
|
|
15
18
|
import kleur from "kleur";
|
|
16
19
|
import tiged from "tiged";
|
|
17
20
|
|
|
21
|
+
import {
|
|
22
|
+
readRr,
|
|
23
|
+
writeRr,
|
|
24
|
+
rrExists,
|
|
25
|
+
validateRr,
|
|
26
|
+
addFeature as rrAddFeature,
|
|
27
|
+
addSkill as rrAddSkill,
|
|
28
|
+
} from "../lib/rr.mjs";
|
|
29
|
+
import { runPostInit } from "../lib/post-init.mjs";
|
|
30
|
+
|
|
18
31
|
const require = createRequire(import.meta.url);
|
|
19
32
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
33
|
const manifest = require(path.join(__dirname, "../lib/manifest.json"));
|
|
34
|
+
const skillsInventory = require(path.join(__dirname, "../lib/skills.json"));
|
|
21
35
|
|
|
22
36
|
const REPO = manifest.repo ?? "rahmanef63/resource-site";
|
|
23
37
|
const BRANCH = manifest.branch ?? "main";
|
|
38
|
+
const SKILLS_REPO = "anthropics/skills";
|
|
24
39
|
|
|
25
40
|
const KINDS = /** @type {const} */ (["layout", "recipe", "feature"]);
|
|
26
41
|
|
|
27
42
|
const [, , cmd, ...rest] = process.argv;
|
|
28
43
|
|
|
29
|
-
// Defer to next tick so all module-level const declarations finish initializing
|
|
30
|
-
// before the dispatch reaches functions that reference them.
|
|
31
44
|
queueMicrotask(() =>
|
|
32
45
|
main().catch((err) => {
|
|
33
46
|
console.error(kleur.red("✖"), err.message ?? err);
|
|
@@ -43,11 +56,17 @@ async function main() {
|
|
|
43
56
|
return runInit(rest);
|
|
44
57
|
case "add":
|
|
45
58
|
return runAdd(rest);
|
|
59
|
+
case "add-skill":
|
|
60
|
+
return runAddSkill(rest);
|
|
46
61
|
case "list":
|
|
47
62
|
case "ls":
|
|
48
63
|
return runList(rest);
|
|
49
64
|
case "info":
|
|
50
65
|
return runInfo(rest);
|
|
66
|
+
case "doctor":
|
|
67
|
+
return runDoctor(rest);
|
|
68
|
+
case "mcp":
|
|
69
|
+
return runMcpHint();
|
|
51
70
|
case undefined:
|
|
52
71
|
case "-h":
|
|
53
72
|
case "--help":
|
|
@@ -71,23 +90,58 @@ function printVersion() {
|
|
|
71
90
|
|
|
72
91
|
function printHelp() {
|
|
73
92
|
console.log(`
|
|
74
|
-
${kleur.bold("rahman-resources")} — scaffold + install templates, recipes, features
|
|
93
|
+
${kleur.bold("rahman-resources")} — scaffold + install templates, recipes, features, Claude skills
|
|
75
94
|
|
|
76
95
|
${kleur.bold("Usage:")}
|
|
77
|
-
npx rahman-resources init <app-name> [--
|
|
96
|
+
npx rahman-resources init <app-name> [--template <slug>] [--features a,b] [--skills x,y]
|
|
97
|
+
[--no-install] [--with-shadcn-reinit]
|
|
78
98
|
npx rahman-resources add <slug> [target-dir]
|
|
79
|
-
npx rahman-resources
|
|
99
|
+
npx rahman-resources add-skill <slug> [target-dir]
|
|
100
|
+
npx rahman-resources list [layouts|recipes|features|skills]
|
|
80
101
|
npx rahman-resources info <slug>
|
|
102
|
+
npx rahman-resources doctor
|
|
103
|
+
npx rahman-resources mcp
|
|
104
|
+
|
|
105
|
+
${kleur.bold("Init flags:")}
|
|
106
|
+
--no-install skip 'npm install' step (faster scaffolds; you run it manually)
|
|
107
|
+
--with-shadcn-reinit delete starter components.json + run 'npx shadcn init -y -d' (canonical shadcn flow)
|
|
81
108
|
|
|
82
109
|
${kleur.bold("Examples:")}
|
|
83
|
-
npx rahman-resources init my-app
|
|
84
|
-
npx rahman-resources
|
|
85
|
-
npx rahman-resources
|
|
86
|
-
npx rahman-resources add
|
|
87
|
-
npx rahman-resources
|
|
110
|
+
npx rahman-resources init my-app
|
|
111
|
+
npx rahman-resources init my-app --template personal-brand-os --skills frontend-design,mcp-builder
|
|
112
|
+
npx rahman-resources init my-app --no-install
|
|
113
|
+
npx rahman-resources add personal-brand-os .
|
|
114
|
+
npx rahman-resources add-skill webapp-testing
|
|
115
|
+
npx rahman-resources list skills
|
|
88
116
|
`);
|
|
89
117
|
}
|
|
90
118
|
|
|
119
|
+
// ─── flag parsing ─────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
function parseFlags(rest) {
|
|
122
|
+
const positional = [];
|
|
123
|
+
const flags = {};
|
|
124
|
+
for (let i = 0; i < rest.length; i++) {
|
|
125
|
+
const a = rest[i];
|
|
126
|
+
if (a.startsWith("--")) {
|
|
127
|
+
const key = a.slice(2);
|
|
128
|
+
const next = rest[i + 1];
|
|
129
|
+
if (next && !next.startsWith("--")) { flags[key] = next; i++; }
|
|
130
|
+
else flags[key] = true;
|
|
131
|
+
} else {
|
|
132
|
+
positional.push(a);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { positional, flags };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function csv(s) {
|
|
139
|
+
if (!s || s === true) return [];
|
|
140
|
+
return String(s).split(",").map((x) => x.trim()).filter(Boolean);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── catalog lookups ──────────────────────────────────────────────────────
|
|
144
|
+
|
|
91
145
|
function findEntry(slug) {
|
|
92
146
|
for (const kind of KINDS) {
|
|
93
147
|
const list = manifest[kind + "s"];
|
|
@@ -97,24 +151,49 @@ function findEntry(slug) {
|
|
|
97
151
|
return null;
|
|
98
152
|
}
|
|
99
153
|
|
|
154
|
+
function findSkill(slug) {
|
|
155
|
+
return skillsInventory.skills.find((s) => s.slug === slug) ?? null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── list / info ──────────────────────────────────────────────────────────
|
|
159
|
+
|
|
100
160
|
function runList([filter]) {
|
|
101
|
-
const groups = filter
|
|
161
|
+
const groups = filter
|
|
162
|
+
? [filter]
|
|
163
|
+
: ["layouts", "recipes", "features", "skills"];
|
|
102
164
|
for (const g of groups) {
|
|
165
|
+
if (g === "skills") {
|
|
166
|
+
console.log(`\n${kleur.bold("SKILLS")} ${kleur.dim(`(${skillsInventory.skills.length})`)}\n`);
|
|
167
|
+
for (const s of skillsInventory.skills) {
|
|
168
|
+
console.log(` ${kleur.cyan(s.slug.padEnd(28))} ${kleur.dim((s.category ?? "").padEnd(12))} ${s.title}`);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
103
172
|
const list = manifest[g];
|
|
104
173
|
if (!list || list.length === 0) continue;
|
|
105
174
|
console.log(`\n${kleur.bold(g.toUpperCase())} ${kleur.dim(`(${list.length})`)}\n`);
|
|
106
175
|
for (const t of list) {
|
|
107
176
|
const cat = (t.category ?? t.source ?? "").padEnd(20).slice(0, 20);
|
|
108
|
-
console.log(
|
|
109
|
-
` ${kleur.cyan(t.slug.padEnd(30))} ${kleur.dim(cat)} ${t.title}`,
|
|
110
|
-
);
|
|
177
|
+
console.log(` ${kleur.cyan(t.slug.padEnd(30))} ${kleur.dim(cat)} ${t.title}`);
|
|
111
178
|
}
|
|
112
179
|
}
|
|
113
|
-
console.log(`\nRun ${kleur.cyan("info <slug>")} for detail, ${kleur.cyan("add <slug>
|
|
180
|
+
console.log(`\nRun ${kleur.cyan("info <slug>")} for detail, ${kleur.cyan("add <slug>")} or ${kleur.cyan("add-skill <slug>")} to install.\n`);
|
|
114
181
|
}
|
|
115
182
|
|
|
116
183
|
function runInfo([slug]) {
|
|
117
184
|
if (!slug) throw new Error("Usage: rahman-resources info <slug>");
|
|
185
|
+
const skill = findSkill(slug);
|
|
186
|
+
if (skill) {
|
|
187
|
+
console.log(`
|
|
188
|
+
${kleur.bold(skill.title)} ${kleur.dim("[skill]")} ${kleur.dim(skill.category)}
|
|
189
|
+
|
|
190
|
+
${skill.description}
|
|
191
|
+
|
|
192
|
+
${kleur.bold("Source:")} ${skill.source}/${skill.path}
|
|
193
|
+
${kleur.bold("Install:")} ${kleur.cyan(`npx rahman-resources add-skill ${skill.slug}`)}
|
|
194
|
+
`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
118
197
|
const found = findEntry(slug);
|
|
119
198
|
if (!found) throw new Error(`Slug not found: ${slug}. Run 'list' to see all.`);
|
|
120
199
|
const { kind, entry: t } = found;
|
|
@@ -124,7 +203,6 @@ ${kleur.bold(t.title)} ${kleur.dim(`[${kind}]`)} ${kleur.dim(t.category ?? "")
|
|
|
124
203
|
|
|
125
204
|
${t.description}
|
|
126
205
|
`);
|
|
127
|
-
|
|
128
206
|
if (kind === "layout") {
|
|
129
207
|
console.log(`${kleur.bold("Pulls:")}`);
|
|
130
208
|
console.log(t.pullPaths.map((p) => ` · ${p}`).join("\n") || " (none)");
|
|
@@ -142,13 +220,12 @@ ${t.description}
|
|
|
142
220
|
console.log(`${kleur.bold("Files:")}`);
|
|
143
221
|
console.log(t.files.map((f) => ` · ${f}`).join("\n"));
|
|
144
222
|
}
|
|
145
|
-
|
|
146
223
|
if (t.docsUrl) console.log(`\n${kleur.dim(`Docs: ${t.docsUrl}`)}`);
|
|
147
224
|
console.log(`${kleur.dim(`Source: ${t.source ?? "—"}`)}\n`);
|
|
148
225
|
}
|
|
149
226
|
|
|
150
|
-
//
|
|
151
|
-
|
|
227
|
+
// ─── starter copy ─────────────────────────────────────────────────────────
|
|
228
|
+
|
|
152
229
|
const STARTER_RENAME_PAIRS = [
|
|
153
230
|
["_package", "package"],
|
|
154
231
|
["_gitignore", ".gitignore"],
|
|
@@ -182,9 +259,13 @@ function copyStarterTree(src, dest, appName, slug) {
|
|
|
182
259
|
}
|
|
183
260
|
}
|
|
184
261
|
|
|
185
|
-
|
|
262
|
+
// ─── init ─────────────────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
async function runInit(rest) {
|
|
265
|
+
const { positional, flags } = parseFlags(rest);
|
|
266
|
+
const [appName] = positional;
|
|
186
267
|
if (!appName || appName.startsWith("-")) {
|
|
187
|
-
throw new Error("Usage: rahman-resources init <app-name>");
|
|
268
|
+
throw new Error("Usage: rahman-resources init <app-name> [--template slug] [--features a,b] [--skills x,y]");
|
|
188
269
|
}
|
|
189
270
|
const slug = appName.replace(/[^a-z0-9-_]/gi, "-").toLowerCase();
|
|
190
271
|
const target = path.resolve(process.cwd(), appName);
|
|
@@ -192,34 +273,99 @@ async function runInit([appName, ...flags]) {
|
|
|
192
273
|
throw new Error(`Directory already exists: ${target}`);
|
|
193
274
|
}
|
|
194
275
|
|
|
276
|
+
const features = csv(flags.features);
|
|
277
|
+
const skills = csv(flags.skills);
|
|
278
|
+
const template = typeof flags.template === "string" ? flags.template : null;
|
|
279
|
+
|
|
280
|
+
if (template && !findEntry(template)) {
|
|
281
|
+
throw new Error(`Unknown template: ${template}. Run 'list layouts' to see available.`);
|
|
282
|
+
}
|
|
283
|
+
for (const s of skills) {
|
|
284
|
+
if (!findSkill(s)) throw new Error(`Unknown skill: ${s}. Run 'list skills' to see available.`);
|
|
285
|
+
}
|
|
286
|
+
for (const f of features) {
|
|
287
|
+
if (!findEntry(f)) throw new Error(`Unknown feature: ${f}. Run 'list features' to see available.`);
|
|
288
|
+
}
|
|
289
|
+
|
|
195
290
|
console.log(kleur.bold(`\n→ Scaffolding ${kleur.cyan(slug)} (Next 16 + Convex + shadcn)\n`));
|
|
196
291
|
|
|
197
292
|
const starter = path.join(__dirname, "../lib/starter");
|
|
198
|
-
if (!existsSync(starter)) {
|
|
199
|
-
throw new Error(`Starter not found at ${starter}`);
|
|
200
|
-
}
|
|
293
|
+
if (!existsSync(starter)) throw new Error(`Starter not found at ${starter}`);
|
|
201
294
|
|
|
202
295
|
process.stdout.write(` copying starter ... `);
|
|
203
296
|
copyStarterTree(starter, target, appName, slug);
|
|
204
297
|
console.log(kleur.green("ok"));
|
|
205
298
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
299
|
+
const skipInstall = !!flags["no-install"];
|
|
300
|
+
const reinitShadcn = !!flags["with-shadcn-reinit"];
|
|
301
|
+
|
|
302
|
+
if (!skipInstall) {
|
|
303
|
+
console.log(kleur.bold(`\n→ Installing dependencies (npm install --legacy-peer-deps)\n`));
|
|
304
|
+
try {
|
|
305
|
+
await runShell("npm", ["install", "--legacy-peer-deps"], target);
|
|
306
|
+
} catch (err) {
|
|
307
|
+
console.log(kleur.yellow(` ⚠ npm install failed (${err.message}). You can rerun manually.`));
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
console.log(kleur.dim(`\n (skipping npm install — --no-install)`));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (reinitShadcn && !skipInstall) {
|
|
314
|
+
console.log(kleur.bold(`\n→ Re-running shadcn init (--with-shadcn-reinit)\n`));
|
|
315
|
+
try {
|
|
316
|
+
// Remove pre-baked components.json so shadcn writes a fresh one — post-init re-applies our aliases.
|
|
317
|
+
const cjPath = path.join(target, "components.json");
|
|
318
|
+
if (existsSync(cjPath)) writeFileSync(cjPath + ".bak", readFileSync(cjPath, "utf8"));
|
|
319
|
+
await runShell("npx", ["shadcn@latest", "init", "--yes", "--defaults"], target);
|
|
320
|
+
} catch (err) {
|
|
321
|
+
console.log(kleur.yellow(` ⚠ shadcn init failed (${err.message}). Continuing.`));
|
|
322
|
+
}
|
|
323
|
+
} else if (!skipInstall) {
|
|
324
|
+
console.log(kleur.dim(`\n (skipping shadcn init — starter already pre-configured. Pass --with-shadcn-reinit to force re-init.)`));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
process.stdout.write(`\n post-init restructure ... `);
|
|
328
|
+
const post = runPostInit(target, { template, features, skills });
|
|
329
|
+
console.log(kleur.green("ok"));
|
|
330
|
+
for (const c of post.changed) console.log(` ${kleur.green("+")} ${c}`);
|
|
331
|
+
for (const s of post.skipped) console.log(` ${kleur.dim("-")} ${kleur.dim(s)}`);
|
|
332
|
+
|
|
333
|
+
if (template) {
|
|
334
|
+
console.log(kleur.bold(`\n→ Pulling template ${kleur.cyan(template)}\n`));
|
|
335
|
+
const t = findEntry(template).entry;
|
|
336
|
+
for (const p of t.pullPaths ?? []) {
|
|
337
|
+
const dest = path.join(target, p);
|
|
338
|
+
process.stdout.write(` ${kleur.dim(p)} ... `);
|
|
339
|
+
await pull(p, dest);
|
|
340
|
+
console.log(kleur.green("ok"));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (skills.length) {
|
|
345
|
+
console.log(kleur.bold(`\n→ Pulling ${skills.length} Claude skill(s)\n`));
|
|
346
|
+
for (const s of skills) await installSkill(s, target);
|
|
209
347
|
}
|
|
210
348
|
|
|
211
349
|
console.log(`\n${kleur.green("✓")} Done. ${kleur.bold(slug)} scaffolded.\n`);
|
|
212
350
|
console.log(`${kleur.bold("Next:")}`);
|
|
213
351
|
console.log(` cd ${appName}`);
|
|
214
352
|
console.log(` cp .env.example .env.local ${kleur.dim("# fill NEXT_PUBLIC_CONVEX_URL")}`);
|
|
215
|
-
console.log(` npm install --legacy-peer-deps`);
|
|
353
|
+
if (skipInstall) console.log(` npm install --legacy-peer-deps`);
|
|
216
354
|
console.log(` npx convex dev --once ${kleur.dim("# generates convex/_generated")}`);
|
|
217
355
|
console.log(` npm run dev\n`);
|
|
218
|
-
console.log(
|
|
219
|
-
`${kleur.dim("Then drop in a layout:")} ${kleur.cyan("npx rahman-resources add personal-brand-os .")}\n`,
|
|
220
|
-
);
|
|
221
356
|
}
|
|
222
357
|
|
|
358
|
+
// Spawn a child process inheriting stdio, resolves on exit 0.
|
|
359
|
+
function runShell(cmd, args, cwd) {
|
|
360
|
+
return new Promise((resolve, reject) => {
|
|
361
|
+
const ps = spawn(cmd, args, { cwd, stdio: "inherit", shell: true });
|
|
362
|
+
ps.on("error", reject);
|
|
363
|
+
ps.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`${cmd} exited ${code}`))));
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ─── add (template / feature / recipe) ────────────────────────────────────
|
|
368
|
+
|
|
223
369
|
async function runAdd([slug, targetArg = "."]) {
|
|
224
370
|
if (!slug) {
|
|
225
371
|
console.error(kleur.red("Missing slug."));
|
|
@@ -227,11 +373,7 @@ async function runAdd([slug, targetArg = "."]) {
|
|
|
227
373
|
process.exit(1);
|
|
228
374
|
}
|
|
229
375
|
const found = findEntry(slug);
|
|
230
|
-
if (!found) {
|
|
231
|
-
throw new Error(
|
|
232
|
-
`"${slug}" not found. Run ${kleur.cyan("npx rahman-resources list")}.`,
|
|
233
|
-
);
|
|
234
|
-
}
|
|
376
|
+
if (!found) throw new Error(`"${slug}" not found. Run ${kleur.cyan("npx rahman-resources list")}.`);
|
|
235
377
|
const { kind, entry } = found;
|
|
236
378
|
const target = path.resolve(process.cwd(), targetArg);
|
|
237
379
|
|
|
@@ -263,6 +405,12 @@ async function addLayout(t, target, targetArg) {
|
|
|
263
405
|
}
|
|
264
406
|
}
|
|
265
407
|
|
|
408
|
+
if (rrExists(target)) {
|
|
409
|
+
const rr = readRr(target);
|
|
410
|
+
rr.template = { slug: t.slug, version: "main" };
|
|
411
|
+
writeRr(rr, target);
|
|
412
|
+
}
|
|
413
|
+
|
|
266
414
|
console.log(`\n${kleur.green("✓")} Done. ${kleur.bold(t.title)} installed.`);
|
|
267
415
|
if (t.agentRecipe) console.log(`\n${kleur.bold("Next:")}\n${indent(t.agentRecipe, 2)}\n`);
|
|
268
416
|
}
|
|
@@ -282,11 +430,15 @@ async function addFeature(t, target, targetArg) {
|
|
|
282
430
|
}
|
|
283
431
|
}
|
|
284
432
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
433
|
+
if (rrExists(target)) {
|
|
434
|
+
const rr = readRr(target);
|
|
435
|
+
rrAddFeature(rr, t.slug);
|
|
436
|
+
writeRr(rr, target);
|
|
437
|
+
console.log(kleur.dim(` rr.json: features += ${t.slug}`));
|
|
289
438
|
}
|
|
439
|
+
|
|
440
|
+
console.log(`\n${kleur.green("✓")} Feature added: ${kleur.bold(t.title)}`);
|
|
441
|
+
if (t.exampleCode) console.log(`\n${kleur.bold("Example:")}\n${indent(t.exampleCode, 2)}`);
|
|
290
442
|
if (t.agentRecipe) console.log(`\n${kleur.bold("Wire-up:")}\n${indent(t.agentRecipe, 2)}\n`);
|
|
291
443
|
if (t.docsUrl) console.log(`\n${kleur.dim(`Docs: ${t.docsUrl}`)}\n`);
|
|
292
444
|
}
|
|
@@ -297,20 +449,97 @@ function addRecipe(t) {
|
|
|
297
449
|
console.log(`\n${kleur.bold("Source:")} ${t.source}`);
|
|
298
450
|
console.log(`\n${kleur.bold("Files to port:")}`);
|
|
299
451
|
console.log(t.files.map((f) => ` · ${f}`).join("\n"));
|
|
300
|
-
if (t.exampleCode) {
|
|
301
|
-
console.log(`\n${kleur.bold("Example:")}`);
|
|
302
|
-
console.log(indent(t.exampleCode, 2));
|
|
303
|
-
}
|
|
452
|
+
if (t.exampleCode) console.log(`\n${kleur.bold("Example:")}\n${indent(t.exampleCode, 2)}`);
|
|
304
453
|
if (t.agentRecipe) console.log(`\n${kleur.bold("Wire-up:")}\n${indent(t.agentRecipe, 2)}\n`);
|
|
305
454
|
console.log(kleur.dim(`\n(Recipes are educational patterns — copy from source repo into your project manually.)\n`));
|
|
306
455
|
}
|
|
307
456
|
|
|
457
|
+
// ─── add-skill ────────────────────────────────────────────────────────────
|
|
458
|
+
|
|
459
|
+
async function runAddSkill([slug, targetArg = "."]) {
|
|
460
|
+
if (!slug) throw new Error("Usage: rahman-resources add-skill <slug>");
|
|
461
|
+
const skill = findSkill(slug);
|
|
462
|
+
if (!skill) throw new Error(`Unknown skill: ${slug}. Run 'list skills' to see available.`);
|
|
463
|
+
const target = path.resolve(process.cwd(), targetArg);
|
|
464
|
+
await installSkill(slug, target);
|
|
465
|
+
|
|
466
|
+
if (rrExists(target)) {
|
|
467
|
+
const rr = readRr(target);
|
|
468
|
+
rrAddSkill(rr, slug, skill.source);
|
|
469
|
+
writeRr(rr, target);
|
|
470
|
+
console.log(kleur.dim(` rr.json: skills += ${slug}`));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
console.log(`\n${kleur.green("✓")} Skill installed: ${kleur.bold(skill.title)}`);
|
|
474
|
+
console.log(kleur.dim(` Location: .claude/skills/${slug}/`));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async function installSkill(slug, target) {
|
|
478
|
+
const skill = findSkill(slug);
|
|
479
|
+
if (!skill) throw new Error(`Unknown skill: ${slug}`);
|
|
480
|
+
const dest = path.join(target, ".claude", "skills", slug);
|
|
481
|
+
process.stdout.write(` ${kleur.cyan(slug.padEnd(20))} ${kleur.dim(`→ .claude/skills/${slug}/`)} ... `);
|
|
482
|
+
if (skill.source === "anthropics") {
|
|
483
|
+
const emitter = tiged(`${SKILLS_REPO}/${skill.path}`, { cache: false, force: true, verbose: false });
|
|
484
|
+
await emitter.clone(dest);
|
|
485
|
+
} else if (skill.source === "rahman") {
|
|
486
|
+
// Future: ship rahman-authored skills inside this repo. For now, scaffold a stub.
|
|
487
|
+
mkdirSync(dest, { recursive: true });
|
|
488
|
+
writeFileSync(
|
|
489
|
+
path.join(dest, "SKILL.md"),
|
|
490
|
+
`---\nname: ${skill.slug}\ndescription: ${skill.description}\n---\n\n# ${skill.title}\n\nStub — to be filled by rahman-resources kitab.\n`,
|
|
491
|
+
);
|
|
492
|
+
} else {
|
|
493
|
+
throw new Error(`Unsupported skill source: ${skill.source}`);
|
|
494
|
+
}
|
|
495
|
+
console.log(kleur.green("ok"));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ─── doctor ───────────────────────────────────────────────────────────────
|
|
499
|
+
|
|
500
|
+
function runDoctor() {
|
|
501
|
+
const target = process.cwd();
|
|
502
|
+
if (!rrExists(target)) {
|
|
503
|
+
console.log(kleur.yellow("⚠ No rr.json found in cwd. Run 'rahman-resources init <app>' first."));
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
const rr = readRr(target);
|
|
507
|
+
const issues = validateRr(rr);
|
|
508
|
+
if (issues.length === 0) {
|
|
509
|
+
console.log(kleur.green("✓ rr.json is valid."));
|
|
510
|
+
console.log(` template: ${kleur.cyan(rr.template?.slug ?? "(none)")}`);
|
|
511
|
+
console.log(` features: ${kleur.cyan(rr.features?.length ?? 0)}`);
|
|
512
|
+
console.log(` skills: ${kleur.cyan(rr.skills?.length ?? 0)}`);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
console.log(kleur.red(`✖ rr.json has ${issues.length} issue(s):`));
|
|
516
|
+
for (const i of issues) console.log(` · ${i}`);
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function runMcpHint() {
|
|
521
|
+
console.log(`
|
|
522
|
+
${kleur.bold("Rahman Resources MCP server")}
|
|
523
|
+
|
|
524
|
+
Install + wire it into your Claude Code / Cursor config:
|
|
525
|
+
|
|
526
|
+
${kleur.cyan(`{
|
|
527
|
+
"mcpServers": {
|
|
528
|
+
"rahman-resources": {
|
|
529
|
+
"command": "npx",
|
|
530
|
+
"args": ["-y", "@rahman-resources/mcp"]
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}`)}
|
|
534
|
+
|
|
535
|
+
Then in Claude Code: ${kleur.cyan("/mcp")} to see available rr_* tools.
|
|
536
|
+
`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ─── helpers ──────────────────────────────────────────────────────────────
|
|
540
|
+
|
|
308
541
|
async function pull(repoPath, dest) {
|
|
309
|
-
const emitter = tiged(`${REPO}/${repoPath}#${BRANCH}`, {
|
|
310
|
-
cache: false,
|
|
311
|
-
force: true,
|
|
312
|
-
verbose: false,
|
|
313
|
-
});
|
|
542
|
+
const emitter = tiged(`${REPO}/${repoPath}#${BRANCH}`, { cache: false, force: true, verbose: false });
|
|
314
543
|
await emitter.clone(dest);
|
|
315
544
|
}
|
|
316
545
|
|
|
@@ -330,9 +559,7 @@ function runPM(pm, deps, cwd) {
|
|
|
330
559
|
return new Promise((resolve, reject) => {
|
|
331
560
|
const ps = spawn(pm, args, { cwd, stdio: "inherit", shell: true });
|
|
332
561
|
ps.on("error", reject);
|
|
333
|
-
ps.on("exit", (code) =>
|
|
334
|
-
code === 0 ? resolve() : reject(new Error(`${pm} ${args[0]} exited ${code}`)),
|
|
335
|
-
);
|
|
562
|
+
ps.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`${pm} ${args[0]} exited ${code}`))));
|
|
336
563
|
});
|
|
337
564
|
}
|
|
338
565
|
|
package/lib/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 2,
|
|
3
|
-
"generatedAt": "2026-05-
|
|
3
|
+
"generatedAt": "2026-05-06T09:30:28.726Z",
|
|
4
4
|
"repo": "rahmanef63/resource-site",
|
|
5
5
|
"branch": "main",
|
|
6
6
|
"layouts": [
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"repoPath": "app/preview/personal-brand-os",
|
|
14
14
|
"pullPaths": [
|
|
15
15
|
"app/preview/personal-brand-os",
|
|
16
|
+
"components/templates/_shared",
|
|
16
17
|
"components/templates/personal-brand",
|
|
17
18
|
"convex/templates/personal-brand-os"
|
|
18
19
|
],
|
|
@@ -48,14 +49,24 @@
|
|
|
48
49
|
"app/preview/personal-brand-os/admin/settings/site/page.tsx",
|
|
49
50
|
"app/preview/personal-brand-os/admin/settings/team/page.tsx",
|
|
50
51
|
"app/preview/personal-brand-os/admin/settings/ai/page.tsx",
|
|
52
|
+
"components/templates/_shared/index.ts",
|
|
53
|
+
"components/templates/_shared/types/common.ts",
|
|
54
|
+
"components/templates/_shared/utils/index.ts",
|
|
55
|
+
"components/templates/_shared/hooks/create-template-store.tsx",
|
|
56
|
+
"components/templates/_shared/ui/section-head.tsx",
|
|
57
|
+
"components/templates/_shared/ui/site-nav.tsx",
|
|
58
|
+
"components/templates/_shared/ui/site-footer.tsx",
|
|
59
|
+
"components/templates/_shared/ui/site-shell.tsx",
|
|
60
|
+
"components/templates/_shared/ui/admin-sidebar.tsx",
|
|
61
|
+
"components/templates/_shared/ui/admin-topbar.tsx",
|
|
62
|
+
"components/templates/_shared/ui/admin-shell.tsx",
|
|
63
|
+
"components/templates/_shared/ui/stat-card.tsx",
|
|
51
64
|
"components/templates/personal-brand/shared/types.ts",
|
|
52
65
|
"components/templates/personal-brand/shared/store.tsx",
|
|
53
66
|
"components/templates/personal-brand/shared/seed.ts",
|
|
54
67
|
"components/templates/personal-brand/shared/site-config.ts",
|
|
55
|
-
"components/templates/personal-brand/shared/
|
|
56
|
-
"components/templates/personal-brand/shared/ui/site-footer.tsx",
|
|
68
|
+
"components/templates/personal-brand/shared/nav-config.ts",
|
|
57
69
|
"components/templates/personal-brand/shared/ui/chat-fab.tsx",
|
|
58
|
-
"components/templates/personal-brand/shared/ui/section-head.tsx",
|
|
59
70
|
"components/templates/personal-brand/slices/home/HomePage.tsx",
|
|
60
71
|
"components/templates/personal-brand/slices/home/NewsletterBlock.tsx",
|
|
61
72
|
"components/templates/personal-brand/slices/blog/BlogList.tsx",
|
|
@@ -66,8 +77,7 @@
|
|
|
66
77
|
"components/templates/personal-brand/slices/resources/ResourcesPage.tsx",
|
|
67
78
|
"components/templates/personal-brand/slices/about/AboutPage.tsx",
|
|
68
79
|
"components/templates/personal-brand/slices/contact/ContactPage.tsx",
|
|
69
|
-
"
|
|
70
|
-
"components/templates/personal-brand/slices/admin/shell/admin-topbar.tsx",
|
|
80
|
+
"app/preview/personal-brand-os/admin/admin-shell-client.tsx",
|
|
71
81
|
"components/templates/personal-brand/slices/admin/dashboard/DashboardView.tsx",
|
|
72
82
|
"components/templates/personal-brand/slices/admin/posts/PostsList.tsx",
|
|
73
83
|
"components/templates/personal-brand/slices/admin/posts/PostEditor.tsx",
|
|
@@ -124,6 +134,94 @@
|
|
|
124
134
|
],
|
|
125
135
|
"primaryFile": "app/preview/personal-brand-os/public/page.tsx"
|
|
126
136
|
},
|
|
137
|
+
{
|
|
138
|
+
"slug": "agency-studio-os",
|
|
139
|
+
"title": "Agency Studio OS",
|
|
140
|
+
"category": "website-template",
|
|
141
|
+
"description": "Full-app B2B agency / studio site — public (home, services, work + project detail, about, contact) + admin (dashboard, projects pipeline, clients, services, leads, settings). Inspired by saudivisuals.com + cescadesigns.",
|
|
142
|
+
"source": "saudivisuals.com + cescadesigns",
|
|
143
|
+
"repoPath": "app/preview/agency-studio-os",
|
|
144
|
+
"pullPaths": [
|
|
145
|
+
"app/preview/agency-studio-os",
|
|
146
|
+
"components/templates/_shared",
|
|
147
|
+
"components/templates/agency-studio",
|
|
148
|
+
"convex/templates/agency-studio-os"
|
|
149
|
+
],
|
|
150
|
+
"files": [
|
|
151
|
+
"app/preview/agency-studio-os/robots.ts",
|
|
152
|
+
"app/preview/agency-studio-os/sitemap.ts",
|
|
153
|
+
"app/preview/agency-studio-os/opengraph-image.tsx",
|
|
154
|
+
"app/preview/agency-studio-os/public/layout.tsx",
|
|
155
|
+
"app/preview/agency-studio-os/public/page.tsx",
|
|
156
|
+
"app/preview/agency-studio-os/public/services/page.tsx",
|
|
157
|
+
"app/preview/agency-studio-os/public/portfolio/page.tsx",
|
|
158
|
+
"app/preview/agency-studio-os/public/portfolio/[slug]/page.tsx",
|
|
159
|
+
"app/preview/agency-studio-os/public/about/page.tsx",
|
|
160
|
+
"app/preview/agency-studio-os/public/contact/page.tsx",
|
|
161
|
+
"app/preview/agency-studio-os/admin/layout.tsx",
|
|
162
|
+
"app/preview/agency-studio-os/admin/page.tsx",
|
|
163
|
+
"app/preview/agency-studio-os/admin/projects/page.tsx",
|
|
164
|
+
"app/preview/agency-studio-os/admin/projects/new/page.tsx",
|
|
165
|
+
"app/preview/agency-studio-os/admin/projects/[id]/page.tsx",
|
|
166
|
+
"app/preview/agency-studio-os/admin/clients/page.tsx",
|
|
167
|
+
"app/preview/agency-studio-os/admin/services/page.tsx",
|
|
168
|
+
"app/preview/agency-studio-os/admin/leads/page.tsx",
|
|
169
|
+
"app/preview/agency-studio-os/admin/settings/page.tsx",
|
|
170
|
+
"components/templates/agency-studio/shared/types.ts",
|
|
171
|
+
"components/templates/agency-studio/shared/store.tsx",
|
|
172
|
+
"components/templates/agency-studio/shared/seed.ts",
|
|
173
|
+
"components/templates/agency-studio/shared/site-config.ts",
|
|
174
|
+
"components/templates/agency-studio/shared/nav-config.ts",
|
|
175
|
+
"components/templates/agency-studio/slices/home/HomePage.tsx",
|
|
176
|
+
"components/templates/agency-studio/slices/services/ServicesPage.tsx",
|
|
177
|
+
"components/templates/agency-studio/slices/portfolio/PortfolioListPage.tsx",
|
|
178
|
+
"components/templates/agency-studio/slices/portfolio/PortfolioDetailPage.tsx",
|
|
179
|
+
"components/templates/agency-studio/slices/about/AboutPage.tsx",
|
|
180
|
+
"components/templates/agency-studio/slices/contact/ContactPage.tsx",
|
|
181
|
+
"app/preview/agency-studio-os/admin/admin-shell-client.tsx",
|
|
182
|
+
"components/templates/agency-studio/slices/admin/dashboard/DashboardView.tsx",
|
|
183
|
+
"components/templates/agency-studio/slices/admin/projects/ProjectsList.tsx",
|
|
184
|
+
"components/templates/agency-studio/slices/admin/projects/ProjectEditor.tsx",
|
|
185
|
+
"components/templates/agency-studio/slices/admin/clients/ClientsList.tsx",
|
|
186
|
+
"components/templates/agency-studio/slices/admin/services/ServicesAdminView.tsx",
|
|
187
|
+
"components/templates/agency-studio/slices/admin/leads/LeadsView.tsx",
|
|
188
|
+
"components/templates/agency-studio/slices/admin/settings/SettingsView.tsx",
|
|
189
|
+
"convex/templates/agency-studio-os/schema.ts",
|
|
190
|
+
"convex/templates/agency-studio-os/projects.ts",
|
|
191
|
+
"convex/templates/agency-studio-os/clients.ts",
|
|
192
|
+
"convex/templates/agency-studio-os/services.ts",
|
|
193
|
+
"convex/templates/agency-studio-os/leads.ts",
|
|
194
|
+
"convex/templates/agency-studio-os/README.md"
|
|
195
|
+
],
|
|
196
|
+
"dependencies": [
|
|
197
|
+
"next@^16",
|
|
198
|
+
"react@^19",
|
|
199
|
+
"react-dom@^19",
|
|
200
|
+
"lucide-react",
|
|
201
|
+
"@tabler/icons-react",
|
|
202
|
+
"sonner",
|
|
203
|
+
"next-themes",
|
|
204
|
+
"tailwindcss@^4",
|
|
205
|
+
"convex",
|
|
206
|
+
"@convex-dev/auth",
|
|
207
|
+
"@radix-ui/react-avatar",
|
|
208
|
+
"@radix-ui/react-dialog",
|
|
209
|
+
"@radix-ui/react-label",
|
|
210
|
+
"@radix-ui/react-separator",
|
|
211
|
+
"@radix-ui/react-slot"
|
|
212
|
+
],
|
|
213
|
+
"agentRecipe": "Agency Studio OS = full-app B2B agency template (public + admin). 1) Move app/preview/agency-studio-os/{robots,sitemap,opengraph-image}.* to app root. 2) Copy public into app/(public)/, admin into app/(admin)/. 3) Edit components/templates/agency-studio/shared/site-config.ts — set studioName, brandName, baseUrl, twitter, email. 4) Wire convex/templates/agency-studio-os/* to convex/_generated and add @convex-dev/auth on admin routes. 5) Replace localStorage StoreProvider with Convex queries.",
|
|
214
|
+
"tags": [
|
|
215
|
+
"template",
|
|
216
|
+
"agency",
|
|
217
|
+
"studio",
|
|
218
|
+
"portfolio",
|
|
219
|
+
"b2b",
|
|
220
|
+
"admin",
|
|
221
|
+
"saas"
|
|
222
|
+
],
|
|
223
|
+
"primaryFile": "app/preview/agency-studio-os/public/page.tsx"
|
|
224
|
+
},
|
|
127
225
|
{
|
|
128
226
|
"slug": "landing-hero-carousel",
|
|
129
227
|
"title": "Landing — Hero Carousel",
|
|
@@ -286,6 +384,147 @@
|
|
|
286
384
|
"storefront"
|
|
287
385
|
],
|
|
288
386
|
"primaryFile": "README.md"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"slug": "saas-marketing-os",
|
|
390
|
+
"title": "SaaS Marketing OS",
|
|
391
|
+
"category": "website-template",
|
|
392
|
+
"status": "stable",
|
|
393
|
+
"description": "Public-only marketing site for a SaaS product — landing, pricing, features, blog, changelog, about, contact. MDX-driven blog + changelog. No admin (CMS via MDX files in repo).",
|
|
394
|
+
"source": "synthesized + mdx-blog feature",
|
|
395
|
+
"repoPath": "app/preview/saas-marketing-os",
|
|
396
|
+
"pullPaths": [
|
|
397
|
+
"app/preview/saas-marketing-os",
|
|
398
|
+
"components/templates/_shared",
|
|
399
|
+
"components/templates/saas-marketing"
|
|
400
|
+
],
|
|
401
|
+
"files": [
|
|
402
|
+
"app/preview/saas-marketing-os/public/layout.tsx",
|
|
403
|
+
"app/preview/saas-marketing-os/public/page.tsx",
|
|
404
|
+
"app/preview/saas-marketing-os/public/pricing/page.tsx",
|
|
405
|
+
"app/preview/saas-marketing-os/public/features/page.tsx",
|
|
406
|
+
"app/preview/saas-marketing-os/public/blog/page.tsx",
|
|
407
|
+
"app/preview/saas-marketing-os/public/blog/[slug]/page.tsx",
|
|
408
|
+
"app/preview/saas-marketing-os/public/changelog/page.tsx",
|
|
409
|
+
"app/preview/saas-marketing-os/public/about/page.tsx",
|
|
410
|
+
"app/preview/saas-marketing-os/public/contact/page.tsx",
|
|
411
|
+
"components/templates/saas-marketing/shared/site-config.ts",
|
|
412
|
+
"components/templates/saas-marketing/shared/nav-config.ts",
|
|
413
|
+
"components/templates/saas-marketing/shared/types.ts",
|
|
414
|
+
"components/templates/saas-marketing/shared/store.tsx",
|
|
415
|
+
"components/templates/saas-marketing/shared/seed.ts",
|
|
416
|
+
"components/templates/saas-marketing/slices/home/HomePage.tsx",
|
|
417
|
+
"components/templates/saas-marketing/slices/pricing/PricingPage.tsx",
|
|
418
|
+
"components/templates/saas-marketing/slices/features/FeaturesPage.tsx",
|
|
419
|
+
"components/templates/saas-marketing/slices/blog/BlogList.tsx",
|
|
420
|
+
"components/templates/saas-marketing/slices/blog/BlogDetail.tsx",
|
|
421
|
+
"components/templates/saas-marketing/slices/changelog/ChangelogPage.tsx",
|
|
422
|
+
"components/templates/saas-marketing/slices/about/AboutPage.tsx",
|
|
423
|
+
"components/templates/saas-marketing/slices/contact/ContactPage.tsx"
|
|
424
|
+
],
|
|
425
|
+
"dependencies": [
|
|
426
|
+
"next@^16",
|
|
427
|
+
"react@^19",
|
|
428
|
+
"react-dom@^19",
|
|
429
|
+
"lucide-react",
|
|
430
|
+
"sonner",
|
|
431
|
+
"next-themes",
|
|
432
|
+
"tailwindcss@^4",
|
|
433
|
+
"@radix-ui/react-slot"
|
|
434
|
+
],
|
|
435
|
+
"agentRecipe": "SaaS Marketing OS = public-only marketing template. Blog + changelog use MDX (add the mdx-blog feature). Edit components/templates/saas-marketing/shared/site-config.ts to set product name, tagline, pricing tiers, contact email.",
|
|
436
|
+
"tags": [
|
|
437
|
+
"template",
|
|
438
|
+
"saas",
|
|
439
|
+
"marketing",
|
|
440
|
+
"mdx",
|
|
441
|
+
"blog",
|
|
442
|
+
"changelog"
|
|
443
|
+
],
|
|
444
|
+
"primaryFile": "app/preview/saas-marketing-os/public/page.tsx"
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
"slug": "kreator-studio-os",
|
|
448
|
+
"title": "Kreator Studio OS",
|
|
449
|
+
"category": "website-template",
|
|
450
|
+
"status": "coming-soon",
|
|
451
|
+
"description": "Creator/influencer studio — newsletter-first public site + admin for posts, drops, audience segments. Resend + Midtrans tip jar. Coming soon.",
|
|
452
|
+
"source": "synthesized",
|
|
453
|
+
"repoPath": "app/preview/kreator-studio-os",
|
|
454
|
+
"pullPaths": [],
|
|
455
|
+
"files": [],
|
|
456
|
+
"dependencies": [],
|
|
457
|
+
"agentRecipe": "Coming soon — track at /build for updates.",
|
|
458
|
+
"tags": [
|
|
459
|
+
"template",
|
|
460
|
+
"creator",
|
|
461
|
+
"newsletter",
|
|
462
|
+
"coming-soon"
|
|
463
|
+
],
|
|
464
|
+
"primaryFile": "README.md"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"slug": "konsultan-os",
|
|
468
|
+
"title": "Konsultan OS",
|
|
469
|
+
"category": "website-template",
|
|
470
|
+
"status": "coming-soon",
|
|
471
|
+
"description": "Consultancy site — services catalog, case studies, booking via Cal.com, Midtrans deposits. Public + lightweight admin. Coming soon.",
|
|
472
|
+
"source": "synthesized",
|
|
473
|
+
"repoPath": "app/preview/konsultan-os",
|
|
474
|
+
"pullPaths": [],
|
|
475
|
+
"files": [],
|
|
476
|
+
"dependencies": [],
|
|
477
|
+
"agentRecipe": "Coming soon — track at /build for updates.",
|
|
478
|
+
"tags": [
|
|
479
|
+
"template",
|
|
480
|
+
"consultant",
|
|
481
|
+
"booking",
|
|
482
|
+
"coming-soon"
|
|
483
|
+
],
|
|
484
|
+
"primaryFile": "README.md"
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"slug": "wirausaha-os",
|
|
488
|
+
"title": "Wirausaha OS",
|
|
489
|
+
"category": "website-template",
|
|
490
|
+
"status": "coming-soon",
|
|
491
|
+
"description": "Indonesian SMB toolkit — public storefront + admin (catalog, orders, Midtrans + QRIS, leads). Bahasa Indonesia first. Coming soon.",
|
|
492
|
+
"source": "synthesized",
|
|
493
|
+
"repoPath": "app/preview/wirausaha-os",
|
|
494
|
+
"pullPaths": [],
|
|
495
|
+
"files": [],
|
|
496
|
+
"dependencies": [],
|
|
497
|
+
"agentRecipe": "Coming soon — track at /build for updates.",
|
|
498
|
+
"tags": [
|
|
499
|
+
"template",
|
|
500
|
+
"smb",
|
|
501
|
+
"ecommerce",
|
|
502
|
+
"midtrans",
|
|
503
|
+
"indonesia",
|
|
504
|
+
"coming-soon"
|
|
505
|
+
],
|
|
506
|
+
"primaryFile": "README.md"
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
"slug": "riset-kit",
|
|
510
|
+
"title": "Riset Kit",
|
|
511
|
+
"category": "website-template",
|
|
512
|
+
"status": "coming-soon",
|
|
513
|
+
"description": "Research / knowledge-base template — Convex vector search + MDX + threaded comments. Public reading + admin authoring. Coming soon.",
|
|
514
|
+
"source": "synthesized",
|
|
515
|
+
"repoPath": "app/preview/riset-kit",
|
|
516
|
+
"pullPaths": [],
|
|
517
|
+
"files": [],
|
|
518
|
+
"dependencies": [],
|
|
519
|
+
"agentRecipe": "Coming soon — track at /build for updates.",
|
|
520
|
+
"tags": [
|
|
521
|
+
"template",
|
|
522
|
+
"research",
|
|
523
|
+
"knowledge-base",
|
|
524
|
+
"vector-search",
|
|
525
|
+
"coming-soon"
|
|
526
|
+
],
|
|
527
|
+
"primaryFile": "README.md"
|
|
289
528
|
}
|
|
290
529
|
],
|
|
291
530
|
"recipes": [
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Post-init restructure — runs after `rahman-resources init` finishes copying
|
|
2
|
+
// the bundled starter into the target dir. Steps:
|
|
3
|
+
//
|
|
4
|
+
// 1. Patch components.json with vertical-slice aliases (shared, templates,
|
|
5
|
+
// slices, features) so future shadcn add commands respect our layout.
|
|
6
|
+
// 2. Write rr.json (project manifest).
|
|
7
|
+
// 3. Patch tsconfig.json paths to mirror the rr.json aliases.
|
|
8
|
+
//
|
|
9
|
+
// Idempotent — safe to re-run. Each step compares before mutating.
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
|
|
14
|
+
import { DEFAULT_RR, writeRr, buildRr } from "./rr.mjs";
|
|
15
|
+
|
|
16
|
+
const RR_ALIASES_TO_TSCONFIG = {
|
|
17
|
+
// alias -> tsconfig key target relative-path
|
|
18
|
+
"@/components/templates/_shared": "components/templates/_shared",
|
|
19
|
+
"@/components/templates": "components/templates",
|
|
20
|
+
"@/features": "features",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function runPostInit(targetDir, opts = {}) {
|
|
24
|
+
const out = { changed: [], skipped: [] };
|
|
25
|
+
|
|
26
|
+
patchComponentsJson(targetDir, out);
|
|
27
|
+
writeRrJson(targetDir, opts, out);
|
|
28
|
+
patchTsconfig(targetDir, out);
|
|
29
|
+
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function patchComponentsJson(targetDir, out) {
|
|
34
|
+
const p = path.join(targetDir, "components.json");
|
|
35
|
+
if (!existsSync(p)) {
|
|
36
|
+
out.skipped.push("components.json (not found — run shadcn init first)");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const cj = JSON.parse(readFileSync(p, "utf8"));
|
|
40
|
+
cj.aliases = cj.aliases ?? {};
|
|
41
|
+
const before = JSON.stringify(cj.aliases);
|
|
42
|
+
cj.aliases.shared = cj.aliases.shared ?? "@/components/templates/_shared";
|
|
43
|
+
cj.aliases.templates = cj.aliases.templates ?? "@/components/templates";
|
|
44
|
+
cj.aliases.slices = cj.aliases.slices ?? "@/components/templates/{template}/slices";
|
|
45
|
+
cj.aliases.features = cj.aliases.features ?? "@/features";
|
|
46
|
+
if (JSON.stringify(cj.aliases) !== before) {
|
|
47
|
+
writeFileSync(p, JSON.stringify(cj, null, 2) + "\n");
|
|
48
|
+
out.changed.push("components.json (aliases)");
|
|
49
|
+
} else {
|
|
50
|
+
out.skipped.push("components.json (already patched)");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function writeRrJson(targetDir, opts, out) {
|
|
55
|
+
const p = path.join(targetDir, "rr.json");
|
|
56
|
+
if (existsSync(p)) {
|
|
57
|
+
out.skipped.push("rr.json (already exists)");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const rr = buildRr({
|
|
61
|
+
template: opts.template,
|
|
62
|
+
features: opts.features,
|
|
63
|
+
skills: opts.skills,
|
|
64
|
+
templateVersion: opts.templateVersion,
|
|
65
|
+
});
|
|
66
|
+
writeRr(rr, targetDir);
|
|
67
|
+
out.changed.push("rr.json (created)");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function patchTsconfig(targetDir, out) {
|
|
71
|
+
const p = path.join(targetDir, "tsconfig.json");
|
|
72
|
+
if (!existsSync(p)) {
|
|
73
|
+
out.skipped.push("tsconfig.json (not found)");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let raw = readFileSync(p, "utf8");
|
|
77
|
+
let cfg;
|
|
78
|
+
try { cfg = JSON.parse(raw); } catch {
|
|
79
|
+
out.skipped.push("tsconfig.json (parse failed — likely has comments; skipped)");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
cfg.compilerOptions = cfg.compilerOptions ?? {};
|
|
83
|
+
cfg.compilerOptions.paths = cfg.compilerOptions.paths ?? {};
|
|
84
|
+
const paths = cfg.compilerOptions.paths;
|
|
85
|
+
const before = JSON.stringify(paths);
|
|
86
|
+
paths["@/*"] = paths["@/*"] ?? ["./*"];
|
|
87
|
+
// Keep tsconfig minimal — @/* covers all aliases. Don't bloat with redundant entries.
|
|
88
|
+
if (JSON.stringify(paths) !== before) {
|
|
89
|
+
writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n");
|
|
90
|
+
out.changed.push("tsconfig.json (paths)");
|
|
91
|
+
} else {
|
|
92
|
+
out.skipped.push("tsconfig.json (already configured)");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Allow running as a CLI script for re-restructure on existing projects.
|
|
97
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
98
|
+
const target = process.argv[2] ?? process.cwd();
|
|
99
|
+
const result = runPostInit(target);
|
|
100
|
+
for (const c of result.changed) console.log(" +", c);
|
|
101
|
+
for (const s of result.skipped) console.log(" -", s);
|
|
102
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://rahmanef63.github.io/resource-site/rr.schema.json",
|
|
4
|
+
"title": "rr.json — Rahman Resources project manifest",
|
|
5
|
+
"description": "Project-local manifest written by `rahman-resources init` and patched by `add` / `add-skill` / `upgrade`. Mirrors what shadcn's components.json does for shadcn, but for the vertical-slice kitab flow (templates + features + Claude skills).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["version", "framework", "aliases", "layout"],
|
|
8
|
+
"additionalProperties": true,
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": { "type": "string" },
|
|
11
|
+
"version": { "type": "integer", "minimum": 1 },
|
|
12
|
+
"framework": { "type": "string", "enum": ["next-16", "next-15", "vite", "remix"] },
|
|
13
|
+
"style": { "type": "string", "default": "shadcn-new-york" },
|
|
14
|
+
"rsc": { "type": "boolean", "default": true },
|
|
15
|
+
"tailwind": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"config": { "type": "string" },
|
|
19
|
+
"css": { "type": "string" }
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"aliases": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"required": ["components", "ui", "shared", "templates", "lib"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"components": { "type": "string" },
|
|
27
|
+
"ui": { "type": "string" },
|
|
28
|
+
"shared": { "type": "string", "description": "Cross-template _shared scaffolding (site-shell, admin-shell, store factory)." },
|
|
29
|
+
"templates": { "type": "string", "description": "Per-template folders root (e.g. components/templates)." },
|
|
30
|
+
"slices": { "type": "string", "description": "Optional slice-root pattern, may use {template} placeholder." },
|
|
31
|
+
"features": { "type": "string" },
|
|
32
|
+
"convex": { "type": "string" },
|
|
33
|
+
"lib": { "type": "string" },
|
|
34
|
+
"hooks": { "type": "string" }
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"layout": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["kind"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"kind": { "type": "string", "enum": ["vertical-slice", "feature-folders"] },
|
|
42
|
+
"publicRoute": { "type": "string" },
|
|
43
|
+
"adminRoute": { "type": "string" },
|
|
44
|
+
"sliceRoot": { "type": "string" }
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"template": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"required": ["slug", "version"],
|
|
50
|
+
"properties": {
|
|
51
|
+
"slug": { "type": "string" },
|
|
52
|
+
"version": { "type": "string" }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"features": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"required": ["slug"],
|
|
60
|
+
"properties": {
|
|
61
|
+
"slug": { "type": "string" },
|
|
62
|
+
"version": { "type": "string" },
|
|
63
|
+
"addedAt": { "type": "string", "format": "date" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"skills": {
|
|
68
|
+
"type": "array",
|
|
69
|
+
"items": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"required": ["slug", "source"],
|
|
72
|
+
"properties": {
|
|
73
|
+
"slug": { "type": "string" },
|
|
74
|
+
"source": { "type": "string", "enum": ["anthropics", "rahman", "custom"] },
|
|
75
|
+
"version": { "type": "string", "default": "main" },
|
|
76
|
+
"addedAt": { "type": "string", "format": "date" }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"auth": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"properties": {
|
|
83
|
+
"provider": { "type": "string", "enum": ["convex-auth", "clerk", "next-auth", "none"] }
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"convex": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"properties": {
|
|
89
|
+
"self_hosted": { "type": "boolean" },
|
|
90
|
+
"deploymentUrl": { "type": "string" }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
package/lib/rr.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Read / write / validate rr.json — the project-local Rahman Resources manifest.
|
|
2
|
+
//
|
|
3
|
+
// Schema lives at lib/rr-schema.json (JSON Schema draft-07). Validation is
|
|
4
|
+
// hand-rolled (shape-only) to avoid pulling in ajv as a runtime dep — the CLI
|
|
5
|
+
// stays small. Full schema validation is available via `npx ajv` if desired.
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
const RR_FILENAME = "rr.json";
|
|
11
|
+
const SCHEMA_URL = "https://rahmanef63.github.io/resource-site/rr.schema.json";
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_RR = {
|
|
14
|
+
$schema: SCHEMA_URL,
|
|
15
|
+
version: 1,
|
|
16
|
+
framework: "next-16",
|
|
17
|
+
style: "shadcn-new-york",
|
|
18
|
+
rsc: true,
|
|
19
|
+
tailwind: { config: "tailwind.config.ts", css: "app/globals.css" },
|
|
20
|
+
aliases: {
|
|
21
|
+
components: "@/components",
|
|
22
|
+
ui: "@/components/ui",
|
|
23
|
+
shared: "@/components/templates/_shared",
|
|
24
|
+
templates: "@/components/templates",
|
|
25
|
+
slices: "@/components/templates/{template}/slices",
|
|
26
|
+
features: "@/features",
|
|
27
|
+
convex: "@/convex",
|
|
28
|
+
lib: "@/lib",
|
|
29
|
+
hooks: "@/hooks",
|
|
30
|
+
},
|
|
31
|
+
layout: {
|
|
32
|
+
kind: "vertical-slice",
|
|
33
|
+
publicRoute: "app/(public)",
|
|
34
|
+
adminRoute: "app/(admin)",
|
|
35
|
+
sliceRoot: "components/templates",
|
|
36
|
+
},
|
|
37
|
+
template: null,
|
|
38
|
+
features: [],
|
|
39
|
+
skills: [],
|
|
40
|
+
auth: { provider: "convex-auth" },
|
|
41
|
+
convex: { self_hosted: true },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function rrPath(targetDir = process.cwd()) {
|
|
45
|
+
return path.join(targetDir, RR_FILENAME);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function rrExists(targetDir = process.cwd()) {
|
|
49
|
+
return existsSync(rrPath(targetDir));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function readRr(targetDir = process.cwd()) {
|
|
53
|
+
const p = rrPath(targetDir);
|
|
54
|
+
if (!existsSync(p)) throw new Error(`No rr.json at ${p}. Run 'rahman-resources init' first.`);
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
57
|
+
} catch (err) {
|
|
58
|
+
throw new Error(`Failed to parse ${p}: ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function writeRr(rr, targetDir = process.cwd()) {
|
|
63
|
+
const p = rrPath(targetDir);
|
|
64
|
+
writeFileSync(p, JSON.stringify(rr, null, 2) + "\n");
|
|
65
|
+
return p;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function buildRr(opts = {}) {
|
|
69
|
+
const rr = JSON.parse(JSON.stringify(DEFAULT_RR));
|
|
70
|
+
if (opts.template) {
|
|
71
|
+
rr.template = { slug: opts.template, version: opts.templateVersion ?? "main" };
|
|
72
|
+
}
|
|
73
|
+
if (opts.features?.length) {
|
|
74
|
+
const stamp = today();
|
|
75
|
+
rr.features = opts.features.map((slug) => ({ slug, version: opts.templateVersion ?? "main", addedAt: stamp }));
|
|
76
|
+
}
|
|
77
|
+
if (opts.skills?.length) {
|
|
78
|
+
const stamp = today();
|
|
79
|
+
rr.skills = opts.skills.map((s) => {
|
|
80
|
+
if (typeof s === "string") return { slug: s, source: "anthropics", version: "main", addedAt: stamp };
|
|
81
|
+
return { source: "anthropics", version: "main", addedAt: stamp, ...s };
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return rr;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function addFeature(rr, slug, version = "main") {
|
|
88
|
+
rr.features = rr.features ?? [];
|
|
89
|
+
if (rr.features.find((f) => f.slug === slug)) return rr;
|
|
90
|
+
rr.features.push({ slug, version, addedAt: today() });
|
|
91
|
+
return rr;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function addSkill(rr, slug, source = "anthropics", version = "main") {
|
|
95
|
+
rr.skills = rr.skills ?? [];
|
|
96
|
+
if (rr.skills.find((s) => s.slug === slug && s.source === source)) return rr;
|
|
97
|
+
rr.skills.push({ slug, source, version, addedAt: today() });
|
|
98
|
+
return rr;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function removeFeature(rr, slug) {
|
|
102
|
+
rr.features = (rr.features ?? []).filter((f) => f.slug !== slug);
|
|
103
|
+
return rr;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function removeSkill(rr, slug) {
|
|
107
|
+
rr.skills = (rr.skills ?? []).filter((s) => s.slug !== slug);
|
|
108
|
+
return rr;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Shape-only validation — returns string[] of issues (empty = ok).
|
|
112
|
+
export function validateRr(rr) {
|
|
113
|
+
const issues = [];
|
|
114
|
+
if (!rr || typeof rr !== "object") return ["rr.json is not an object"];
|
|
115
|
+
if (typeof rr.version !== "number") issues.push("version must be a number");
|
|
116
|
+
if (!rr.framework) issues.push("framework is required");
|
|
117
|
+
if (!rr.aliases || typeof rr.aliases !== "object") issues.push("aliases is required");
|
|
118
|
+
else {
|
|
119
|
+
for (const k of ["components", "ui", "shared", "templates", "lib"]) {
|
|
120
|
+
if (typeof rr.aliases[k] !== "string") issues.push(`aliases.${k} must be a string`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (!rr.layout || typeof rr.layout !== "object") issues.push("layout is required");
|
|
124
|
+
else if (!["vertical-slice", "feature-folders"].includes(rr.layout.kind)) {
|
|
125
|
+
issues.push(`layout.kind must be vertical-slice|feature-folders (got ${rr.layout.kind})`);
|
|
126
|
+
}
|
|
127
|
+
if (rr.features && !Array.isArray(rr.features)) issues.push("features must be an array");
|
|
128
|
+
if (rr.skills && !Array.isArray(rr.skills)) issues.push("skills must be an array");
|
|
129
|
+
return issues;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function today() {
|
|
133
|
+
return new Date().toISOString().slice(0, 10);
|
|
134
|
+
}
|
package/lib/skills.json
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"description": "Claude Skills inventory — 18 entries (sync'd from site/lib/content/claude-skills.ts). Used by CLI add-skill + builder UI + MCP server.",
|
|
4
|
+
"anthropicsRepo": "anthropics/skills",
|
|
5
|
+
"branch": "main",
|
|
6
|
+
"lastSynced": "2026-05-05"
|
|
7
|
+
},
|
|
8
|
+
"skills": [
|
|
9
|
+
{
|
|
10
|
+
"slug": "algorithmic-art",
|
|
11
|
+
"title": "Algorithmic Art",
|
|
12
|
+
"category": "creative",
|
|
13
|
+
"source": "anthropics",
|
|
14
|
+
"path": "skills/algorithmic-art",
|
|
15
|
+
"description": "Generate generative-art pieces (p5.js, processing-style algorithms)."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"slug": "brand-guidelines",
|
|
19
|
+
"title": "Brand Guidelines",
|
|
20
|
+
"category": "design",
|
|
21
|
+
"source": "anthropics",
|
|
22
|
+
"path": "skills/brand-guidelines",
|
|
23
|
+
"description": "Apply a brand's visual language consistently across artifacts."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"slug": "canvas-design",
|
|
27
|
+
"title": "Canvas Design",
|
|
28
|
+
"category": "design",
|
|
29
|
+
"source": "anthropics",
|
|
30
|
+
"path": "skills/canvas-design",
|
|
31
|
+
"description": "Design canvas layouts (Canva-style) — slides, posters, social posts."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"slug": "claude-api",
|
|
35
|
+
"title": "Claude API",
|
|
36
|
+
"category": "development",
|
|
37
|
+
"source": "anthropics",
|
|
38
|
+
"path": "skills/claude-api",
|
|
39
|
+
"description": "Call the Claude API correctly — auth, models, tools, streaming patterns."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"slug": "doc-coauthoring",
|
|
43
|
+
"title": "Doc Co-authoring",
|
|
44
|
+
"category": "enterprise",
|
|
45
|
+
"source": "anthropics",
|
|
46
|
+
"path": "skills/doc-coauthoring",
|
|
47
|
+
"description": "Collaborate on long-form documents with section-aware editing."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"slug": "docx",
|
|
51
|
+
"title": "DOCX",
|
|
52
|
+
"category": "documents",
|
|
53
|
+
"source": "anthropics",
|
|
54
|
+
"path": "skills/docx",
|
|
55
|
+
"description": "Read, write, and edit Microsoft Word .docx files."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"slug": "frontend-design",
|
|
59
|
+
"title": "Frontend Design",
|
|
60
|
+
"category": "development",
|
|
61
|
+
"source": "anthropics",
|
|
62
|
+
"path": "skills/frontend-design",
|
|
63
|
+
"description": "Design and build polished frontend UIs with modern patterns."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"slug": "internal-comms",
|
|
67
|
+
"title": "Internal Comms",
|
|
68
|
+
"category": "enterprise",
|
|
69
|
+
"source": "anthropics",
|
|
70
|
+
"path": "skills/internal-comms",
|
|
71
|
+
"description": "Draft internal company communications (announcements, memos, all-hands)."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"slug": "mcp-builder",
|
|
75
|
+
"title": "MCP Builder",
|
|
76
|
+
"category": "development",
|
|
77
|
+
"source": "anthropics",
|
|
78
|
+
"path": "skills/mcp-builder",
|
|
79
|
+
"description": "Build Model Context Protocol servers — tools, resources, transports."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"slug": "pdf",
|
|
83
|
+
"title": "PDF",
|
|
84
|
+
"category": "documents",
|
|
85
|
+
"source": "anthropics",
|
|
86
|
+
"path": "skills/pdf",
|
|
87
|
+
"description": "Read, fill, and generate PDF files."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"slug": "pptx",
|
|
91
|
+
"title": "PPTX",
|
|
92
|
+
"category": "documents",
|
|
93
|
+
"source": "anthropics",
|
|
94
|
+
"path": "skills/pptx",
|
|
95
|
+
"description": "Read, write, and edit Microsoft PowerPoint .pptx files."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"slug": "skill-creator",
|
|
99
|
+
"title": "Skill Creator",
|
|
100
|
+
"category": "development",
|
|
101
|
+
"source": "anthropics",
|
|
102
|
+
"path": "skills/skill-creator",
|
|
103
|
+
"description": "Create new Claude Skills — scaffold SKILL.md, references, scripts."
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"slug": "slack-gif-creator",
|
|
107
|
+
"title": "Slack GIF Creator",
|
|
108
|
+
"category": "creative",
|
|
109
|
+
"source": "anthropics",
|
|
110
|
+
"path": "skills/slack-gif-creator",
|
|
111
|
+
"description": "Make small animated GIFs for Slack reactions and team messaging."
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"slug": "theme-factory",
|
|
115
|
+
"title": "Theme Factory",
|
|
116
|
+
"category": "design",
|
|
117
|
+
"source": "anthropics",
|
|
118
|
+
"path": "skills/theme-factory",
|
|
119
|
+
"description": "Generate cohesive design-system themes (colors, typography, spacing)."
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"slug": "web-artifacts-builder",
|
|
123
|
+
"title": "Web Artifacts Builder",
|
|
124
|
+
"category": "development",
|
|
125
|
+
"source": "anthropics",
|
|
126
|
+
"path": "skills/web-artifacts-builder",
|
|
127
|
+
"description": "Build self-contained web artifacts (HTML/CSS/JS) for Claude artifacts surface."
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"slug": "webapp-testing",
|
|
131
|
+
"title": "Webapp Testing",
|
|
132
|
+
"category": "development",
|
|
133
|
+
"source": "anthropics",
|
|
134
|
+
"path": "skills/webapp-testing",
|
|
135
|
+
"description": "Test web applications — Playwright/Puppeteer flows, snapshot, a11y."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"slug": "xlsx",
|
|
139
|
+
"title": "XLSX",
|
|
140
|
+
"category": "documents",
|
|
141
|
+
"source": "anthropics",
|
|
142
|
+
"path": "skills/xlsx",
|
|
143
|
+
"description": "Read, write, and edit Microsoft Excel .xlsx files."
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"slug": "rahman-resources",
|
|
147
|
+
"title": "Rahman Resources",
|
|
148
|
+
"category": "development",
|
|
149
|
+
"source": "rahman",
|
|
150
|
+
"path": "skills/rahman-resources",
|
|
151
|
+
"description": "Use the Rahman Resources kitab — discover templates, features, recipes; assemble bundles; emit npx commands."
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rahman-resources",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Scaffolder + installer for Rahman Resources kitab — npx rahman-resources init <app>; add <template>; list; info",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"gen": "node scripts/gen-manifest.mjs",
|
|
27
27
|
"validate": "node scripts/validate.mjs",
|
|
28
|
-
"
|
|
28
|
+
"sync:skills": "node scripts/sync-skills.mjs",
|
|
29
|
+
"sync:skills:check": "node scripts/sync-skills.mjs --check",
|
|
30
|
+
"prepublishOnly": "node scripts/sync-skills.mjs --check && node scripts/validate.mjs"
|
|
29
31
|
},
|
|
30
32
|
"dependencies": {
|
|
31
33
|
"kleur": "^4.1.5",
|