@maggit/claude-workspace 0.1.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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -0
  3. package/assets/claude-md/default.md +34 -0
  4. package/assets/claude-md/engineering-exec.md +34 -0
  5. package/assets/claude-md/indie-maker.md +33 -0
  6. package/assets/claude-md/marketing.md +33 -0
  7. package/assets/profiles/default.json +46 -0
  8. package/assets/profiles/engineering-exec.json +42 -0
  9. package/assets/profiles/indie-maker.json +32 -0
  10. package/assets/profiles/marketing.json +27 -0
  11. package/assets/skills/adr/SKILL.md +66 -0
  12. package/assets/skills/apidoc/SKILL.md +78 -0
  13. package/assets/skills/changelog/SKILL.md +54 -0
  14. package/assets/skills/commit/SKILL.md +18 -0
  15. package/assets/skills/completetodo/SKILL.md +75 -0
  16. package/assets/skills/createtodo/SKILL.md +79 -0
  17. package/assets/skills/deadcode/SKILL.md +56 -0
  18. package/assets/skills/debug/SKILL.md +62 -0
  19. package/assets/skills/deps/SKILL.md +66 -0
  20. package/assets/skills/e2e/SKILL.md +68 -0
  21. package/assets/skills/eng-spec/SKILL.md +93 -0
  22. package/assets/skills/envcheck/SKILL.md +65 -0
  23. package/assets/skills/explain/SKILL.md +52 -0
  24. package/assets/skills/landing-page-copy/SKILL.md +95 -0
  25. package/assets/skills/meeting-notes/SKILL.md +90 -0
  26. package/assets/skills/migration/SKILL.md +61 -0
  27. package/assets/skills/openpr/SKILL.md +143 -0
  28. package/assets/skills/opentodos/SKILL.md +79 -0
  29. package/assets/skills/perf/SKILL.md +58 -0
  30. package/assets/skills/prd/SKILL.md +71 -0
  31. package/assets/skills/readme/SKILL.md +55 -0
  32. package/assets/skills/rebase/SKILL.md +59 -0
  33. package/assets/skills/release/SKILL.md +64 -0
  34. package/assets/skills/release-plan/SKILL.md +107 -0
  35. package/assets/skills/requirements/SKILL.md +77 -0
  36. package/assets/skills/seo-brief/SKILL.md +88 -0
  37. package/assets/skills/standup/SKILL.md +48 -0
  38. package/assets/skills/storecontext/SKILL.md +76 -0
  39. package/assets/skills/summary/SKILL.md +72 -0
  40. package/assets/skills/test/SKILL.md +55 -0
  41. package/assets/skills/testcoverage/SKILL.md +57 -0
  42. package/assets/skills/todo/SKILL.md +84 -0
  43. package/assets/templates/DECISION_RECORD_TEMPLATE.md +59 -0
  44. package/assets/templates/ENG_SPEC_TEMPLATE.md +84 -0
  45. package/assets/templates/PRD_TEMPLATE.md +72 -0
  46. package/assets/templates/PROJECT_INDEX_TEMPLATE.md +65 -0
  47. package/assets/templates/WEEKLY_REVIEW_TEMPLATE.md +57 -0
  48. package/assets/vault/README.md +34 -0
  49. package/assets/vault/_index.md +27 -0
  50. package/dist/bin.js +888 -0
  51. package/dist/bin.js.map +1 -0
  52. package/package.json +60 -0
package/dist/bin.js ADDED
@@ -0,0 +1,888 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command as Command5 } from "commander";
5
+
6
+ // src/constants.ts
7
+ var VERSION = "0.1.0";
8
+ var CLAUDE_DIR = ".claude";
9
+ var CLAUDE_EXAMPLE_FILE = "CLAUDE.example.md";
10
+ var CLAUDE_MD_FILE = "CLAUDE.md";
11
+ var CONFIG_FILE = "config.json";
12
+ var ACTIVE_PROFILE_FILE = "active.json";
13
+ var CLAUDE_SUBDIRS = [
14
+ "skills",
15
+ "templates",
16
+ "snippets",
17
+ "logs",
18
+ "profiles"
19
+ ];
20
+ var VAULT_FOLDERS = [
21
+ "00_inbox",
22
+ "01_specs",
23
+ "02_architecture",
24
+ "03_decisions",
25
+ "04_knowledge",
26
+ "05_prompts",
27
+ "06_agents",
28
+ "07_diagrams",
29
+ "08_todos"
30
+ ];
31
+ var DEFAULT_VAULT_NAME = "ContextDB";
32
+ var DEFAULT_PROFILE = "default";
33
+ var PROFILES = [
34
+ "default",
35
+ "engineering-exec",
36
+ "indie-maker",
37
+ "marketing"
38
+ ];
39
+
40
+ // src/commands/init.ts
41
+ import { Command } from "commander";
42
+
43
+ // src/actions/init.ts
44
+ import path9 from "path";
45
+ import ora from "ora";
46
+
47
+ // src/utils/logger.ts
48
+ import chalk from "chalk";
49
+ var log = {
50
+ info(msg) {
51
+ console.log(chalk.blue("\u2139"), msg);
52
+ },
53
+ success(msg) {
54
+ console.log(chalk.green("\u2714"), msg);
55
+ },
56
+ warn(msg) {
57
+ console.log(chalk.yellow("\u26A0"), msg);
58
+ },
59
+ error(msg) {
60
+ console.error(chalk.red("\u2716"), msg);
61
+ },
62
+ step(msg) {
63
+ console.log(chalk.cyan("\u2192"), msg);
64
+ },
65
+ dryRun(msg) {
66
+ console.log(chalk.magenta("[dry-run]"), msg);
67
+ },
68
+ pass(msg) {
69
+ console.log(chalk.green("PASS"), msg);
70
+ },
71
+ fail(msg) {
72
+ console.log(chalk.red("FAIL"), msg);
73
+ },
74
+ plain(msg) {
75
+ console.log(msg);
76
+ }
77
+ };
78
+
79
+ // src/utils/fs.ts
80
+ import fs from "fs-extra";
81
+ import crypto from "crypto";
82
+ import path from "path";
83
+ async function fileExists(filePath) {
84
+ try {
85
+ await fs.access(filePath);
86
+ return true;
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+ async function hashFile(filePath) {
92
+ const content = await fs.readFile(filePath, "utf-8");
93
+ return hashString(content);
94
+ }
95
+ function hashString(content) {
96
+ return crypto.createHash("sha256").update(content).digest("hex");
97
+ }
98
+ async function backupFile(filePath) {
99
+ const timestamp = Date.now();
100
+ const ext = path.extname(filePath);
101
+ const base = ext ? filePath.slice(0, -ext.length) : filePath;
102
+ const backupPath = `${base}.bak.${timestamp}${ext}`;
103
+ await fs.copy(filePath, backupPath);
104
+ return backupPath;
105
+ }
106
+ async function safeWriteFile(filePath, content) {
107
+ await fs.ensureDir(path.dirname(filePath));
108
+ await fs.writeFile(filePath, content, "utf-8");
109
+ }
110
+ async function safeReadFile(filePath) {
111
+ try {
112
+ return await fs.readFile(filePath, "utf-8");
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ // src/services/prompts.ts
119
+ import prompts from "prompts";
120
+ async function runInitPrompts(opts) {
121
+ if (opts.yes) {
122
+ return {
123
+ profile: opts.profile,
124
+ vault: opts.vault
125
+ };
126
+ }
127
+ const response = await prompts(
128
+ [
129
+ {
130
+ type: "select",
131
+ name: "profile",
132
+ message: "Which profile would you like to use?",
133
+ choices: PROFILES.map((p) => ({ title: p, value: p })),
134
+ initial: PROFILES.indexOf(
135
+ opts.profile
136
+ )
137
+ },
138
+ {
139
+ type: "text",
140
+ name: "vault",
141
+ message: "Vault folder name?",
142
+ initial: opts.vault || DEFAULT_VAULT_NAME
143
+ }
144
+ ],
145
+ {
146
+ onCancel: () => {
147
+ process.exit(1);
148
+ }
149
+ }
150
+ );
151
+ return {
152
+ profile: response.profile || DEFAULT_PROFILE,
153
+ vault: response.vault || DEFAULT_VAULT_NAME
154
+ };
155
+ }
156
+
157
+ // src/services/profiles.ts
158
+ import path3 from "path";
159
+ import fs2 from "fs-extra";
160
+
161
+ // src/utils/paths.ts
162
+ import path2 from "path";
163
+ import { fileURLToPath } from "url";
164
+ var __filename = fileURLToPath(import.meta.url);
165
+ var __dirname = path2.dirname(__filename);
166
+ function getAssetsDir() {
167
+ const inSrc = __dirname.includes(path2.sep + "src");
168
+ const cliRoot = inSrc ? path2.resolve(__dirname, "..", "..") : path2.resolve(__dirname, "..");
169
+ const assetsDir = path2.join(cliRoot, "assets");
170
+ return assetsDir;
171
+ }
172
+ function getSkillsDir() {
173
+ return path2.join(getAssetsDir(), "skills");
174
+ }
175
+ function getTemplatesDir() {
176
+ return path2.join(getAssetsDir(), "templates");
177
+ }
178
+ function getProfilesDir() {
179
+ return path2.join(getAssetsDir(), "profiles");
180
+ }
181
+ function getClaudeMdTemplatesDir() {
182
+ return path2.join(getAssetsDir(), "claude-md");
183
+ }
184
+ function getVaultTemplatesDir() {
185
+ return path2.join(getAssetsDir(), "vault");
186
+ }
187
+
188
+ // src/services/profiles.ts
189
+ async function loadProfile(name) {
190
+ if (!PROFILES.includes(name)) {
191
+ throw new Error(
192
+ `Unknown profile "${name}". Available profiles: ${PROFILES.join(", ")}`
193
+ );
194
+ }
195
+ const profilePath = path3.join(getProfilesDir(), `${name}.json`);
196
+ const content = await fs2.readFile(profilePath, "utf-8");
197
+ return JSON.parse(content);
198
+ }
199
+
200
+ // src/services/scaffold-claude-dir.ts
201
+ import path4 from "path";
202
+ import fs3 from "fs-extra";
203
+ async function scaffoldClaudeDir(targetDir, profile, options, existingActive) {
204
+ const claudeDir = path4.join(targetDir, CLAUDE_DIR);
205
+ const managedFiles = [];
206
+ for (const subdir of CLAUDE_SUBDIRS) {
207
+ const dir = path4.join(claudeDir, subdir);
208
+ if (options.dryRun) {
209
+ if (!await fileExists(dir)) {
210
+ log.dryRun(`Would create directory: ${CLAUDE_DIR}/${subdir}/`);
211
+ }
212
+ } else {
213
+ await fs3.ensureDir(dir);
214
+ }
215
+ }
216
+ const existingHashes = buildExistingHashes(existingActive);
217
+ for (const skillName of profile.skills) {
218
+ const result = await installSingleSkill(
219
+ targetDir,
220
+ skillName,
221
+ existingHashes,
222
+ options
223
+ );
224
+ if (result) {
225
+ managedFiles.push(result);
226
+ }
227
+ }
228
+ const templatesSrcDir = getTemplatesDir();
229
+ for (const templateFile of profile.templates) {
230
+ const srcPath = path4.join(templatesSrcDir, templateFile);
231
+ const destPath = path4.join(claudeDir, "templates", templateFile);
232
+ const relativePath = path4.join(CLAUDE_DIR, "templates", templateFile);
233
+ const result = await copyManagedFile(
234
+ srcPath,
235
+ destPath,
236
+ relativePath,
237
+ existingHashes,
238
+ options
239
+ );
240
+ if (result) {
241
+ managedFiles.push(result);
242
+ }
243
+ }
244
+ return { managedFiles };
245
+ }
246
+ async function installSingleSkill(targetDir, skillName, existingHashes, options) {
247
+ const claudeDir = path4.join(targetDir, CLAUDE_DIR);
248
+ const skillsSrcDir = getSkillsDir();
249
+ const srcPath = path4.join(skillsSrcDir, skillName, "SKILL.md");
250
+ const destDir = path4.join(claudeDir, "skills", skillName);
251
+ const destPath = path4.join(destDir, "SKILL.md");
252
+ const relativePath = path4.join(CLAUDE_DIR, "skills", skillName, "SKILL.md");
253
+ if (options.dryRun) {
254
+ if (!await fileExists(destDir)) {
255
+ log.dryRun(`Would create directory: ${CLAUDE_DIR}/skills/${skillName}/`);
256
+ }
257
+ } else {
258
+ await fs3.ensureDir(destDir);
259
+ }
260
+ return copyManagedFile(srcPath, destPath, relativePath, existingHashes, options);
261
+ }
262
+ function buildExistingHashes(existingActive) {
263
+ const hashes = /* @__PURE__ */ new Map();
264
+ if (existingActive) {
265
+ for (const mf of existingActive.managedFiles) {
266
+ hashes.set(mf.path, mf.hash);
267
+ }
268
+ }
269
+ return hashes;
270
+ }
271
+ async function copyManagedFile(srcPath, destPath, relativePath, existingHashes, options) {
272
+ const srcContent = await fs3.readFile(srcPath, "utf-8");
273
+ const srcHash = hashString(srcContent);
274
+ if (await fileExists(destPath)) {
275
+ const destHash = await hashFile(destPath);
276
+ const previousHash = existingHashes.get(relativePath);
277
+ if (destHash === srcHash) {
278
+ return { path: relativePath, hash: srcHash };
279
+ }
280
+ if (!previousHash && !options.force) {
281
+ log.warn(
282
+ `Skipping ${relativePath} (already exists, not managed). Delete it and re-run to reinstall, or use --force to overwrite.`
283
+ );
284
+ return null;
285
+ }
286
+ if (previousHash && destHash !== previousHash && !options.force) {
287
+ log.warn(`Skipping ${relativePath} (modified by user). Use --force to overwrite.`);
288
+ return { path: relativePath, hash: destHash };
289
+ }
290
+ if (options.dryRun) {
291
+ log.dryRun(`Would update: ${relativePath}`);
292
+ } else {
293
+ const backupPath = await backupFile(destPath);
294
+ log.step(`Backed up ${relativePath} \u2192 ${path4.basename(backupPath)}`);
295
+ await safeWriteFile(destPath, srcContent);
296
+ log.success(`Updated ${relativePath}`);
297
+ }
298
+ } else {
299
+ if (options.dryRun) {
300
+ log.dryRun(`Would create: ${relativePath}`);
301
+ } else {
302
+ await safeWriteFile(destPath, srcContent);
303
+ log.success(`Created ${relativePath}`);
304
+ }
305
+ }
306
+ return { path: relativePath, hash: srcHash };
307
+ }
308
+
309
+ // src/services/scaffold-vault.ts
310
+ import path5 from "path";
311
+ import fs4 from "fs-extra";
312
+ async function scaffoldVault(targetDir, vaultName, options) {
313
+ const vaultDir = path5.join(targetDir, vaultName);
314
+ if (options.dryRun) {
315
+ if (!await fileExists(vaultDir)) {
316
+ log.dryRun(`Would create vault: ${vaultName}/`);
317
+ }
318
+ } else {
319
+ await fs4.ensureDir(vaultDir);
320
+ }
321
+ for (const folder of VAULT_FOLDERS) {
322
+ const folderPath = path5.join(vaultDir, folder);
323
+ const gitkeepPath = path5.join(folderPath, ".gitkeep");
324
+ if (options.dryRun) {
325
+ if (!await fileExists(folderPath)) {
326
+ log.dryRun(`Would create: ${vaultName}/${folder}/`);
327
+ }
328
+ } else {
329
+ await fs4.ensureDir(folderPath);
330
+ if (!await fileExists(gitkeepPath)) {
331
+ await safeWriteFile(gitkeepPath, "");
332
+ }
333
+ }
334
+ }
335
+ const vaultTemplatesDir = getVaultTemplatesDir();
336
+ const readmeSrc = path5.join(vaultTemplatesDir, "README.md");
337
+ const readmeDest = path5.join(vaultDir, "README.md");
338
+ if (!await fileExists(readmeDest)) {
339
+ if (options.dryRun) {
340
+ log.dryRun(`Would create: ${vaultName}/README.md`);
341
+ } else {
342
+ const content = await fs4.readFile(readmeSrc, "utf-8");
343
+ await safeWriteFile(readmeDest, content);
344
+ log.success(`Created ${vaultName}/README.md`);
345
+ }
346
+ }
347
+ const indexSrc = path5.join(vaultTemplatesDir, "_index.md");
348
+ const indexDest = path5.join(vaultDir, "_index.md");
349
+ if (!await fileExists(indexDest)) {
350
+ if (options.dryRun) {
351
+ log.dryRun(`Would create: ${vaultName}/_index.md`);
352
+ } else {
353
+ const content = await fs4.readFile(indexSrc, "utf-8");
354
+ await safeWriteFile(indexDest, content);
355
+ log.success(`Created ${vaultName}/_index.md`);
356
+ }
357
+ }
358
+ }
359
+
360
+ // src/services/claude-md.ts
361
+ import path6 from "path";
362
+ import fs5 from "fs-extra";
363
+ async function generateClaudeMdContent(profile, vaultName) {
364
+ const templateDir = getClaudeMdTemplatesDir();
365
+ const templatePath = path6.join(templateDir, profile.claudeMdTemplate);
366
+ const template = await fs5.readFile(templatePath, "utf-8");
367
+ const folderList = VAULT_FOLDERS.map(
368
+ (f) => `- \`${vaultName}/${f}/\``
369
+ ).join("\n");
370
+ const skillInstructions = profile.skills.length > 0 ? `Available skills:
371
+ ${profile.skills.map((s) => `- \`${s}\` \u2014 see \`.claude/skills/${s}\` for usage`).join("\n")}` : "No skills installed for this profile.";
372
+ const namingConvention = [
373
+ "- Use lowercase with hyphens: `my-feature-spec.md`",
374
+ "- Prefix with date when chronology matters: `2026-02-07-auth-decision.md`",
375
+ "- Keep filenames descriptive and specific",
376
+ "- One topic per file"
377
+ ].join("\n");
378
+ const content = template.replace(/\{\{vaultPath\}\}/g, vaultName).replace(/\{\{folderList\}\}/g, folderList).replace(/\{\{skillInstructions\}\}/g, skillInstructions).replace(/\{\{namingConvention\}\}/g, namingConvention);
379
+ return content;
380
+ }
381
+ async function writeClaudeExampleMd(targetDir, content, options) {
382
+ const examplePath = path6.join(targetDir, CLAUDE_EXAMPLE_FILE);
383
+ if (options.dryRun) {
384
+ log.dryRun(`Would write ${CLAUDE_EXAMPLE_FILE}`);
385
+ return;
386
+ }
387
+ await safeWriteFile(examplePath, content);
388
+ log.success(`Wrote ${CLAUDE_EXAMPLE_FILE}`);
389
+ }
390
+
391
+ // src/services/config.ts
392
+ import path7 from "path";
393
+ function createConfig(profile, vaultPath) {
394
+ const now = (/* @__PURE__ */ new Date()).toISOString();
395
+ return {
396
+ version: VERSION,
397
+ profile,
398
+ vaultPath,
399
+ createdAt: now,
400
+ updatedAt: now
401
+ };
402
+ }
403
+ async function writeConfig(targetDir, config, options) {
404
+ const configPath = path7.join(targetDir, CLAUDE_DIR, CONFIG_FILE);
405
+ if (options.dryRun) {
406
+ log.dryRun(`Would write ${CLAUDE_DIR}/${CONFIG_FILE}`);
407
+ return;
408
+ }
409
+ const existing = await readConfig(targetDir);
410
+ if (existing) {
411
+ config.createdAt = existing.createdAt;
412
+ }
413
+ await safeWriteFile(configPath, JSON.stringify(config, null, 2) + "\n");
414
+ log.success(`Wrote ${CLAUDE_DIR}/${CONFIG_FILE}`);
415
+ }
416
+ async function readConfig(targetDir) {
417
+ const configPath = path7.join(targetDir, CLAUDE_DIR, CONFIG_FILE);
418
+ const content = await safeReadFile(configPath);
419
+ if (!content) return null;
420
+ try {
421
+ return JSON.parse(content);
422
+ } catch {
423
+ return null;
424
+ }
425
+ }
426
+
427
+ // src/services/active-profile.ts
428
+ import path8 from "path";
429
+ async function writeActiveProfile(targetDir, profile, managedFiles, options) {
430
+ const activePath = path8.join(
431
+ targetDir,
432
+ CLAUDE_DIR,
433
+ "profiles",
434
+ ACTIVE_PROFILE_FILE
435
+ );
436
+ const activeProfile = {
437
+ profile,
438
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
439
+ managedFiles
440
+ };
441
+ if (options.dryRun) {
442
+ log.dryRun(
443
+ `Would write ${CLAUDE_DIR}/profiles/${ACTIVE_PROFILE_FILE}`
444
+ );
445
+ return;
446
+ }
447
+ await safeWriteFile(
448
+ activePath,
449
+ JSON.stringify(activeProfile, null, 2) + "\n"
450
+ );
451
+ log.success(`Wrote ${CLAUDE_DIR}/profiles/${ACTIVE_PROFILE_FILE}`);
452
+ }
453
+ async function readActiveProfile(targetDir) {
454
+ const activePath = path8.join(
455
+ targetDir,
456
+ CLAUDE_DIR,
457
+ "profiles",
458
+ ACTIVE_PROFILE_FILE
459
+ );
460
+ const content = await safeReadFile(activePath);
461
+ if (!content) return null;
462
+ try {
463
+ return JSON.parse(content);
464
+ } catch {
465
+ return null;
466
+ }
467
+ }
468
+
469
+ // src/actions/init.ts
470
+ async function initAction(opts) {
471
+ const targetDir = path9.resolve(opts.dir);
472
+ if (opts.dryRun) {
473
+ log.info("Dry run mode \u2014 no changes will be made.\n");
474
+ }
475
+ const answers = await runInitPrompts(opts);
476
+ const profileName = answers.profile;
477
+ const vaultName = answers.vault;
478
+ const spinner = ora("Loading profile...").start();
479
+ let profile;
480
+ try {
481
+ profile = await loadProfile(profileName);
482
+ spinner.succeed(`Profile: ${profile.name} \u2014 ${profile.description}`);
483
+ } catch (err) {
484
+ spinner.fail("Failed to load profile");
485
+ log.error(err.message);
486
+ process.exit(1);
487
+ }
488
+ const existingActive = await readActiveProfile(targetDir);
489
+ log.step("Scaffolding .claude/ directory...");
490
+ const { managedFiles } = await scaffoldClaudeDir(targetDir, profile, {
491
+ force: opts.force,
492
+ dryRun: opts.dryRun
493
+ }, existingActive);
494
+ log.step(`Scaffolding vault: ${vaultName}/...`);
495
+ await scaffoldVault(targetDir, vaultName, { dryRun: opts.dryRun });
496
+ log.step(`Generating ${CLAUDE_EXAMPLE_FILE}...`);
497
+ const claudeMdContent = await generateClaudeMdContent(profile, vaultName);
498
+ await writeClaudeExampleMd(targetDir, claudeMdContent, {
499
+ dryRun: opts.dryRun
500
+ });
501
+ const config = createConfig(profileName, vaultName);
502
+ await writeConfig(targetDir, config, { dryRun: opts.dryRun });
503
+ await writeActiveProfile(targetDir, profileName, managedFiles, {
504
+ dryRun: opts.dryRun
505
+ });
506
+ console.log("");
507
+ log.success(`Workspace initialized with profile "${profileName}"!`);
508
+ log.info(` Vault: ${vaultName}/`);
509
+ log.info(` Skills: ${profile.skills.length} installed`);
510
+ log.info(` Templates: ${profile.templates.length} installed`);
511
+ const hasClaudeMd = await fileExists(path9.join(targetDir, CLAUDE_MD_FILE));
512
+ console.log("");
513
+ if (hasClaudeMd) {
514
+ log.info(` ${CLAUDE_EXAMPLE_FILE} has been written.`);
515
+ log.info(` You already have a ${CLAUDE_MD_FILE} \u2014 merge the example content into it as needed.`);
516
+ } else {
517
+ log.info(` ${CLAUDE_EXAMPLE_FILE} has been written.`);
518
+ log.info(` Rename it to ${CLAUDE_MD_FILE} to activate: mv ${CLAUDE_EXAMPLE_FILE} ${CLAUDE_MD_FILE}`);
519
+ }
520
+ console.log("");
521
+ log.info("Run 'cws doctor' to verify your workspace.");
522
+ }
523
+
524
+ // src/commands/init.ts
525
+ function createInitCommand() {
526
+ return new Command("init").description("Scaffold a Claude workspace into a project directory").option("-d, --dir <path>", "Target directory", process.cwd()).option(
527
+ `-p, --profile <name>`,
528
+ `Profile to use (${PROFILES.join(", ")})`,
529
+ DEFAULT_PROFILE
530
+ ).option("--vault <name>", "Vault folder name", DEFAULT_VAULT_NAME).option(
531
+ "--force",
532
+ "Overwrite user-modified managed files (with backup)",
533
+ false
534
+ ).option("--dry-run", "Show what would be done without making changes", false).option("-y, --yes", "Skip interactive prompts, use defaults", false).action(async (opts) => {
535
+ await initAction({
536
+ dir: opts.dir,
537
+ profile: opts.profile,
538
+ vault: opts.vault,
539
+ force: opts.force,
540
+ dryRun: opts.dryRun,
541
+ yes: opts.yes
542
+ });
543
+ });
544
+ }
545
+
546
+ // src/commands/doctor.ts
547
+ import { Command as Command2 } from "commander";
548
+
549
+ // src/actions/doctor.ts
550
+ import path10 from "path";
551
+ async function doctorAction(opts) {
552
+ const targetDir = path10.resolve(opts.dir);
553
+ const checks = [];
554
+ log.info(`Checking workspace: ${targetDir}
555
+ `);
556
+ const claudeDir = path10.join(targetDir, CLAUDE_DIR);
557
+ const claudeDirExists = await fileExists(claudeDir);
558
+ checks.push({
559
+ name: ".claude/ directory",
560
+ passed: claudeDirExists,
561
+ message: claudeDirExists ? ".claude/ directory exists" : ".claude/ directory not found",
562
+ fix: "Run 'cws init' to create the workspace"
563
+ });
564
+ const config = await readConfig(targetDir);
565
+ checks.push({
566
+ name: "config.json",
567
+ passed: config !== null,
568
+ message: config ? `config.json valid (profile: ${config.profile})` : "config.json missing or invalid",
569
+ fix: "Run 'cws init' to regenerate config"
570
+ });
571
+ const active = await readActiveProfile(targetDir);
572
+ checks.push({
573
+ name: "active.json",
574
+ passed: active !== null,
575
+ message: active ? `active.json present (profile: ${active.profile})` : "active.json missing",
576
+ fix: "Run 'cws init' to regenerate active profile"
577
+ });
578
+ if (config && active) {
579
+ try {
580
+ const profile = await loadProfile(config.profile);
581
+ let allSkillsPresent = true;
582
+ for (const skill of profile.skills) {
583
+ const skillPath = path10.join(claudeDir, "skills", skill, "SKILL.md");
584
+ if (!await fileExists(skillPath)) {
585
+ allSkillsPresent = false;
586
+ checks.push({
587
+ name: `skill: ${skill}`,
588
+ passed: false,
589
+ message: `Missing skill: .claude/skills/${skill}/SKILL.md`,
590
+ fix: `Run 'cws init --force' to restore missing files`
591
+ });
592
+ }
593
+ }
594
+ if (allSkillsPresent) {
595
+ checks.push({
596
+ name: "skill files",
597
+ passed: true,
598
+ message: `All ${profile.skills.length} skills present`
599
+ });
600
+ }
601
+ let allTemplatesPresent = true;
602
+ for (const tmpl of profile.templates) {
603
+ const tmplPath = path10.join(claudeDir, "templates", tmpl);
604
+ if (!await fileExists(tmplPath)) {
605
+ allTemplatesPresent = false;
606
+ checks.push({
607
+ name: `template: ${tmpl}`,
608
+ passed: false,
609
+ message: `Missing template file: .claude/templates/${tmpl}`,
610
+ fix: `Run 'cws init --force' to restore missing files`
611
+ });
612
+ }
613
+ }
614
+ if (allTemplatesPresent) {
615
+ checks.push({
616
+ name: "template files",
617
+ passed: true,
618
+ message: `All ${profile.templates.length} template files present`
619
+ });
620
+ }
621
+ } catch {
622
+ checks.push({
623
+ name: "profile validation",
624
+ passed: false,
625
+ message: `Could not load profile "${config.profile}"`,
626
+ fix: "Check config.json profile name is valid"
627
+ });
628
+ }
629
+ }
630
+ if (config) {
631
+ const vaultDir = path10.join(targetDir, config.vaultPath);
632
+ const vaultExists = await fileExists(vaultDir);
633
+ checks.push({
634
+ name: "vault directory",
635
+ passed: vaultExists,
636
+ message: vaultExists ? `Vault directory exists: ${config.vaultPath}/` : `Vault directory missing: ${config.vaultPath}/`,
637
+ fix: "Run 'cws init' to create vault"
638
+ });
639
+ if (vaultExists) {
640
+ let allFoldersPresent = true;
641
+ for (const folder of VAULT_FOLDERS) {
642
+ const folderPath = path10.join(vaultDir, folder);
643
+ if (!await fileExists(folderPath)) {
644
+ allFoldersPresent = false;
645
+ checks.push({
646
+ name: `vault: ${folder}`,
647
+ passed: false,
648
+ message: `Missing vault folder: ${config.vaultPath}/${folder}/`,
649
+ fix: "Run 'cws init' to restore vault structure"
650
+ });
651
+ }
652
+ }
653
+ if (allFoldersPresent) {
654
+ checks.push({
655
+ name: "vault subfolders",
656
+ passed: true,
657
+ message: `All ${VAULT_FOLDERS.length} vault subfolders present`
658
+ });
659
+ }
660
+ }
661
+ }
662
+ const examplePath = path10.join(targetDir, CLAUDE_EXAMPLE_FILE);
663
+ const claudeMdPath = path10.join(targetDir, CLAUDE_MD_FILE);
664
+ const exampleExists = await fileExists(examplePath);
665
+ const claudeMdExists = await fileExists(claudeMdPath);
666
+ if (claudeMdExists) {
667
+ checks.push({
668
+ name: CLAUDE_MD_FILE,
669
+ passed: true,
670
+ message: `${CLAUDE_MD_FILE} present`
671
+ });
672
+ } else if (exampleExists) {
673
+ checks.push({
674
+ name: CLAUDE_MD_FILE,
675
+ passed: true,
676
+ message: `${CLAUDE_EXAMPLE_FILE} present \u2014 rename to ${CLAUDE_MD_FILE} to activate`,
677
+ fix: `mv ${CLAUDE_EXAMPLE_FILE} ${CLAUDE_MD_FILE}`
678
+ });
679
+ } else {
680
+ checks.push({
681
+ name: CLAUDE_MD_FILE,
682
+ passed: false,
683
+ message: `Neither ${CLAUDE_MD_FILE} nor ${CLAUDE_EXAMPLE_FILE} found`,
684
+ fix: "Run 'cws init' to generate CLAUDE.example.md"
685
+ });
686
+ }
687
+ console.log("");
688
+ let failCount = 0;
689
+ for (const check of checks) {
690
+ if (check.passed) {
691
+ log.pass(check.message);
692
+ } else {
693
+ log.fail(check.message);
694
+ if (check.fix) {
695
+ log.plain(` ${check.fix}`);
696
+ }
697
+ failCount++;
698
+ }
699
+ }
700
+ console.log("");
701
+ if (failCount === 0) {
702
+ log.success("All checks passed!");
703
+ } else {
704
+ log.warn(`${failCount} check(s) failed.`);
705
+ process.exitCode = 1;
706
+ }
707
+ }
708
+
709
+ // src/commands/doctor.ts
710
+ function createDoctorCommand() {
711
+ return new Command2("doctor").description("Validate Claude workspace configuration and files").option("-d, --dir <path>", "Target directory", process.cwd()).action(async (opts) => {
712
+ await doctorAction({ dir: opts.dir });
713
+ });
714
+ }
715
+
716
+ // src/commands/print-claude-md.ts
717
+ import { Command as Command3 } from "commander";
718
+
719
+ // src/actions/print-claude-md.ts
720
+ async function printClaudeMdAction(opts) {
721
+ try {
722
+ const profile = await loadProfile(opts.profile);
723
+ const content = await generateClaudeMdContent(profile, opts.vault);
724
+ process.stdout.write(content);
725
+ } catch (err) {
726
+ log.error(err.message);
727
+ process.exit(1);
728
+ }
729
+ }
730
+
731
+ // src/commands/print-claude-md.ts
732
+ function createPrintClaudeMdCommand() {
733
+ return new Command3("print-claude-md").description("Print the generated CLAUDE.md content to stdout").option(
734
+ `-p, --profile <name>`,
735
+ `Profile to use (${PROFILES.join(", ")})`,
736
+ DEFAULT_PROFILE
737
+ ).option("--vault <name>", "Vault folder name", DEFAULT_VAULT_NAME).action(async (opts) => {
738
+ await printClaudeMdAction({
739
+ profile: opts.profile,
740
+ vault: opts.vault
741
+ });
742
+ });
743
+ }
744
+
745
+ // src/commands/add-skill.ts
746
+ import { Command as Command4 } from "commander";
747
+
748
+ // src/actions/add-skill.ts
749
+ import path12 from "path";
750
+ import fs7 from "fs-extra";
751
+
752
+ // src/services/skills.ts
753
+ import path11 from "path";
754
+ import fs6 from "fs-extra";
755
+ function parseFrontmatter(content) {
756
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
757
+ if (!match) {
758
+ return { name: "", description: "" };
759
+ }
760
+ const frontmatter = match[1];
761
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
762
+ const descMatch = frontmatter.match(/^description:\s*"?(.+?)"?\s*$/m);
763
+ return {
764
+ name: nameMatch ? nameMatch[1].trim() : "",
765
+ description: descMatch ? descMatch[1].trim() : ""
766
+ };
767
+ }
768
+ async function getAvailableSkillNames() {
769
+ const skillsDir = getSkillsDir();
770
+ const entries = await fs6.readdir(skillsDir, { withFileTypes: true });
771
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
772
+ }
773
+ async function validateSkillName(name) {
774
+ const skillPath = path11.join(getSkillsDir(), name, "SKILL.md");
775
+ try {
776
+ await fs6.access(skillPath);
777
+ return true;
778
+ } catch {
779
+ return false;
780
+ }
781
+ }
782
+ async function listAvailableSkills() {
783
+ const skillsDir = getSkillsDir();
784
+ const names = await getAvailableSkillNames();
785
+ const skills = [];
786
+ for (const name of names) {
787
+ const content = await fs6.readFile(
788
+ path11.join(skillsDir, name, "SKILL.md"),
789
+ "utf-8"
790
+ );
791
+ const fm = parseFrontmatter(content);
792
+ skills.push({
793
+ name,
794
+ description: fm.description || "(no description)"
795
+ });
796
+ }
797
+ return skills;
798
+ }
799
+
800
+ // src/actions/add-skill.ts
801
+ async function addSkillAction(skillName, opts) {
802
+ if (opts.list) {
803
+ const skills = await listAvailableSkills();
804
+ log.info(`Available skills (${skills.length}):
805
+ `);
806
+ const maxName = Math.max(...skills.map((s) => s.name.length));
807
+ for (const skill of skills) {
808
+ log.plain(` ${skill.name.padEnd(maxName + 2)}${skill.description}`);
809
+ }
810
+ return;
811
+ }
812
+ if (!skillName) {
813
+ log.error("Please specify a skill name. Use --list to see available skills.");
814
+ process.exit(1);
815
+ }
816
+ const targetDir = path12.resolve(opts.dir);
817
+ if (opts.dryRun) {
818
+ log.info("Dry run mode \u2014 no changes will be made.\n");
819
+ }
820
+ const isValid = await validateSkillName(skillName);
821
+ if (!isValid) {
822
+ log.error(
823
+ `Unknown skill "${skillName}". Use --list to see available skills.`
824
+ );
825
+ process.exit(1);
826
+ }
827
+ const claudeDir = path12.join(targetDir, CLAUDE_DIR);
828
+ if (!opts.dryRun) {
829
+ await fs7.ensureDir(path12.join(claudeDir, "skills"));
830
+ await fs7.ensureDir(path12.join(claudeDir, "profiles"));
831
+ }
832
+ const existingActive = await readActiveProfile(targetDir);
833
+ const existingHashes = buildExistingHashes(existingActive);
834
+ const result = await installSingleSkill(
835
+ targetDir,
836
+ skillName,
837
+ existingHashes,
838
+ { force: opts.force, dryRun: opts.dryRun }
839
+ );
840
+ if (!result) {
841
+ return;
842
+ }
843
+ if (!opts.dryRun) {
844
+ const existingFiles = existingActive?.managedFiles ?? [];
845
+ const merged = existingFiles.filter((mf) => mf.path !== result.path);
846
+ merged.push(result);
847
+ await writeActiveProfile(
848
+ targetDir,
849
+ existingActive?.profile ?? "custom",
850
+ merged,
851
+ { dryRun: false }
852
+ );
853
+ }
854
+ console.log("");
855
+ log.success(`Skill "${skillName}" installed!`);
856
+ }
857
+
858
+ // src/commands/add-skill.ts
859
+ function createAddSkillCommand() {
860
+ return new Command4("add-skill").description("Install a single skill into the workspace").argument("[skill-name]", "Name of the skill to install").option("-d, --dir <path>", "Target directory", process.cwd()).option(
861
+ "--force",
862
+ "Overwrite existing skill (with backup)",
863
+ false
864
+ ).option("--dry-run", "Show what would be done without making changes", false).option("-l, --list", "List all available skills", false).action(async (skillName, opts) => {
865
+ await addSkillAction(skillName, {
866
+ dir: opts.dir,
867
+ force: opts.force,
868
+ dryRun: opts.dryRun,
869
+ list: opts.list
870
+ });
871
+ });
872
+ }
873
+
874
+ // src/cli.ts
875
+ function createProgram() {
876
+ const program2 = new Command5();
877
+ program2.name("cws").description("Dotfiles for Claude \u2014 scaffold Claude workspaces into any project").version(VERSION);
878
+ program2.addCommand(createInitCommand());
879
+ program2.addCommand(createDoctorCommand());
880
+ program2.addCommand(createPrintClaudeMdCommand());
881
+ program2.addCommand(createAddSkillCommand());
882
+ return program2;
883
+ }
884
+
885
+ // src/bin.ts
886
+ var program = createProgram();
887
+ program.parse();
888
+ //# sourceMappingURL=bin.js.map