@withmata/blueprints 0.3.4 → 0.4.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/skills/audit/SKILL.md +4 -4
- package/.claude/skills/blueprint-catalog/SKILL.md +17 -7
- package/.claude/skills/copywrite/SKILL.md +187 -0
- package/.claude/skills/copywrite-landing/SKILL.md +489 -0
- package/.claude/skills/design-system/SKILL.md +970 -0
- package/.claude/skills/new-project/SKILL.md +168 -112
- package/.claude/skills/scaffold-auth/SKILL.md +9 -9
- package/.claude/skills/scaffold-db/SKILL.md +14 -14
- package/.claude/skills/scaffold-env/SKILL.md +4 -4
- package/.claude/skills/scaffold-foundation/SKILL.md +15 -15
- package/.claude/skills/scaffold-tailwind/SKILL.md +17 -3
- package/.claude/skills/scaffold-ui/SKILL.md +155 -36
- package/ENGINEERING.md +2 -2
- package/blueprints/discovery/design-system/BLUEPRINT.md +1479 -0
- package/blueprints/discovery/marketing-copywriting/BLUEPRINT.md +664 -0
- package/blueprints/features/auth-better-auth/BLUEPRINT.md +20 -22
- package/blueprints/features/db-drizzle-postgres/BLUEPRINT.md +12 -12
- package/blueprints/features/db-drizzle-postgres/files/db/src/example-entity.ts +1 -1
- package/blueprints/features/db-drizzle-postgres/files/db/src/scripts/seed.ts +1 -1
- package/blueprints/features/env-t3/BLUEPRINT.md +1 -1
- package/blueprints/features/tailwind-v4/BLUEPRINT.md +9 -2
- package/blueprints/features/tailwind-v4/files/tailwind-config/shared-styles.css +80 -1
- package/blueprints/features/ui-shared-components/BLUEPRINT.md +411 -78
- package/blueprints/features/ui-shared-components/files/ui/components/ui/alert-dialog.tsx +192 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/avatar.tsx +71 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/badge.tsx +52 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/breadcrumb.tsx +122 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/button.tsx +56 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/card-select.tsx +72 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/card.tsx +100 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/collapsible.tsx +34 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/combobox.tsx +301 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/dropdown-menu.tsx +264 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/empty-state.tsx +43 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/entity-select.tsx +110 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/field.tsx +237 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/form-field.tsx +217 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/input-group.tsx +161 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/input.tsx +20 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/label.tsx +20 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/org-switcher.tsx +114 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/page-header.tsx +45 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/pagination.tsx +52 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/pill-select.tsx +151 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/popover.tsx +41 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/search-input.tsx +49 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/select.tsx +205 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/selected-entity-card.tsx +47 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/separator.tsx +25 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/sidebar.tsx +389 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/status-filter.tsx +43 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/tag-input.tsx +131 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/textarea.tsx +18 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/user-menu.tsx +149 -0
- package/blueprints/features/ui-shared-components/files/ui/components.json +11 -8
- package/blueprints/features/ui-shared-components/files/ui/package.json +20 -11
- package/blueprints/foundation/monorepo-turbo/BLUEPRINT.md +19 -20
- package/blueprints/foundation/monorepo-turbo/files/root/package.json +1 -1
- package/dist/index.js +241 -100
- package/package.json +1 -1
- package/blueprints/features/tailwind-v4/files/tailwind-config/package.json +0 -20
- package/blueprints/foundation/monorepo-turbo/files/root/pnpm-workspace.yaml +0 -5
package/dist/index.js
CHANGED
|
@@ -2,23 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { intro as intro2, outro as outro2, log as log6 } from "@clack/prompts";
|
|
5
|
-
import
|
|
5
|
+
import pc4 from "picocolors";
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
var API_URL = process.env["WITHMATA_API_URL"] || "https://blueprints.withmata.dev";
|
|
9
|
-
var VERSION = "0.
|
|
10
|
-
|
|
11
|
-
// src/steps/auth.ts
|
|
12
|
-
import { log } from "@clack/prompts";
|
|
9
|
+
var VERSION = "0.4.0";
|
|
13
10
|
|
|
14
|
-
// src/lib/
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
readFileSync,
|
|
19
|
-
writeFileSync,
|
|
20
|
-
unlinkSync
|
|
21
|
-
} from "fs";
|
|
11
|
+
// src/lib/install-state.ts
|
|
12
|
+
import { existsSync, readdirSync, lstatSync, readlinkSync } from "fs";
|
|
13
|
+
import { execSync } from "child_process";
|
|
14
|
+
import { join as join2 } from "path";
|
|
22
15
|
|
|
23
16
|
// src/lib/paths.ts
|
|
24
17
|
import { join } from "path";
|
|
@@ -41,9 +34,92 @@ var OPENCODE_COMMANDS_DIR = ".opencode/commands";
|
|
|
41
34
|
var CURSOR_COMMANDS_DIR = ".cursor/commands";
|
|
42
35
|
var BLUEPRINTS_PATH_ENV = "WITHMATA_BLUEPRINTS_PATH";
|
|
43
36
|
|
|
37
|
+
// src/lib/install-state.ts
|
|
38
|
+
function detectInstallState(targetDir) {
|
|
39
|
+
const previousSkillCount = countSkillDirs(SKILLS_SOURCE_DIR);
|
|
40
|
+
const toolStates = {
|
|
41
|
+
"claude-code": {
|
|
42
|
+
detected: hasClaudeCode(targetDir),
|
|
43
|
+
...countInstalledSkills(CLAUDE_SKILLS_DIR)
|
|
44
|
+
},
|
|
45
|
+
opencode: {
|
|
46
|
+
detected: hasOpenCode(targetDir),
|
|
47
|
+
...countInstalledSkills(OPENCODE_SKILLS_DIR)
|
|
48
|
+
},
|
|
49
|
+
cursor: {
|
|
50
|
+
detected: hasCursor(targetDir),
|
|
51
|
+
...countInstalledSkills(CURSOR_SKILLS_DIR)
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const anyToolHasSkills = Object.values(toolStates).some(
|
|
55
|
+
(s) => s.skillsInstalled
|
|
56
|
+
);
|
|
57
|
+
const isUpdate = anyToolHasSkills || existsSync(BLUEPRINTS_DIR) && previousSkillCount > 0;
|
|
58
|
+
return { isUpdate, previousSkillCount, toolStates };
|
|
59
|
+
}
|
|
60
|
+
function countSkillDirs(dir) {
|
|
61
|
+
if (!existsSync(dir)) return 0;
|
|
62
|
+
return readdirSync(dir, { withFileTypes: true }).filter(
|
|
63
|
+
(e) => e.isDirectory()
|
|
64
|
+
).length;
|
|
65
|
+
}
|
|
66
|
+
function countInstalledSkills(skillsDir) {
|
|
67
|
+
if (!existsSync(skillsDir)) {
|
|
68
|
+
return { skillsInstalled: false, installedCount: 0 };
|
|
69
|
+
}
|
|
70
|
+
let count = 0;
|
|
71
|
+
for (const entry of readdirSync(skillsDir)) {
|
|
72
|
+
const entryPath = join2(skillsDir, entry);
|
|
73
|
+
try {
|
|
74
|
+
if (lstatSync(entryPath).isSymbolicLink()) {
|
|
75
|
+
const target = readlinkSync(entryPath);
|
|
76
|
+
if (target.includes(BLUEPRINTS_DIR)) {
|
|
77
|
+
count++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { skillsInstalled: count > 0, installedCount: count };
|
|
84
|
+
}
|
|
85
|
+
function hasClaudeCode(targetDir) {
|
|
86
|
+
try {
|
|
87
|
+
execSync("which claude", { stdio: "ignore" });
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return existsSync(join2(targetDir, ".claude")) || existsSync(join2(targetDir, "CLAUDE.md"));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function hasOpenCode(targetDir) {
|
|
94
|
+
try {
|
|
95
|
+
execSync("which opencode", { stdio: "ignore" });
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
return existsSync(join2(targetDir, ".opencode")) || existsSync(join2(targetDir, "opencode.json"));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function hasCursor(targetDir) {
|
|
102
|
+
try {
|
|
103
|
+
execSync("which cursor", { stdio: "ignore" });
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return existsSync(join2(targetDir, ".cursor"));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/steps/auth.ts
|
|
111
|
+
import { log } from "@clack/prompts";
|
|
112
|
+
|
|
44
113
|
// src/lib/credentials.ts
|
|
114
|
+
import {
|
|
115
|
+
existsSync as existsSync2,
|
|
116
|
+
mkdirSync,
|
|
117
|
+
readFileSync,
|
|
118
|
+
writeFileSync,
|
|
119
|
+
unlinkSync
|
|
120
|
+
} from "fs";
|
|
45
121
|
function readCredentials() {
|
|
46
|
-
if (!
|
|
122
|
+
if (!existsSync2(CREDENTIALS_PATH)) return null;
|
|
47
123
|
try {
|
|
48
124
|
const raw = readFileSync(CREDENTIALS_PATH, "utf-8");
|
|
49
125
|
const data = JSON.parse(raw);
|
|
@@ -65,37 +141,35 @@ async function checkAuth() {
|
|
|
65
141
|
}
|
|
66
142
|
|
|
67
143
|
// src/steps/tool-detect.ts
|
|
68
|
-
import { existsSync as existsSync2 } from "fs";
|
|
69
|
-
import { execSync } from "child_process";
|
|
70
|
-
import { join as join2 } from "path";
|
|
71
144
|
import { multiselect, isCancel, cancel } from "@clack/prompts";
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const cursorDetected = hasCursor(targetDir);
|
|
145
|
+
import pc from "picocolors";
|
|
146
|
+
async function detectAndSelectTools(installState) {
|
|
147
|
+
const { isUpdate, toolStates } = installState;
|
|
76
148
|
const choices = [
|
|
77
149
|
{
|
|
78
150
|
value: "claude-code",
|
|
79
151
|
label: "Claude Code",
|
|
80
|
-
hint:
|
|
152
|
+
hint: buildHint(toolStates["claude-code"])
|
|
81
153
|
},
|
|
82
154
|
{
|
|
83
155
|
value: "opencode",
|
|
84
156
|
label: "OpenCode",
|
|
85
|
-
hint:
|
|
157
|
+
hint: buildHint(toolStates.opencode)
|
|
86
158
|
},
|
|
87
159
|
{
|
|
88
160
|
value: "cursor",
|
|
89
161
|
label: "Cursor",
|
|
90
|
-
hint:
|
|
162
|
+
hint: buildHint(toolStates.cursor)
|
|
91
163
|
}
|
|
92
164
|
];
|
|
93
165
|
const initialValues = [];
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
166
|
+
for (const [tool, state] of Object.entries(toolStates)) {
|
|
167
|
+
if (state.detected || state.skillsInstalled) {
|
|
168
|
+
initialValues.push(tool);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
97
171
|
const tools = await multiselect({
|
|
98
|
-
message: "Which AI coding tools do you use?",
|
|
172
|
+
message: isUpdate ? "Update skills for these tools?" : "Which AI coding tools do you use?",
|
|
99
173
|
options: choices,
|
|
100
174
|
initialValues,
|
|
101
175
|
required: true
|
|
@@ -106,39 +180,27 @@ async function detectAndSelectTools(targetDir) {
|
|
|
106
180
|
}
|
|
107
181
|
return tools;
|
|
108
182
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return true;
|
|
113
|
-
} catch {
|
|
114
|
-
return existsSync2(join2(targetDir, ".claude")) || existsSync2(join2(targetDir, "CLAUDE.md"));
|
|
183
|
+
function buildHint(state) {
|
|
184
|
+
if (state.detected && state.skillsInstalled) {
|
|
185
|
+
return pc.green("detected \xB7 installed");
|
|
115
186
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
execSync("which opencode", { stdio: "ignore" });
|
|
120
|
-
return true;
|
|
121
|
-
} catch {
|
|
122
|
-
return existsSync2(join2(targetDir, ".opencode")) || existsSync2(join2(targetDir, "opencode.json"));
|
|
187
|
+
if (state.detected) {
|
|
188
|
+
return "detected";
|
|
123
189
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
execSync("which cursor", { stdio: "ignore" });
|
|
128
|
-
return true;
|
|
129
|
-
} catch {
|
|
130
|
-
return existsSync2(join2(targetDir, ".cursor"));
|
|
190
|
+
if (state.skillsInstalled) {
|
|
191
|
+
return "installed";
|
|
131
192
|
}
|
|
193
|
+
return void 0;
|
|
132
194
|
}
|
|
133
195
|
|
|
134
196
|
// src/steps/download.ts
|
|
135
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync, rmSync, readdirSync } from "fs";
|
|
197
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync, rmSync, readdirSync as readdirSync2 } from "fs";
|
|
136
198
|
import { join as join3, resolve, dirname } from "path";
|
|
137
199
|
import { fileURLToPath } from "url";
|
|
138
200
|
import { spinner, log as log2 } from "@clack/prompts";
|
|
139
|
-
async function downloadBlueprints(_token) {
|
|
201
|
+
async function downloadBlueprints(_token, isUpdate = false) {
|
|
140
202
|
const s = spinner();
|
|
141
|
-
s.start("Resolving blueprints...");
|
|
203
|
+
s.start(isUpdate ? "Updating blueprints..." : "Resolving blueprints...");
|
|
142
204
|
const sourcePath = resolveBlueprintsSource();
|
|
143
205
|
if (!sourcePath) {
|
|
144
206
|
s.stop("Could not find blueprints source");
|
|
@@ -149,7 +211,7 @@ async function downloadBlueprints(_token) {
|
|
|
149
211
|
);
|
|
150
212
|
process.exit(1);
|
|
151
213
|
}
|
|
152
|
-
s.message("Copying blueprints...");
|
|
214
|
+
s.message(isUpdate ? "Updating blueprints..." : "Copying blueprints...");
|
|
153
215
|
rmSync(BLUEPRINTS_DIR, { recursive: true, force: true });
|
|
154
216
|
mkdirSync2(BLUEPRINTS_DIR, { recursive: true });
|
|
155
217
|
const blueprintsSource = join3(sourcePath, "blueprints");
|
|
@@ -169,7 +231,9 @@ async function downloadBlueprints(_token) {
|
|
|
169
231
|
cpSync(engineeringSource, join3(BLUEPRINTS_DIR, "ENGINEERING.md"));
|
|
170
232
|
}
|
|
171
233
|
const blueprintIds = discoverBlueprints(join3(BLUEPRINTS_DIR, "blueprints"));
|
|
172
|
-
s.stop(
|
|
234
|
+
s.stop(
|
|
235
|
+
`${blueprintIds.length} blueprints ${isUpdate ? "updated" : "installed"}`
|
|
236
|
+
);
|
|
173
237
|
return {
|
|
174
238
|
blueprintIds,
|
|
175
239
|
total: blueprintIds.length,
|
|
@@ -209,7 +273,7 @@ function discoverBlueprints(blueprintsDir) {
|
|
|
209
273
|
for (const tier of tiers) {
|
|
210
274
|
const tierDir = join3(blueprintsDir, tier);
|
|
211
275
|
if (!existsSync3(tierDir)) continue;
|
|
212
|
-
for (const entry of
|
|
276
|
+
for (const entry of readdirSync2(tierDir, { withFileTypes: true })) {
|
|
213
277
|
if (entry.isDirectory()) {
|
|
214
278
|
ids.push(entry.name);
|
|
215
279
|
}
|
|
@@ -223,57 +287,62 @@ import {
|
|
|
223
287
|
existsSync as existsSync4,
|
|
224
288
|
mkdirSync as mkdirSync3,
|
|
225
289
|
symlinkSync,
|
|
226
|
-
lstatSync,
|
|
290
|
+
lstatSync as lstatSync2,
|
|
227
291
|
unlinkSync as unlinkSync2,
|
|
228
292
|
renameSync,
|
|
229
|
-
readdirSync as
|
|
230
|
-
readlinkSync
|
|
293
|
+
readdirSync as readdirSync3,
|
|
294
|
+
readlinkSync as readlinkSync2
|
|
231
295
|
} from "fs";
|
|
232
296
|
import { join as join4, resolve as resolve2 } from "path";
|
|
233
297
|
import { spinner as spinner2, log as log3 } from "@clack/prompts";
|
|
234
|
-
async function installSkillsGlobally(tools) {
|
|
298
|
+
async function installSkillsGlobally(tools, isUpdate = false) {
|
|
235
299
|
let skillsInstalled = 0;
|
|
236
300
|
for (const tool of tools) {
|
|
237
301
|
if (tool === "claude-code") {
|
|
238
302
|
skillsInstalled += installSkills(
|
|
239
303
|
SKILLS_SOURCE_DIR,
|
|
240
304
|
CLAUDE_SKILLS_DIR,
|
|
241
|
-
"Claude Code"
|
|
305
|
+
"Claude Code",
|
|
306
|
+
isUpdate
|
|
242
307
|
);
|
|
243
308
|
}
|
|
244
309
|
if (tool === "opencode") {
|
|
245
310
|
skillsInstalled += installSkills(
|
|
246
311
|
SKILLS_SOURCE_DIR,
|
|
247
312
|
OPENCODE_SKILLS_DIR,
|
|
248
|
-
"OpenCode"
|
|
313
|
+
"OpenCode",
|
|
314
|
+
isUpdate
|
|
249
315
|
);
|
|
250
316
|
}
|
|
251
317
|
if (tool === "cursor") {
|
|
252
318
|
skillsInstalled += installSkills(
|
|
253
319
|
SKILLS_SOURCE_DIR,
|
|
254
320
|
CURSOR_SKILLS_DIR,
|
|
255
|
-
"Cursor"
|
|
321
|
+
"Cursor",
|
|
322
|
+
isUpdate
|
|
256
323
|
);
|
|
257
324
|
}
|
|
258
325
|
}
|
|
259
326
|
const legacyCommandsCleaned = cleanupLegacyCommands(process.cwd());
|
|
260
327
|
return { skillsInstalled, legacyCommandsCleaned };
|
|
261
328
|
}
|
|
262
|
-
function installSkills(sourceDir, globalTargetDir, toolLabel) {
|
|
329
|
+
function installSkills(sourceDir, globalTargetDir, toolLabel, isUpdate) {
|
|
263
330
|
const s = spinner2();
|
|
264
|
-
s.start(
|
|
331
|
+
s.start(
|
|
332
|
+
isUpdate ? `Updating skills for ${toolLabel}...` : `Installing skills for ${toolLabel}...`
|
|
333
|
+
);
|
|
265
334
|
mkdirSync3(globalTargetDir, { recursive: true });
|
|
266
335
|
if (!existsSync4(sourceDir)) {
|
|
267
336
|
s.stop(`No skills found for ${toolLabel}`);
|
|
268
337
|
return 0;
|
|
269
338
|
}
|
|
270
339
|
let count = 0;
|
|
271
|
-
for (const entry of
|
|
340
|
+
for (const entry of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
272
341
|
if (!entry.isDirectory()) continue;
|
|
273
342
|
const source = resolve2(sourceDir, entry.name);
|
|
274
343
|
const target = join4(globalTargetDir, entry.name);
|
|
275
344
|
if (existsSync4(target) || lstatExists(target)) {
|
|
276
|
-
if (
|
|
345
|
+
if (lstatSync2(target).isSymbolicLink()) {
|
|
277
346
|
unlinkSync2(target);
|
|
278
347
|
} else {
|
|
279
348
|
const backup = `${target}.bak`;
|
|
@@ -286,7 +355,9 @@ function installSkills(sourceDir, globalTargetDir, toolLabel) {
|
|
|
286
355
|
symlinkSync(source, target);
|
|
287
356
|
count++;
|
|
288
357
|
}
|
|
289
|
-
s.stop(
|
|
358
|
+
s.stop(
|
|
359
|
+
`${count} skills ${isUpdate ? "updated" : "installed"} for ${toolLabel}`
|
|
360
|
+
);
|
|
290
361
|
return count;
|
|
291
362
|
}
|
|
292
363
|
function cleanupLegacyCommands(targetDir) {
|
|
@@ -299,11 +370,11 @@ function cleanupLegacyCommands(targetDir) {
|
|
|
299
370
|
for (const dir of legacyDirs) {
|
|
300
371
|
if (!existsSync4(dir)) continue;
|
|
301
372
|
let dirEmpty = true;
|
|
302
|
-
for (const file of
|
|
373
|
+
for (const file of readdirSync3(dir)) {
|
|
303
374
|
const filePath = join4(dir, file);
|
|
304
|
-
if (
|
|
375
|
+
if (lstatSync2(filePath).isSymbolicLink()) {
|
|
305
376
|
try {
|
|
306
|
-
const linkTarget =
|
|
377
|
+
const linkTarget = readlinkSync2(filePath);
|
|
307
378
|
if (linkTarget.includes(BLUEPRINTS_DIR)) {
|
|
308
379
|
unlinkSync2(filePath);
|
|
309
380
|
cleaned++;
|
|
@@ -317,10 +388,10 @@ function cleanupLegacyCommands(targetDir) {
|
|
|
317
388
|
}
|
|
318
389
|
dirEmpty = false;
|
|
319
390
|
}
|
|
320
|
-
if (dirEmpty &&
|
|
391
|
+
if (dirEmpty && readdirSync3(dir).length === 0) {
|
|
321
392
|
try {
|
|
322
|
-
|
|
323
|
-
if (
|
|
393
|
+
readdirSync3(dir);
|
|
394
|
+
if (readdirSync3(dir).length === 0) {
|
|
324
395
|
unlinkSync2(dir);
|
|
325
396
|
}
|
|
326
397
|
} catch {
|
|
@@ -334,7 +405,7 @@ function cleanupLegacyCommands(targetDir) {
|
|
|
334
405
|
}
|
|
335
406
|
function lstatExists(path) {
|
|
336
407
|
try {
|
|
337
|
-
|
|
408
|
+
lstatSync2(path);
|
|
338
409
|
return true;
|
|
339
410
|
} catch {
|
|
340
411
|
return false;
|
|
@@ -343,30 +414,92 @@ function lstatExists(path) {
|
|
|
343
414
|
|
|
344
415
|
// src/steps/summary.ts
|
|
345
416
|
import { log as log4, note } from "@clack/prompts";
|
|
346
|
-
import
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
417
|
+
import pc2 from "picocolors";
|
|
418
|
+
var SKILL_GROUPS = [
|
|
419
|
+
{
|
|
420
|
+
title: "Getting Started",
|
|
421
|
+
skills: [
|
|
422
|
+
{
|
|
423
|
+
command: "/new-project",
|
|
424
|
+
description: "Build a new product end-to-end \u2014 from idea to running code"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
command: "/discover",
|
|
428
|
+
description: "Define your product vision before writing any code"
|
|
429
|
+
}
|
|
430
|
+
]
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
title: "Add to Your Project",
|
|
434
|
+
skills: [
|
|
435
|
+
{
|
|
436
|
+
command: "/scaffold-foundation",
|
|
437
|
+
description: "Set up a monorepo with Turborepo and shared tooling"
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
command: "/scaffold-db",
|
|
441
|
+
description: "Add a database with Drizzle ORM and PostgreSQL"
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
command: "/scaffold-auth",
|
|
445
|
+
description: "Add authentication, sessions, and organization support"
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
command: "/scaffold-env",
|
|
449
|
+
description: "Add validated environment variables with type-safe access"
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
command: "/scaffold-tailwind",
|
|
453
|
+
description: "Add a design system with Tailwind v4 tokens and animations"
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
command: "/scaffold-ui",
|
|
457
|
+
description: "Add a component library powered by shadcn/ui"
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
title: "Maintain",
|
|
463
|
+
skills: [
|
|
464
|
+
{
|
|
465
|
+
command: "/audit",
|
|
466
|
+
description: "Check project health and find available upgrades"
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
}
|
|
470
|
+
];
|
|
471
|
+
function showSummary({
|
|
472
|
+
installed,
|
|
473
|
+
tools,
|
|
474
|
+
isUpdate
|
|
475
|
+
}) {
|
|
476
|
+
const maxCommandLen = Math.max(
|
|
477
|
+
...SKILL_GROUPS.flatMap((g) => g.skills.map((s) => s.command.length))
|
|
478
|
+
);
|
|
479
|
+
const sections = SKILL_GROUPS.map((group) => {
|
|
480
|
+
const header = pc2.bold(group.title);
|
|
481
|
+
const lines = group.skills.map((skill) => {
|
|
482
|
+
const padded = skill.command.padEnd(maxCommandLen + 2);
|
|
483
|
+
return ` ${pc2.cyan(padded)}${pc2.dim(skill.description)}`;
|
|
484
|
+
});
|
|
485
|
+
return `${header}
|
|
486
|
+
${lines.join("\n")}`;
|
|
487
|
+
});
|
|
488
|
+
note(sections.join("\n\n"), "Available skills");
|
|
357
489
|
const toolNameMap = {
|
|
358
490
|
"claude-code": "Claude Code",
|
|
359
491
|
opencode: "OpenCode",
|
|
360
492
|
cursor: "Cursor"
|
|
361
493
|
};
|
|
362
494
|
const toolNames = tools.map((t) => toolNameMap[t]).join(", ");
|
|
495
|
+
const verb = isUpdate ? "updated" : "installed";
|
|
363
496
|
log4.info(
|
|
364
|
-
`${
|
|
497
|
+
`${pc2.bold(String(installed.skillsInstalled))} skills ${verb} for ${toolNames}.`
|
|
365
498
|
);
|
|
366
499
|
log4.info("These skills work in any project \u2014 no per-project setup needed.");
|
|
367
500
|
if (installed.legacyCommandsCleaned > 0) {
|
|
368
501
|
log4.info(
|
|
369
|
-
|
|
502
|
+
pc2.dim(
|
|
370
503
|
`Cleaned up ${installed.legacyCommandsCleaned} legacy command symlink(s).`
|
|
371
504
|
)
|
|
372
505
|
);
|
|
@@ -376,30 +509,30 @@ function showSummary({ installed, tools, blueprintCount }) {
|
|
|
376
509
|
// src/steps/uninstall.ts
|
|
377
510
|
import {
|
|
378
511
|
existsSync as existsSync5,
|
|
379
|
-
readdirSync as
|
|
380
|
-
lstatSync as
|
|
381
|
-
readlinkSync as
|
|
512
|
+
readdirSync as readdirSync4,
|
|
513
|
+
lstatSync as lstatSync3,
|
|
514
|
+
readlinkSync as readlinkSync3,
|
|
382
515
|
unlinkSync as unlinkSync3,
|
|
383
516
|
rmSync as rmSync2
|
|
384
517
|
} from "fs";
|
|
385
518
|
import { join as join5 } from "path";
|
|
386
519
|
import { intro, outro, confirm, log as log5, spinner as spinner3 } from "@clack/prompts";
|
|
387
|
-
import
|
|
520
|
+
import pc3 from "picocolors";
|
|
388
521
|
var SKILL_DIRS = [CLAUDE_SKILLS_DIR, OPENCODE_SKILLS_DIR, CURSOR_SKILLS_DIR];
|
|
389
522
|
async function runUninstall() {
|
|
390
523
|
intro(
|
|
391
|
-
`${
|
|
524
|
+
`${pc3.bgCyan(pc3.black(" @withmata/blueprints "))} ${pc3.dim(`v${VERSION}`)} ${pc3.yellow("uninstall")}`
|
|
392
525
|
);
|
|
393
526
|
const s = spinner3();
|
|
394
527
|
s.start("Scanning for withmata skill symlinks...");
|
|
395
528
|
let symlinksRemoved = 0;
|
|
396
529
|
for (const dir of SKILL_DIRS) {
|
|
397
530
|
if (!existsSync5(dir)) continue;
|
|
398
|
-
for (const entry of
|
|
531
|
+
for (const entry of readdirSync4(dir)) {
|
|
399
532
|
const fullPath = join5(dir, entry);
|
|
400
533
|
try {
|
|
401
|
-
if (!
|
|
402
|
-
const target =
|
|
534
|
+
if (!lstatSync3(fullPath).isSymbolicLink()) continue;
|
|
535
|
+
const target = readlinkSync3(fullPath);
|
|
403
536
|
if (target.startsWith(BLUEPRINTS_DIR)) {
|
|
404
537
|
unlinkSync3(fullPath);
|
|
405
538
|
symlinksRemoved++;
|
|
@@ -472,22 +605,30 @@ async function main() {
|
|
|
472
605
|
console.log(HELP);
|
|
473
606
|
process.exit(1);
|
|
474
607
|
}
|
|
475
|
-
|
|
608
|
+
const installState = detectInstallState(process.cwd());
|
|
609
|
+
const { isUpdate } = installState;
|
|
610
|
+
const modeLabel = isUpdate ? ` ${pc4.dim("\xB7 update")}` : "";
|
|
611
|
+
intro2(
|
|
612
|
+
`${pc4.bgCyan(pc4.black(" @withmata/blueprints "))} ${pc4.dim(`v${VERSION}`)}${modeLabel}`
|
|
613
|
+
);
|
|
476
614
|
const user = await checkAuth();
|
|
477
615
|
if (user) {
|
|
478
616
|
log6.success(`Logged in as ${user.email} (${user.tier} plan)`);
|
|
479
617
|
} else {
|
|
480
618
|
log6.info("Running in local mode");
|
|
481
619
|
}
|
|
482
|
-
const tools = await detectAndSelectTools(
|
|
483
|
-
const blueprints = await downloadBlueprints(user?.token);
|
|
484
|
-
const installed = await installSkillsGlobally(tools);
|
|
620
|
+
const tools = await detectAndSelectTools(installState);
|
|
621
|
+
const blueprints = await downloadBlueprints(user?.token, isUpdate);
|
|
622
|
+
const installed = await installSkillsGlobally(tools, isUpdate);
|
|
485
623
|
showSummary({
|
|
486
624
|
installed,
|
|
487
625
|
tools,
|
|
488
|
-
blueprintCount: blueprints.total
|
|
626
|
+
blueprintCount: blueprints.total,
|
|
627
|
+
isUpdate
|
|
489
628
|
});
|
|
490
|
-
outro2(
|
|
629
|
+
outro2(
|
|
630
|
+
isUpdate ? "Updated! Changes take effect in your next conversation." : "Setup complete! Type /new-project in any project to get started."
|
|
631
|
+
);
|
|
491
632
|
}
|
|
492
633
|
main().catch((err) => {
|
|
493
634
|
log6.error(
|
package/package.json
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{SCOPE}}/tailwind-config",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"private": true,
|
|
6
|
-
"exports": {
|
|
7
|
-
".": "./shared-styles.css",
|
|
8
|
-
"./postcss": "./postcss.config.mjs"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"@tailwindcss/typography": "^0.5",
|
|
12
|
-
"shadcn": "^3",
|
|
13
|
-
"tailwind-scrollbar-hide": "^4",
|
|
14
|
-
"tw-animate-css": "^1"
|
|
15
|
-
},
|
|
16
|
-
"devDependencies": {
|
|
17
|
-
"@tailwindcss/postcss": "^4",
|
|
18
|
-
"tailwindcss": "^4"
|
|
19
|
-
}
|
|
20
|
-
}
|