youmd 0.4.4 → 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.
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +27 -5
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +47 -7
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +30 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.js +1 -1
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +150 -24
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/preview.js +1 -1
- package/dist/commands/preview.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +66 -4
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +68 -4
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts +1 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +120 -2
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +174 -39
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/skill.d.ts +8 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +1150 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +118 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +12 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +16 -11
- package/dist/commands/whoami.js.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +88 -5
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +56 -2
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/ascii.d.ts +14 -0
- package/dist/lib/ascii.d.ts.map +1 -0
- package/dist/lib/ascii.js +108 -0
- package/dist/lib/ascii.js.map +1 -0
- package/dist/lib/config.d.ts +26 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +40 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/hash.d.ts +3 -0
- package/dist/lib/hash.d.ts.map +1 -0
- package/dist/lib/hash.js +31 -0
- package/dist/lib/hash.js.map +1 -0
- package/dist/lib/onboarding.d.ts +1 -1
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +227 -85
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/render.d.ts +4 -2
- package/dist/lib/render.d.ts.map +1 -1
- package/dist/lib/render.js +81 -9
- package/dist/lib/render.js.map +1 -1
- package/dist/lib/skill-catalog.d.ts +57 -0
- package/dist/lib/skill-catalog.d.ts.map +1 -0
- package/dist/lib/skill-catalog.js +196 -0
- package/dist/lib/skill-catalog.js.map +1 -0
- package/dist/lib/skill-renderer.d.ts +55 -0
- package/dist/lib/skill-renderer.d.ts.map +1 -0
- package/dist/lib/skill-renderer.js +364 -0
- package/dist/lib/skill-renderer.js.map +1 -0
- package/dist/lib/skills.d.ts +125 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +677 -0
- package/dist/lib/skills.js.map +1 -0
- package/package.json +8 -4
- package/skills/claude-md-generator.md +66 -0
- package/skills/meta-improve.md +57 -0
- package/skills/project-context-init.md +51 -0
- 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
|