youmd 0.4.9 → 0.5.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.
Files changed (78) hide show
  1. package/dist/commands/build.d.ts.map +1 -1
  2. package/dist/commands/build.js +27 -5
  3. package/dist/commands/build.js.map +1 -1
  4. package/dist/commands/chat.d.ts.map +1 -1
  5. package/dist/commands/chat.js +47 -7
  6. package/dist/commands/chat.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +30 -0
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/link.js +1 -1
  11. package/dist/commands/link.js.map +1 -1
  12. package/dist/commands/login.js +9 -0
  13. package/dist/commands/login.js.map +1 -1
  14. package/dist/commands/preview.js +1 -1
  15. package/dist/commands/preview.js.map +1 -1
  16. package/dist/commands/publish.d.ts.map +1 -1
  17. package/dist/commands/publish.js +66 -4
  18. package/dist/commands/publish.js.map +1 -1
  19. package/dist/commands/pull.d.ts.map +1 -1
  20. package/dist/commands/pull.js +68 -4
  21. package/dist/commands/pull.js.map +1 -1
  22. package/dist/commands/push.d.ts +1 -0
  23. package/dist/commands/push.d.ts.map +1 -1
  24. package/dist/commands/push.js +92 -2
  25. package/dist/commands/push.js.map +1 -1
  26. package/dist/commands/register.js +1 -1
  27. package/dist/commands/register.js.map +1 -1
  28. package/dist/commands/skill.d.ts +8 -0
  29. package/dist/commands/skill.d.ts.map +1 -0
  30. package/dist/commands/skill.js +1150 -0
  31. package/dist/commands/skill.js.map +1 -0
  32. package/dist/commands/status.d.ts.map +1 -1
  33. package/dist/commands/status.js +118 -2
  34. package/dist/commands/status.js.map +1 -1
  35. package/dist/commands/sync.d.ts.map +1 -1
  36. package/dist/commands/sync.js +12 -0
  37. package/dist/commands/sync.js.map +1 -1
  38. package/dist/commands/whoami.d.ts.map +1 -1
  39. package/dist/commands/whoami.js +16 -11
  40. package/dist/commands/whoami.js.map +1 -1
  41. package/dist/index.js +12 -3
  42. package/dist/index.js.map +1 -1
  43. package/dist/lib/api.d.ts +76 -5
  44. package/dist/lib/api.d.ts.map +1 -1
  45. package/dist/lib/api.js +47 -2
  46. package/dist/lib/api.js.map +1 -1
  47. package/dist/lib/ascii.d.ts.map +1 -1
  48. package/dist/lib/ascii.js +20 -48
  49. package/dist/lib/ascii.js.map +1 -1
  50. package/dist/lib/config.d.ts +24 -0
  51. package/dist/lib/config.d.ts.map +1 -1
  52. package/dist/lib/config.js +40 -0
  53. package/dist/lib/config.js.map +1 -1
  54. package/dist/lib/hash.d.ts +3 -0
  55. package/dist/lib/hash.d.ts.map +1 -0
  56. package/dist/lib/hash.js +31 -0
  57. package/dist/lib/hash.js.map +1 -0
  58. package/dist/lib/onboarding.d.ts +1 -1
  59. package/dist/lib/onboarding.d.ts.map +1 -1
  60. package/dist/lib/onboarding.js +113 -57
  61. package/dist/lib/onboarding.js.map +1 -1
  62. package/dist/lib/skill-catalog.d.ts +57 -0
  63. package/dist/lib/skill-catalog.d.ts.map +1 -0
  64. package/dist/lib/skill-catalog.js +196 -0
  65. package/dist/lib/skill-catalog.js.map +1 -0
  66. package/dist/lib/skill-renderer.d.ts +55 -0
  67. package/dist/lib/skill-renderer.d.ts.map +1 -0
  68. package/dist/lib/skill-renderer.js +364 -0
  69. package/dist/lib/skill-renderer.js.map +1 -0
  70. package/dist/lib/skills.d.ts +125 -0
  71. package/dist/lib/skills.d.ts.map +1 -0
  72. package/dist/lib/skills.js +677 -0
  73. package/dist/lib/skills.js.map +1 -0
  74. package/package.json +7 -4
  75. package/skills/claude-md-generator.md +66 -0
  76. package/skills/meta-improve.md +57 -0
  77. package/skills/project-context-init.md +51 -0
  78. package/skills/voice-sync.md +41 -0
@@ -0,0 +1,677 @@
1
+ "use strict";
2
+ /**
3
+ * Skill engine — install, remove, resolve, sync, link.
4
+ *
5
+ * Manages the lifecycle of skills from the catalog to the filesystem.
6
+ * Skills are SKILL.md files with identity-aware template variables.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.getSkillDir = getSkillDir;
46
+ exports.getSkillPath = getSkillPath;
47
+ exports.readSkillFile = readSkillFile;
48
+ exports.resolveSkillSource = resolveSkillSource;
49
+ exports.resolveSkillSourceAsync = resolveSkillSourceAsync;
50
+ exports.installSkill = installSkill;
51
+ exports.installSkillAsync = installSkillAsync;
52
+ exports.removeSkill = removeSkill;
53
+ exports.useSkill = useSkill;
54
+ exports.syncAllSkills = syncAllSkills;
55
+ exports.syncAffectedSkills = syncAffectedSkills;
56
+ exports.linkToAgent = linkToAgent;
57
+ exports.initProject = initProject;
58
+ exports.trackSkillEvent = trackSkillEvent;
59
+ exports.getMetrics = getMetrics;
60
+ const fs = __importStar(require("fs"));
61
+ const path = __importStar(require("path"));
62
+ const gray_matter_1 = __importDefault(require("gray-matter"));
63
+ const config_1 = require("./config");
64
+ const skill_catalog_1 = require("./skill-catalog");
65
+ const skill_renderer_1 = require("./skill-renderer");
66
+ const config_2 = require("./config");
67
+ const api_1 = require("./api");
68
+ // ─── Skill File I/O ───────────────────────────────────────────────────
69
+ /**
70
+ * Get the directory for an installed skill.
71
+ */
72
+ function getSkillDir(skillName) {
73
+ return path.join((0, config_1.getSkillsDir)(), skillName);
74
+ }
75
+ /**
76
+ * Get the SKILL.md path for an installed skill.
77
+ */
78
+ function getSkillPath(skillName) {
79
+ return path.join(getSkillDir(skillName), "SKILL.md");
80
+ }
81
+ /**
82
+ * Read a skill's SKILL.md content and frontmatter.
83
+ */
84
+ function readSkillFile(skillName) {
85
+ const skillPath = getSkillPath(skillName);
86
+ if (!fs.existsSync(skillPath))
87
+ return null;
88
+ try {
89
+ const raw = fs.readFileSync(skillPath, "utf-8");
90
+ const { data, content } = (0, gray_matter_1.default)(raw);
91
+ return { content: content.trim(), metadata: data };
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
97
+ // ─── Source Resolution ────────────────────────────────────────────────
98
+ /**
99
+ * Resolve a skill source to its raw markdown content.
100
+ *
101
+ * Supported sources:
102
+ * bundled:path/to/file.md — relative to repo root
103
+ * local:/absolute/path.md — local filesystem
104
+ * github:owner/repo/path — (future) GitHub raw content
105
+ */
106
+ function resolveSkillSource(source) {
107
+ if (source.startsWith("bundled:")) {
108
+ const relativePath = source.slice("bundled:".length);
109
+ const filename = path.basename(relativePath);
110
+ // Priority 1: cli/skills/ directory (npm package ships these)
111
+ const pkgRoot = path.resolve(__dirname, "..", "..");
112
+ const pkgSkillsPath = path.join(pkgRoot, "skills", filename);
113
+ if (fs.existsSync(pkgSkillsPath)) {
114
+ return fs.readFileSync(pkgSkillsPath, "utf-8");
115
+ }
116
+ // Priority 2: repo root (development mode)
117
+ const repoRoot = path.resolve(__dirname, "..", "..", "..");
118
+ const repoPath = path.join(repoRoot, relativePath);
119
+ if (fs.existsSync(repoPath)) {
120
+ return fs.readFileSync(repoPath, "utf-8");
121
+ }
122
+ // Priority 3: relative to package root
123
+ const altPath = path.join(pkgRoot, relativePath);
124
+ if (fs.existsSync(altPath)) {
125
+ return fs.readFileSync(altPath, "utf-8");
126
+ }
127
+ return null;
128
+ }
129
+ if (source.startsWith("local:")) {
130
+ const filePath = source.slice("local:".length);
131
+ if (fs.existsSync(filePath)) {
132
+ return fs.readFileSync(filePath, "utf-8");
133
+ }
134
+ return null;
135
+ }
136
+ // Plain file path
137
+ if (fs.existsSync(source)) {
138
+ return fs.readFileSync(source, "utf-8");
139
+ }
140
+ return null;
141
+ }
142
+ /**
143
+ * Async source resolver — handles remote sources (GitHub).
144
+ *
145
+ * github:owner/repo/path → https://raw.githubusercontent.com/owner/repo/main/path
146
+ * Also supports full https:// URLs to raw markdown files.
147
+ */
148
+ async function resolveSkillSourceAsync(source) {
149
+ // Try sync first (bundled, local, file path)
150
+ const syncResult = resolveSkillSource(source);
151
+ if (syncResult)
152
+ return syncResult;
153
+ // GitHub: github:owner/repo/path/to/file.md
154
+ if (source.startsWith("github:")) {
155
+ const ghPath = source.slice("github:".length);
156
+ const parts = ghPath.split("/");
157
+ if (parts.length < 3)
158
+ return null;
159
+ const owner = parts[0];
160
+ const repo = parts[1];
161
+ const filePath = parts.slice(2).join("/");
162
+ // Try main, then master
163
+ for (const branch of ["main", "master"]) {
164
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}`;
165
+ try {
166
+ const res = await fetch(url);
167
+ if (res.ok) {
168
+ return await res.text();
169
+ }
170
+ }
171
+ catch {
172
+ // try next branch
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+ // Direct HTTPS URL to raw markdown
178
+ if (source.startsWith("https://") || source.startsWith("http://")) {
179
+ try {
180
+ const res = await fetch(source);
181
+ if (res.ok) {
182
+ return await res.text();
183
+ }
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ }
189
+ return null;
190
+ }
191
+ // ─── Install / Remove ─────────────────────────────────────────────────
192
+ /**
193
+ * Install a skill from its source to ~/.youmd/skills/<name>/SKILL.md.
194
+ * Uses sync resolution for local/bundled, falls back to async for remote.
195
+ */
196
+ function installSkill(skillName) {
197
+ const catalog = (0, skill_catalog_1.readSkillCatalog)();
198
+ const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
199
+ if (!entry) {
200
+ return { ok: false, error: `skill "${skillName}" not found in catalog` };
201
+ }
202
+ // Resolve source (sync only — async handled by installSkillAsync)
203
+ const content = resolveSkillSource(entry.source);
204
+ if (!content) {
205
+ // If source is remote, caller should use installSkillAsync instead
206
+ if (entry.source.startsWith("github:") || entry.source.startsWith("https://")) {
207
+ return { ok: false, error: `remote source — use installSkillAsync for: ${entry.source}` };
208
+ }
209
+ return { ok: false, error: `could not resolve source: ${entry.source}` };
210
+ }
211
+ // Create skill directory and write SKILL.md
212
+ const skillDir = getSkillDir(entry.name);
213
+ fs.mkdirSync(skillDir, { recursive: true });
214
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), content);
215
+ // Render an interpolated version alongside the raw one
216
+ const identity = (0, skill_renderer_1.loadIdentityData)();
217
+ const rendered = (0, skill_renderer_1.renderSkillTemplate)(content, identity);
218
+ fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
219
+ // Mark installed in catalog
220
+ (0, skill_catalog_1.setSkillInstalled)(catalog, entry.name, true);
221
+ // Track metrics
222
+ trackSkillEvent(entry.name, "install");
223
+ // Sync to Convex (non-blocking)
224
+ syncInstallToRemote(entry);
225
+ return { ok: true };
226
+ }
227
+ /**
228
+ * Async install — handles remote sources (GitHub, HTTPS URLs).
229
+ */
230
+ async function installSkillAsync(skillName) {
231
+ const catalog = (0, skill_catalog_1.readSkillCatalog)();
232
+ const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
233
+ if (!entry) {
234
+ return { ok: false, error: `skill "${skillName}" not found in catalog` };
235
+ }
236
+ // Try sync first
237
+ let content = resolveSkillSource(entry.source);
238
+ // Fall back to async
239
+ if (!content) {
240
+ content = await resolveSkillSourceAsync(entry.source);
241
+ }
242
+ if (!content) {
243
+ return { ok: false, error: `could not resolve source: ${entry.source}` };
244
+ }
245
+ const skillDir = getSkillDir(entry.name);
246
+ fs.mkdirSync(skillDir, { recursive: true });
247
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), content);
248
+ const identity = (0, skill_renderer_1.loadIdentityData)();
249
+ const rendered = (0, skill_renderer_1.renderSkillTemplate)(content, identity);
250
+ fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
251
+ (0, skill_catalog_1.setSkillInstalled)(catalog, entry.name, true);
252
+ trackSkillEvent(entry.name, "install");
253
+ syncInstallToRemote(entry);
254
+ return { ok: true };
255
+ }
256
+ /**
257
+ * Remove an installed skill.
258
+ */
259
+ function removeSkill(skillName) {
260
+ const catalog = (0, skill_catalog_1.readSkillCatalog)();
261
+ const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
262
+ if (!entry) {
263
+ return { ok: false, error: `skill "${skillName}" not found in catalog` };
264
+ }
265
+ const skillDir = getSkillDir(entry.name);
266
+ if (fs.existsSync(skillDir)) {
267
+ fs.rmSync(skillDir, { recursive: true, force: true });
268
+ }
269
+ (0, skill_catalog_1.setSkillInstalled)(catalog, entry.name, false);
270
+ trackSkillEvent(entry.name, "remove");
271
+ // Sync removal to Convex (non-blocking, log failures)
272
+ if ((0, config_2.isAuthenticated)()) {
273
+ (0, api_1.removeSkillInstall)(entry.name).catch((err) => {
274
+ // Non-fatal: remote sync failed but local removal succeeded
275
+ if (process.env.DEBUG)
276
+ console.error(`[skill sync] remove failed: ${err}`);
277
+ });
278
+ }
279
+ return { ok: true };
280
+ }
281
+ // ─── Use / Render ─────────────────────────────────────────────────────
282
+ /**
283
+ * Use a skill — render it with current identity data.
284
+ * Returns the rendered content.
285
+ */
286
+ function useSkill(skillName) {
287
+ const catalog = (0, skill_catalog_1.readSkillCatalog)();
288
+ const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
289
+ if (!entry) {
290
+ return { ok: false, error: `skill "${skillName}" not found in catalog` };
291
+ }
292
+ // Install if not already
293
+ if (!entry.installed) {
294
+ const installResult = installSkill(skillName);
295
+ if (!installResult.ok)
296
+ return installResult;
297
+ }
298
+ const skillFile = readSkillFile(entry.name);
299
+ if (!skillFile) {
300
+ return { ok: false, error: `could not read SKILL.md for "${skillName}"` };
301
+ }
302
+ const identity = (0, skill_renderer_1.loadIdentityData)();
303
+ const readiness = (0, skill_renderer_1.checkTemplateReadiness)(skillFile.content, identity);
304
+ const rendered = (0, skill_renderer_1.renderSkillTemplate)(skillFile.content, identity);
305
+ // Update rendered version
306
+ const skillDir = getSkillDir(entry.name);
307
+ fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
308
+ trackSkillEvent(entry.name, "use");
309
+ // Sync usage to Convex (non-blocking, log failures)
310
+ if ((0, config_2.isAuthenticated)()) {
311
+ (0, api_1.trackSkillUsage)(entry.name).catch((err) => {
312
+ if (process.env.DEBUG)
313
+ console.error(`[skill sync] usage tracking failed: ${err}`);
314
+ });
315
+ }
316
+ return { ok: true, content: rendered, readiness };
317
+ }
318
+ // ─── Sync ─────────────────────────────────────────────────────────────
319
+ /**
320
+ * Re-interpolate all installed skills against current identity data.
321
+ */
322
+ function syncAllSkills() {
323
+ const catalog = (0, skill_catalog_1.readSkillCatalog)();
324
+ const identity = (0, skill_renderer_1.loadIdentityData)();
325
+ const synced = [];
326
+ const errors = [];
327
+ for (const entry of catalog.skills) {
328
+ if (!entry.installed)
329
+ continue;
330
+ const skillFile = readSkillFile(entry.name);
331
+ if (!skillFile) {
332
+ errors.push(`${entry.name}: SKILL.md not found`);
333
+ continue;
334
+ }
335
+ try {
336
+ const rendered = (0, skill_renderer_1.renderSkillTemplate)(skillFile.content, identity);
337
+ const skillDir = getSkillDir(entry.name);
338
+ fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
339
+ synced.push(entry.name);
340
+ }
341
+ catch (err) {
342
+ errors.push(`${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
343
+ }
344
+ }
345
+ return { synced, errors };
346
+ }
347
+ /**
348
+ * Sync only skills affected by specific identity field changes.
349
+ */
350
+ function syncAffectedSkills(changedFields) {
351
+ const catalog = (0, skill_catalog_1.readSkillCatalog)();
352
+ const identity = (0, skill_renderer_1.loadIdentityData)();
353
+ const synced = [];
354
+ const errors = [];
355
+ const affected = catalog.skills.filter((s) => s.installed && s.identity_fields.some((f) => changedFields.includes(f)));
356
+ for (const entry of affected) {
357
+ const skillFile = readSkillFile(entry.name);
358
+ if (!skillFile) {
359
+ errors.push(`${entry.name}: SKILL.md not found`);
360
+ continue;
361
+ }
362
+ try {
363
+ const rendered = (0, skill_renderer_1.renderSkillTemplate)(skillFile.content, identity);
364
+ const skillDir = getSkillDir(entry.name);
365
+ fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
366
+ synced.push(entry.name);
367
+ }
368
+ catch (err) {
369
+ errors.push(`${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
370
+ }
371
+ }
372
+ return { synced, errors };
373
+ }
374
+ /**
375
+ * Link installed skills to an agent's directory.
376
+ *
377
+ * claude → .claude/skills/youmd/
378
+ * cursor → .cursor/rules/youmd.md (single file concatenation)
379
+ * codex → .codex/skills/youmd/
380
+ */
381
+ function linkToAgent(target) {
382
+ const catalog = (0, skill_catalog_1.readSkillCatalog)();
383
+ const installedSkills = catalog.skills.filter((s) => s.installed);
384
+ if (installedSkills.length === 0) {
385
+ return { ok: false, error: "no skills installed. run: youmd skill install <name>" };
386
+ }
387
+ const cwd = process.cwd();
388
+ switch (target) {
389
+ case "claude": {
390
+ const targetDir = path.join(cwd, ".claude", "skills", "youmd");
391
+ fs.mkdirSync(targetDir, { recursive: true });
392
+ for (const entry of installedSkills) {
393
+ const rendered = getRenderedContent(entry.name);
394
+ if (rendered) {
395
+ fs.writeFileSync(path.join(targetDir, `${entry.name}.md`), rendered);
396
+ }
397
+ }
398
+ return { ok: true, path: targetDir };
399
+ }
400
+ case "cursor": {
401
+ const targetDir = path.join(cwd, ".cursor", "rules");
402
+ fs.mkdirSync(targetDir, { recursive: true });
403
+ // Cursor prefers a single concatenated file
404
+ const parts = [];
405
+ parts.push("# You.md Identity Skills\n");
406
+ parts.push(`> Auto-generated by youmd skill link. Do not edit manually.\n`);
407
+ for (const entry of installedSkills) {
408
+ const rendered = getRenderedContent(entry.name);
409
+ if (rendered) {
410
+ parts.push(`\n---\n\n## ${entry.name}\n\n${rendered}`);
411
+ }
412
+ }
413
+ const outPath = path.join(targetDir, "youmd.md");
414
+ fs.writeFileSync(outPath, parts.join("\n"));
415
+ return { ok: true, path: outPath };
416
+ }
417
+ case "codex": {
418
+ const targetDir = path.join(cwd, ".codex", "skills", "youmd");
419
+ fs.mkdirSync(targetDir, { recursive: true });
420
+ for (const entry of installedSkills) {
421
+ const rendered = getRenderedContent(entry.name);
422
+ if (rendered) {
423
+ fs.writeFileSync(path.join(targetDir, `${entry.name}.md`), rendered);
424
+ }
425
+ }
426
+ return { ok: true, path: targetDir };
427
+ }
428
+ default:
429
+ return { ok: false, error: `unknown agent target: ${target}` };
430
+ }
431
+ }
432
+ function getRenderedContent(skillName) {
433
+ const renderedPath = path.join(getSkillDir(skillName), "RENDERED.md");
434
+ if (fs.existsSync(renderedPath)) {
435
+ return fs.readFileSync(renderedPath, "utf-8");
436
+ }
437
+ // Fallback: render on the fly
438
+ const skillFile = readSkillFile(skillName);
439
+ if (!skillFile)
440
+ return null;
441
+ return (0, skill_renderer_1.renderSkillTemplate)(skillFile.content);
442
+ }
443
+ // ─── Init Project (Compound Command) ─────────────────────────────────
444
+ /**
445
+ * Initialize a project with skills: install core skills, generate CLAUDE.md,
446
+ * scaffold project-context/, and link to .claude/skills/.
447
+ */
448
+ function initProject() {
449
+ const steps = [];
450
+ const project = (0, config_1.detectProjectContext)();
451
+ const cwd = process.cwd();
452
+ // Step 1: Install core skills
453
+ for (const name of ["claude-md-generator", "project-context-init"]) {
454
+ const result = installSkill(name);
455
+ steps.push({
456
+ name: `install ${name}`,
457
+ ok: result.ok,
458
+ detail: result.error,
459
+ });
460
+ }
461
+ // Step 2: Generate or merge CLAUDE.md
462
+ const identity = (0, skill_renderer_1.loadIdentityData)();
463
+ const projectName = project?.name || path.basename(cwd);
464
+ const claudeMdPath = path.join(cwd, "CLAUDE.md");
465
+ if (fs.existsSync(claudeMdPath)) {
466
+ // Merge: append identity section if not already present
467
+ const existing = fs.readFileSync(claudeMdPath, "utf-8");
468
+ const IDENTITY_MARKER = "<!-- youmd:identity -->";
469
+ if (existing.includes(IDENTITY_MARKER)) {
470
+ steps.push({ name: "CLAUDE.md", ok: true, detail: "identity section already present" });
471
+ }
472
+ else {
473
+ const identitySection = generateIdentitySection(identity);
474
+ if (identitySection) {
475
+ fs.writeFileSync(claudeMdPath, existing.trimEnd() + "\n\n" + identitySection);
476
+ steps.push({ name: "CLAUDE.md", ok: true, detail: "identity section appended" });
477
+ }
478
+ else {
479
+ steps.push({ name: "CLAUDE.md", ok: true, detail: "exists, no identity data to add" });
480
+ }
481
+ }
482
+ }
483
+ else {
484
+ const claudeMd = generateClaudeMd(identity, projectName);
485
+ fs.writeFileSync(claudeMdPath, claudeMd);
486
+ steps.push({ name: "CLAUDE.md", ok: true, detail: "generated" });
487
+ }
488
+ // Step 3: Scaffold project-context/
489
+ const pcDir = path.join(cwd, "project-context");
490
+ if (fs.existsSync(pcDir)) {
491
+ steps.push({ name: "project-context/", ok: true, detail: "already exists, skipped" });
492
+ }
493
+ else {
494
+ scaffoldProjectContext(pcDir, identity, project?.name || path.basename(cwd));
495
+ steps.push({ name: "project-context/", ok: true, detail: "scaffolded" });
496
+ }
497
+ // Step 4: Link to .claude/skills/
498
+ const linkResult = linkToAgent("claude");
499
+ steps.push({
500
+ name: "link .claude/skills/youmd/",
501
+ ok: linkResult.ok,
502
+ detail: linkResult.error || linkResult.path,
503
+ });
504
+ // Step 5: Also link to Cursor if .cursor/ exists
505
+ if (fs.existsSync(path.join(cwd, ".cursor"))) {
506
+ const cursorResult = linkToAgent("cursor");
507
+ steps.push({
508
+ name: "link .cursor/rules/youmd.md",
509
+ ok: cursorResult.ok,
510
+ detail: cursorResult.error || cursorResult.path,
511
+ });
512
+ }
513
+ return { ok: steps.every((s) => s.ok), steps };
514
+ }
515
+ function generateClaudeMd(identity, projectName) {
516
+ const parts = [];
517
+ parts.push(`# ${projectName} — Coding Agent Operating Manual\n`);
518
+ parts.push(`> Generated by You.md skill system. Powered by your identity context.\n`);
519
+ if (identity.profile.about) {
520
+ parts.push(`\n## Who You're Working With\n\n${identity.profile.about}\n`);
521
+ }
522
+ if (identity.preferences.agent) {
523
+ parts.push(`\n## Agent Preferences\n\n${identity.preferences.agent}\n`);
524
+ }
525
+ if (identity.directives.agent) {
526
+ parts.push(`\n## Directives\n\n${identity.directives.agent}\n`);
527
+ }
528
+ if (identity.voice.overall) {
529
+ parts.push(`\n## Voice & Communication\n\n${identity.voice.overall}\n`);
530
+ }
531
+ parts.push(`\n## Project\n\n- **Name:** ${projectName}\n`);
532
+ // Detect stack
533
+ const cwd = process.cwd();
534
+ const stackHints = [];
535
+ if (fs.existsSync(path.join(cwd, "package.json"))) {
536
+ try {
537
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf-8"));
538
+ if (pkg.dependencies?.next)
539
+ stackHints.push("Next.js");
540
+ if (pkg.dependencies?.react)
541
+ stackHints.push("React");
542
+ if (pkg.dependencies?.typescript || pkg.devDependencies?.typescript)
543
+ stackHints.push("TypeScript");
544
+ if (pkg.dependencies?.convex)
545
+ stackHints.push("Convex");
546
+ }
547
+ catch { /* skip */ }
548
+ }
549
+ if (fs.existsSync(path.join(cwd, "Cargo.toml")))
550
+ stackHints.push("Rust");
551
+ if (fs.existsSync(path.join(cwd, "go.mod")))
552
+ stackHints.push("Go");
553
+ if (fs.existsSync(path.join(cwd, "pyproject.toml")))
554
+ stackHints.push("Python");
555
+ if (stackHints.length > 0) {
556
+ parts.push(`- **Stack:** ${stackHints.join(", ")}\n`);
557
+ }
558
+ return parts.join("\n");
559
+ }
560
+ /**
561
+ * Generate an identity section to append to an existing CLAUDE.md.
562
+ * Returns null if no identity data is available.
563
+ */
564
+ function generateIdentitySection(identity) {
565
+ const sections = [];
566
+ if (identity.profile.about) {
567
+ sections.push(`## Who You're Working With\n\n${identity.profile.about}`);
568
+ }
569
+ if (identity.preferences.agent) {
570
+ sections.push(`## Agent Preferences\n\n${identity.preferences.agent}`);
571
+ }
572
+ if (identity.directives.agent) {
573
+ sections.push(`## Directives\n\n${identity.directives.agent}`);
574
+ }
575
+ if (identity.voice.overall) {
576
+ sections.push(`## Voice & Communication\n\n${identity.voice.overall}`);
577
+ }
578
+ if (sections.length === 0)
579
+ return null;
580
+ return [
581
+ "<!-- youmd:identity -->",
582
+ "<!-- Auto-generated by You.md skill system. Re-run `youmd skill init-project` to update. -->",
583
+ "",
584
+ "---",
585
+ "",
586
+ "# You.md Identity Context",
587
+ "",
588
+ ...sections,
589
+ "",
590
+ ].join("\n");
591
+ }
592
+ function scaffoldProjectContext(dir, identity, projectName) {
593
+ fs.mkdirSync(dir, { recursive: true });
594
+ const owner = identity.username || "you";
595
+ const date = new Date().toISOString().slice(0, 10);
596
+ const files = {
597
+ "PRD.md": `# ${projectName} — Product Requirements\n\n> Owner: ${owner}\n> Created: ${date}\n\n## Vision\n\n(describe the product vision)\n\n## User Journeys\n\n(describe key user flows)\n`,
598
+ "TODO.md": `# ${projectName} — Task Tracking\n\n> Updated: ${date}\n\n## In Progress\n\n## Up Next\n\n## Done\n`,
599
+ "FEATURES.md": `# ${projectName} — Feature Inventory\n\n> Updated: ${date}\n\n| Feature | Status | Notes |\n|---|---|---|\n`,
600
+ "CHANGELOG.md": `# ${projectName} — Changelog\n\n## ${date}\n\n- Project initialized with You.md skill system\n`,
601
+ "ARCHITECTURE.md": `# ${projectName} — Architecture\n\n> Updated: ${date}\n\n## Overview\n\n(describe system architecture)\n\n## Stack\n\n(list technologies)\n`,
602
+ "CURRENT_STATE.md": `# ${projectName} — Current State\n\n> Updated: ${date}\n\n## Deployed\n\n## Known Issues\n\n## Next Priorities\n`,
603
+ "STYLE_GUIDE.md": `# ${projectName} — Style Guide\n\n> Updated: ${date}\n\n(describe design system, colors, typography)\n`,
604
+ "feature-requests-active.md": `# ${projectName} — Active Feature Requests\n\n> Updated: ${date}\n\n| Request | Status | Source | Notes |\n|---|---|---|---|\n`,
605
+ };
606
+ for (const [filename, content] of Object.entries(files)) {
607
+ const filePath = path.join(dir, filename);
608
+ if (!fs.existsSync(filePath)) {
609
+ fs.writeFileSync(filePath, content);
610
+ }
611
+ }
612
+ }
613
+ // ─── Remote Sync (non-blocking) ───────────────────────────────────────
614
+ /**
615
+ * Sync a local skill install to Convex. Fire-and-forget.
616
+ */
617
+ function syncInstallToRemote(entry) {
618
+ if (!(0, config_2.isAuthenticated)())
619
+ return;
620
+ (0, api_1.recordSkillInstall)({
621
+ skillName: entry.name,
622
+ source: entry.source,
623
+ scope: entry.scope,
624
+ identityFields: entry.identity_fields,
625
+ }).catch((err) => {
626
+ if (process.env.DEBUG)
627
+ console.error(`[skill sync] install sync failed: ${err}`);
628
+ });
629
+ }
630
+ function readMetrics() {
631
+ const metricsPath = (0, config_1.getSkillMetricsPath)();
632
+ if (fs.existsSync(metricsPath)) {
633
+ try {
634
+ return JSON.parse(fs.readFileSync(metricsPath, "utf-8"));
635
+ }
636
+ catch {
637
+ // reset
638
+ }
639
+ }
640
+ return { skills: {}, identityFields: {}, lastUpdated: new Date().toISOString() };
641
+ }
642
+ function writeMetrics(metrics) {
643
+ (0, config_1.ensureSkillsDir)();
644
+ metrics.lastUpdated = new Date().toISOString();
645
+ fs.writeFileSync((0, config_1.getSkillMetricsPath)(), JSON.stringify(metrics, null, 2) + "\n");
646
+ }
647
+ function trackSkillEvent(skillName, event) {
648
+ const metrics = readMetrics();
649
+ if (!metrics.skills[skillName]) {
650
+ metrics.skills[skillName] = { uses: 0, installs: 0, lastUsed: "" };
651
+ }
652
+ const now = new Date().toISOString();
653
+ if (event === "use") {
654
+ metrics.skills[skillName].uses++;
655
+ metrics.skills[skillName].lastUsed = now;
656
+ }
657
+ else if (event === "install") {
658
+ metrics.skills[skillName].installs++;
659
+ metrics.skills[skillName].lastInstalled = now;
660
+ }
661
+ // Track identity field references from the skill's catalog entry
662
+ const catalog = (0, skill_catalog_1.readSkillCatalog)();
663
+ const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
664
+ if (entry && event === "use") {
665
+ for (const field of entry.identity_fields) {
666
+ if (!metrics.identityFields[field]) {
667
+ metrics.identityFields[field] = { references: 0 };
668
+ }
669
+ metrics.identityFields[field].references++;
670
+ }
671
+ }
672
+ writeMetrics(metrics);
673
+ }
674
+ function getMetrics() {
675
+ return readMetrics();
676
+ }
677
+ //# sourceMappingURL=skills.js.map