youmd 0.4.9 → 0.6.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/__tests__/api.test.d.ts +2 -0
- package/dist/__tests__/api.test.d.ts.map +1 -0
- package/dist/__tests__/api.test.js +84 -0
- package/dist/__tests__/api.test.js.map +1 -0
- package/dist/__tests__/compiler.test.d.ts +2 -0
- package/dist/__tests__/compiler.test.d.ts.map +1 -0
- package/dist/__tests__/compiler.test.js +127 -0
- package/dist/__tests__/compiler.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +79 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/decompile.test.d.ts +2 -0
- package/dist/__tests__/decompile.test.d.ts.map +1 -0
- package/dist/__tests__/decompile.test.js +102 -0
- package/dist/__tests__/decompile.test.js.map +1 -0
- package/dist/__tests__/hash.test.d.ts +2 -0
- package/dist/__tests__/hash.test.d.ts.map +1 -0
- package/dist/__tests__/hash.test.js +44 -0
- package/dist/__tests__/hash.test.js.map +1 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +277 -0
- package/dist/__tests__/integration.test.js.map +1 -0
- package/dist/__tests__/skill-renderer.test.d.ts +2 -0
- package/dist/__tests__/skill-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/skill-renderer.test.js +68 -0
- package/dist/__tests__/skill-renderer.test.js.map +1 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +6 -3
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +93 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +96 -21
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +110 -11
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/diff.d.ts +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +402 -16
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/export.js +1 -1
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +138 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +1 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +77 -13
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +48 -27
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logs.d.ts +7 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +115 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +258 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/preview.d.ts.map +1 -1
- package/dist/commands/preview.js +191 -7
- package/dist/commands/preview.js.map +1 -1
- package/dist/commands/private.d.ts.map +1 -1
- package/dist/commands/private.js +248 -0
- package/dist/commands/private.js.map +1 -1
- package/dist/commands/prompts.d.ts +12 -0
- package/dist/commands/prompts.d.ts.map +1 -0
- package/dist/commands/prompts.js +245 -0
- package/dist/commands/prompts.js.map +1 -0
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +69 -6
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +110 -137
- 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 +236 -38
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +40 -84
- 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 +1226 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +221 -69
- 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 +62 -33
- package/dist/commands/whoami.js.map +1 -1
- package/dist/index.js +187 -6
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +169 -12
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +183 -33
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/ascii.d.ts.map +1 -1
- package/dist/lib/ascii.js +20 -48
- package/dist/lib/ascii.js.map +1 -1
- package/dist/lib/compiler.d.ts +16 -33
- package/dist/lib/compiler.d.ts.map +1 -1
- package/dist/lib/compiler.js +499 -84
- package/dist/lib/compiler.js.map +1 -1
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +50 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/decompile.d.ts +21 -0
- package/dist/lib/decompile.d.ts.map +1 -0
- package/dist/lib/decompile.js +304 -0
- package/dist/lib/decompile.js.map +1 -0
- 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 +4 -4
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +228 -81
- package/dist/lib/onboarding.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 +245 -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 +382 -0
- package/dist/lib/skill-renderer.js.map +1 -0
- package/dist/lib/skills.d.ts +130 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +876 -0
- package/dist/lib/skills.js.map +1 -0
- package/dist/lib/vault.d.ts +40 -0
- package/dist/lib/vault.d.ts.map +1 -0
- package/dist/lib/vault.js +187 -0
- package/dist/lib/vault.js.map +1 -0
- package/dist/mcp/server.d.ts +21 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +1283 -0
- package/dist/mcp/server.js.map +1 -0
- package/examples/houston/directives/agent.md +13 -0
- package/examples/houston/preferences/agent.md +14 -0
- package/examples/houston/profile/about.md +15 -0
- package/examples/houston/profile/links.md +10 -0
- package/examples/houston/profile/projects.md +37 -0
- package/examples/houston/profile/values.md +9 -0
- package/examples/houston/voice/voice.md +13 -0
- package/examples/jordan/directives/agent.md +13 -0
- package/examples/jordan/preferences/agent.md +16 -0
- package/examples/jordan/profile/about.md +15 -0
- package/examples/jordan/profile/links.md +10 -0
- package/examples/jordan/profile/projects.md +29 -0
- package/examples/jordan/profile/values.md +9 -0
- package/examples/jordan/voice/voice.md +12 -0
- package/examples/priya/directives/agent.md +13 -0
- package/examples/priya/preferences/agent.md +15 -0
- package/examples/priya/profile/about.md +15 -0
- package/examples/priya/profile/links.md +9 -0
- package/examples/priya/profile/projects.md +28 -0
- package/examples/priya/profile/values.md +9 -0
- package/examples/priya/voice/voice.md +12 -0
- package/package.json +15 -6
- package/skills/claude-md-generator.md +91 -0
- package/skills/meta-improve.md +84 -0
- package/skills/proactive-context-fill.md +52 -0
- package/skills/project-context-init.md +77 -0
- package/skills/voice-sync.md +89 -0
- package/skills/you-logs.md +71 -0
|
@@ -0,0 +1,876 @@
|
|
|
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
|
+
// Cycle 53: was previously accepting http:// as well. Skill installs are
|
|
179
|
+
// executable content — fetching them over insecure HTTP allows MITM
|
|
180
|
+
// injection of malicious skill code. HTTPS-only.
|
|
181
|
+
if (source.startsWith("https://")) {
|
|
182
|
+
try {
|
|
183
|
+
const res = await fetch(source);
|
|
184
|
+
if (res.ok) {
|
|
185
|
+
return await res.text();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
// ─── Install / Remove ─────────────────────────────────────────────────
|
|
195
|
+
/**
|
|
196
|
+
* Install a skill from its source to ~/.youmd/skills/<name>/SKILL.md.
|
|
197
|
+
* Uses sync resolution for local/bundled, falls back to async for remote.
|
|
198
|
+
*/
|
|
199
|
+
function installSkill(skillName) {
|
|
200
|
+
const catalog = (0, skill_catalog_1.readSkillCatalog)();
|
|
201
|
+
const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
|
|
202
|
+
if (!entry) {
|
|
203
|
+
return { ok: false, error: `skill "${skillName}" not found in catalog` };
|
|
204
|
+
}
|
|
205
|
+
// Resolve source (sync only — async handled by installSkillAsync)
|
|
206
|
+
const content = resolveSkillSource(entry.source);
|
|
207
|
+
if (!content) {
|
|
208
|
+
// If source is remote, caller should use installSkillAsync instead
|
|
209
|
+
if (entry.source.startsWith("github:") || entry.source.startsWith("https://")) {
|
|
210
|
+
return { ok: false, error: `remote source — use installSkillAsync for: ${entry.source}` };
|
|
211
|
+
}
|
|
212
|
+
return { ok: false, error: `could not resolve source: ${entry.source}` };
|
|
213
|
+
}
|
|
214
|
+
// Create skill directory and write SKILL.md
|
|
215
|
+
const skillDir = getSkillDir(entry.name);
|
|
216
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
217
|
+
fs.writeFileSync(path.join(skillDir, "SKILL.md"), content);
|
|
218
|
+
// Render an interpolated version alongside the raw one
|
|
219
|
+
const identity = (0, skill_renderer_1.loadIdentityData)();
|
|
220
|
+
const rendered = (0, skill_renderer_1.renderSkillTemplate)(content, identity);
|
|
221
|
+
fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
|
|
222
|
+
// Mark installed in catalog
|
|
223
|
+
(0, skill_catalog_1.setSkillInstalled)(catalog, entry.name, true);
|
|
224
|
+
// Track metrics
|
|
225
|
+
trackSkillEvent(entry.name, "install");
|
|
226
|
+
// Sync to Convex (non-blocking, warn on failure)
|
|
227
|
+
syncInstallToRemote(entry);
|
|
228
|
+
return { ok: true };
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Async install — handles remote sources (GitHub, HTTPS URLs).
|
|
232
|
+
*/
|
|
233
|
+
async function installSkillAsync(skillName) {
|
|
234
|
+
const catalog = (0, skill_catalog_1.readSkillCatalog)();
|
|
235
|
+
const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
|
|
236
|
+
if (!entry) {
|
|
237
|
+
return { ok: false, error: `skill "${skillName}" not found in catalog` };
|
|
238
|
+
}
|
|
239
|
+
// Try sync first
|
|
240
|
+
let content = resolveSkillSource(entry.source);
|
|
241
|
+
// Fall back to async
|
|
242
|
+
if (!content) {
|
|
243
|
+
content = await resolveSkillSourceAsync(entry.source);
|
|
244
|
+
}
|
|
245
|
+
if (!content) {
|
|
246
|
+
return { ok: false, error: `could not resolve source: ${entry.source}` };
|
|
247
|
+
}
|
|
248
|
+
const skillDir = getSkillDir(entry.name);
|
|
249
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
250
|
+
fs.writeFileSync(path.join(skillDir, "SKILL.md"), content);
|
|
251
|
+
const identity = (0, skill_renderer_1.loadIdentityData)();
|
|
252
|
+
const rendered = (0, skill_renderer_1.renderSkillTemplate)(content, identity);
|
|
253
|
+
fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
|
|
254
|
+
(0, skill_catalog_1.setSkillInstalled)(catalog, entry.name, true);
|
|
255
|
+
trackSkillEvent(entry.name, "install");
|
|
256
|
+
syncInstallToRemote(entry);
|
|
257
|
+
return { ok: true };
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Remove an installed skill.
|
|
261
|
+
*/
|
|
262
|
+
function removeSkill(skillName) {
|
|
263
|
+
const catalog = (0, skill_catalog_1.readSkillCatalog)();
|
|
264
|
+
const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
|
|
265
|
+
if (!entry) {
|
|
266
|
+
return { ok: false, error: `skill "${skillName}" not found in catalog` };
|
|
267
|
+
}
|
|
268
|
+
const skillDir = getSkillDir(entry.name);
|
|
269
|
+
if (fs.existsSync(skillDir)) {
|
|
270
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
271
|
+
}
|
|
272
|
+
(0, skill_catalog_1.setSkillInstalled)(catalog, entry.name, false);
|
|
273
|
+
trackSkillEvent(entry.name, "remove");
|
|
274
|
+
// Sync removal to Convex (non-blocking, warn on failure)
|
|
275
|
+
if ((0, config_2.isAuthenticated)()) {
|
|
276
|
+
(0, api_1.removeSkillInstall)(entry.name).catch((err) => {
|
|
277
|
+
const chalk = require("chalk");
|
|
278
|
+
console.log(chalk.dim(` sync: ${entry.name} remote removal sync failed (non-fatal)`));
|
|
279
|
+
if (process.env.DEBUG)
|
|
280
|
+
console.error(`[skill sync] remove failed: ${err}`);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return { ok: true };
|
|
284
|
+
}
|
|
285
|
+
// ─── Use / Render ─────────────────────────────────────────────────────
|
|
286
|
+
/**
|
|
287
|
+
* Use a skill — render it with current identity data.
|
|
288
|
+
* Returns the rendered content.
|
|
289
|
+
*/
|
|
290
|
+
function useSkill(skillName) {
|
|
291
|
+
const catalog = (0, skill_catalog_1.readSkillCatalog)();
|
|
292
|
+
const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
|
|
293
|
+
if (!entry) {
|
|
294
|
+
return { ok: false, error: `skill "${skillName}" not found in catalog` };
|
|
295
|
+
}
|
|
296
|
+
// Install if not already
|
|
297
|
+
if (!entry.installed) {
|
|
298
|
+
const installResult = installSkill(skillName);
|
|
299
|
+
if (!installResult.ok)
|
|
300
|
+
return installResult;
|
|
301
|
+
}
|
|
302
|
+
const skillFile = readSkillFile(entry.name);
|
|
303
|
+
if (!skillFile) {
|
|
304
|
+
return { ok: false, error: `could not read SKILL.md for "${skillName}"` };
|
|
305
|
+
}
|
|
306
|
+
const identity = (0, skill_renderer_1.loadIdentityData)();
|
|
307
|
+
const readiness = (0, skill_renderer_1.checkTemplateReadiness)(skillFile.content, identity);
|
|
308
|
+
const rendered = (0, skill_renderer_1.renderSkillTemplate)(skillFile.content, identity);
|
|
309
|
+
// Update rendered version
|
|
310
|
+
const skillDir = getSkillDir(entry.name);
|
|
311
|
+
fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
|
|
312
|
+
trackSkillEvent(entry.name, "use");
|
|
313
|
+
// Sync usage to Convex (non-blocking, warn on failure)
|
|
314
|
+
if ((0, config_2.isAuthenticated)()) {
|
|
315
|
+
(0, api_1.trackSkillUsage)(entry.name).catch((err) => {
|
|
316
|
+
const chalk = require("chalk");
|
|
317
|
+
console.log(chalk.dim(` sync: usage tracking failed for ${entry.name} (non-fatal)`));
|
|
318
|
+
if (process.env.DEBUG)
|
|
319
|
+
console.error(`[skill sync] usage tracking failed: ${err}`);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return { ok: true, content: rendered, readiness };
|
|
323
|
+
}
|
|
324
|
+
// ─── Sync ─────────────────────────────────────────────────────────────
|
|
325
|
+
/**
|
|
326
|
+
* Re-interpolate all installed skills against current identity data.
|
|
327
|
+
*/
|
|
328
|
+
function syncAllSkills() {
|
|
329
|
+
const catalog = (0, skill_catalog_1.readSkillCatalog)();
|
|
330
|
+
const identity = (0, skill_renderer_1.loadIdentityData)();
|
|
331
|
+
const synced = [];
|
|
332
|
+
const errors = [];
|
|
333
|
+
for (const entry of catalog.skills) {
|
|
334
|
+
if (!entry.installed)
|
|
335
|
+
continue;
|
|
336
|
+
const skillFile = readSkillFile(entry.name);
|
|
337
|
+
if (!skillFile) {
|
|
338
|
+
errors.push(`${entry.name}: SKILL.md not found`);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const rendered = (0, skill_renderer_1.renderSkillTemplate)(skillFile.content, identity);
|
|
343
|
+
const skillDir = getSkillDir(entry.name);
|
|
344
|
+
fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
|
|
345
|
+
synced.push(entry.name);
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
errors.push(`${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return { synced, errors };
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Sync only skills affected by specific identity field changes.
|
|
355
|
+
*/
|
|
356
|
+
function syncAffectedSkills(changedFields) {
|
|
357
|
+
const catalog = (0, skill_catalog_1.readSkillCatalog)();
|
|
358
|
+
const identity = (0, skill_renderer_1.loadIdentityData)();
|
|
359
|
+
const synced = [];
|
|
360
|
+
const errors = [];
|
|
361
|
+
const affected = catalog.skills.filter((s) => s.installed && s.identity_fields.some((f) => changedFields.includes(f)));
|
|
362
|
+
for (const entry of affected) {
|
|
363
|
+
const skillFile = readSkillFile(entry.name);
|
|
364
|
+
if (!skillFile) {
|
|
365
|
+
errors.push(`${entry.name}: SKILL.md not found`);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const rendered = (0, skill_renderer_1.renderSkillTemplate)(skillFile.content, identity);
|
|
370
|
+
const skillDir = getSkillDir(entry.name);
|
|
371
|
+
fs.writeFileSync(path.join(skillDir, "RENDERED.md"), rendered);
|
|
372
|
+
synced.push(entry.name);
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
errors.push(`${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return { synced, errors };
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Link installed skills to an agent's directory.
|
|
382
|
+
*
|
|
383
|
+
* claude → .claude/skills/youmd/
|
|
384
|
+
* cursor → .cursor/rules/youmd.md (single file concatenation)
|
|
385
|
+
* codex → .codex/skills/youmd/
|
|
386
|
+
*/
|
|
387
|
+
function linkToAgent(target) {
|
|
388
|
+
const catalog = (0, skill_catalog_1.readSkillCatalog)();
|
|
389
|
+
const installedSkills = catalog.skills.filter((s) => s.installed);
|
|
390
|
+
if (installedSkills.length === 0) {
|
|
391
|
+
return { ok: false, error: "no skills installed. run: youmd skill install <name>" };
|
|
392
|
+
}
|
|
393
|
+
const cwd = process.cwd();
|
|
394
|
+
switch (target) {
|
|
395
|
+
case "claude": {
|
|
396
|
+
const targetDir = path.join(cwd, ".claude", "skills", "youmd");
|
|
397
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
398
|
+
for (const entry of installedSkills) {
|
|
399
|
+
const rendered = getRenderedContent(entry.name);
|
|
400
|
+
if (rendered) {
|
|
401
|
+
fs.writeFileSync(path.join(targetDir, `${entry.name}.md`), rendered);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return { ok: true, path: targetDir };
|
|
405
|
+
}
|
|
406
|
+
case "cursor": {
|
|
407
|
+
const targetDir = path.join(cwd, ".cursor", "rules");
|
|
408
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
409
|
+
// Cursor prefers a single concatenated file
|
|
410
|
+
const parts = [];
|
|
411
|
+
parts.push("# You.md Identity Skills\n");
|
|
412
|
+
parts.push(`> Auto-generated by youmd skill link. Do not edit manually.\n`);
|
|
413
|
+
for (const entry of installedSkills) {
|
|
414
|
+
const rendered = getRenderedContent(entry.name);
|
|
415
|
+
if (rendered) {
|
|
416
|
+
parts.push(`\n---\n\n## ${entry.name}\n\n${rendered}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const outPath = path.join(targetDir, "youmd.md");
|
|
420
|
+
fs.writeFileSync(outPath, parts.join("\n"));
|
|
421
|
+
return { ok: true, path: outPath };
|
|
422
|
+
}
|
|
423
|
+
case "codex": {
|
|
424
|
+
const targetDir = path.join(cwd, ".codex", "skills", "youmd");
|
|
425
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
426
|
+
for (const entry of installedSkills) {
|
|
427
|
+
const rendered = getRenderedContent(entry.name);
|
|
428
|
+
if (rendered) {
|
|
429
|
+
fs.writeFileSync(path.join(targetDir, `${entry.name}.md`), rendered);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return { ok: true, path: targetDir };
|
|
433
|
+
}
|
|
434
|
+
default:
|
|
435
|
+
return { ok: false, error: `unknown agent target: ${target}` };
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function getRenderedContent(skillName) {
|
|
439
|
+
const renderedPath = path.join(getSkillDir(skillName), "RENDERED.md");
|
|
440
|
+
if (fs.existsSync(renderedPath)) {
|
|
441
|
+
return fs.readFileSync(renderedPath, "utf-8");
|
|
442
|
+
}
|
|
443
|
+
// Fallback: render on the fly
|
|
444
|
+
const skillFile = readSkillFile(skillName);
|
|
445
|
+
if (!skillFile)
|
|
446
|
+
return null;
|
|
447
|
+
return (0, skill_renderer_1.renderSkillTemplate)(skillFile.content);
|
|
448
|
+
}
|
|
449
|
+
// ─── Init Project (Compound Command) ─────────────────────────────────
|
|
450
|
+
const BOOTSTRAP_START = "<!-- youmd:bootstrap:start -->";
|
|
451
|
+
const BOOTSTRAP_END = "<!-- youmd:bootstrap:end -->";
|
|
452
|
+
/**
|
|
453
|
+
* Initialize a project with additive agent bootstrap files, scaffolded
|
|
454
|
+
* project-context docs, a generated .you layer, and host-linked skills.
|
|
455
|
+
*/
|
|
456
|
+
function initProject(options = {}) {
|
|
457
|
+
const steps = [];
|
|
458
|
+
const project = (0, config_1.detectProjectContext)();
|
|
459
|
+
const cwd = process.cwd();
|
|
460
|
+
const identity = (0, skill_renderer_1.loadIdentityData)();
|
|
461
|
+
const projectName = project?.name || path.basename(cwd);
|
|
462
|
+
const agentsPath = path.join(cwd, "AGENTS.md");
|
|
463
|
+
const claudePath = path.join(cwd, "CLAUDE.md");
|
|
464
|
+
const projectContextDir = path.join(cwd, "project-context");
|
|
465
|
+
const youDir = path.join(cwd, ".you");
|
|
466
|
+
const requestedMode = options.mode ?? "auto";
|
|
467
|
+
const resolvedMode = resolveInitProjectMode({
|
|
468
|
+
mode: requestedMode,
|
|
469
|
+
hasAgentFile: fs.existsSync(agentsPath),
|
|
470
|
+
hasClaudeFile: fs.existsSync(claudePath),
|
|
471
|
+
hasProjectContext: fs.existsSync(projectContextDir),
|
|
472
|
+
});
|
|
473
|
+
// Step 1: Install core skills that power the generated layer
|
|
474
|
+
for (const name of ["claude-md-generator", "project-context-init"]) {
|
|
475
|
+
const result = installSkill(name);
|
|
476
|
+
steps.push({
|
|
477
|
+
name: `install ${name}`,
|
|
478
|
+
ok: result.ok,
|
|
479
|
+
detail: result.ok ? "ready" : result.error,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
// Step 2: Create/update the generated .you layer
|
|
483
|
+
const youResult = scaffoldYouLayer(youDir, identity, projectName);
|
|
484
|
+
steps.push({
|
|
485
|
+
name: ".you/",
|
|
486
|
+
ok: true,
|
|
487
|
+
detail: youResult,
|
|
488
|
+
});
|
|
489
|
+
// Step 3: Scaffold or update first-class entrypoints
|
|
490
|
+
if (resolvedMode === "zero-touch") {
|
|
491
|
+
steps.push({
|
|
492
|
+
name: "top-level agent files",
|
|
493
|
+
ok: true,
|
|
494
|
+
detail: "zero-touch mode — left AGENTS.md and CLAUDE.md unchanged",
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
const agentResult = ensureAgentInstructionFiles({
|
|
499
|
+
cwd,
|
|
500
|
+
projectName,
|
|
501
|
+
identity,
|
|
502
|
+
});
|
|
503
|
+
steps.push({
|
|
504
|
+
name: "agent instruction files",
|
|
505
|
+
ok: true,
|
|
506
|
+
detail: agentResult,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
// Step 4: Scaffold the canonical project-context/ directory
|
|
510
|
+
if (resolvedMode === "zero-touch") {
|
|
511
|
+
steps.push({
|
|
512
|
+
name: "project-context/",
|
|
513
|
+
ok: true,
|
|
514
|
+
detail: "zero-touch mode — left canonical project-context untouched",
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
const scaffolded = scaffoldProjectContext(projectContextDir, identity, projectName);
|
|
519
|
+
steps.push({
|
|
520
|
+
name: "project-context/",
|
|
521
|
+
ok: true,
|
|
522
|
+
detail: scaffolded,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
// Step 5: Link rendered skills into host-specific discovery paths
|
|
526
|
+
const linkTargets = ["claude"];
|
|
527
|
+
if (fs.existsSync(path.join(cwd, ".cursor")))
|
|
528
|
+
linkTargets.push("cursor");
|
|
529
|
+
if (fs.existsSync(path.join(cwd, ".codex")))
|
|
530
|
+
linkTargets.push("codex");
|
|
531
|
+
for (const target of linkTargets) {
|
|
532
|
+
const result = linkToAgent(target);
|
|
533
|
+
const label = target === "claude"
|
|
534
|
+
? "link .claude/skills/youmd/"
|
|
535
|
+
: target === "cursor"
|
|
536
|
+
? "link .cursor/rules/youmd.md"
|
|
537
|
+
: "link .codex/skills/youmd/";
|
|
538
|
+
steps.push({
|
|
539
|
+
name: label,
|
|
540
|
+
ok: result.ok,
|
|
541
|
+
detail: result.error || result.path,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
return { ok: steps.every((s) => s.ok), mode: resolvedMode, steps };
|
|
545
|
+
}
|
|
546
|
+
function resolveInitProjectMode(args) {
|
|
547
|
+
if (args.mode !== "auto")
|
|
548
|
+
return args.mode;
|
|
549
|
+
if (!args.hasAgentFile && !args.hasClaudeFile && !args.hasProjectContext) {
|
|
550
|
+
return "scaffold";
|
|
551
|
+
}
|
|
552
|
+
return "additive";
|
|
553
|
+
}
|
|
554
|
+
function generateAgentMd(identity, projectName) {
|
|
555
|
+
const parts = [];
|
|
556
|
+
parts.push(`# ${projectName} — Agent Operating Manual\n`);
|
|
557
|
+
parts.push(`> Bootstrapped by You.md. This is the repo-visible instruction layer agents should read first.\n`);
|
|
558
|
+
if (identity.profile.about) {
|
|
559
|
+
parts.push(`\n## Who You're Working With\n\n${identity.profile.about}\n`);
|
|
560
|
+
}
|
|
561
|
+
if (identity.preferences.agent) {
|
|
562
|
+
parts.push(`\n## Agent Preferences\n\n${identity.preferences.agent}\n`);
|
|
563
|
+
}
|
|
564
|
+
if (identity.directives.agent) {
|
|
565
|
+
parts.push(`\n## Directives\n\n${identity.directives.agent}\n`);
|
|
566
|
+
}
|
|
567
|
+
if (identity.voice.overall) {
|
|
568
|
+
parts.push(`\n## Voice & Communication\n\n${identity.voice.overall}\n`);
|
|
569
|
+
}
|
|
570
|
+
parts.push(`\n## Workflow\n`);
|
|
571
|
+
parts.push(`- Read \`project-context/CURRENT_STATE.md\` and \`project-context/feature-requests-active.md\` before substantial work.\n`);
|
|
572
|
+
parts.push(`- Track multi-part user asks in \`project-context/feature-requests-active.md\` rather than silently handling only part of the request.\n`);
|
|
573
|
+
parts.push(`- Treat updates to \`project-context/TODO.md\`, \`FEATURES.md\`, \`CHANGELOG.md\`, \`feature-requests-active.md\`, and \`PROMPTS.md\` as part of "done" after meaningful development sessions.\n`);
|
|
574
|
+
parts.push(`- Read \`.you/AGENT.md\`, \`.you/STACK-MAP.md\`, and \`.you/project-context/\` for additive You.md-generated context.\n`);
|
|
575
|
+
parts.push(`\n## Project\n\n- **Name:** ${projectName}\n`);
|
|
576
|
+
// Detect stack
|
|
577
|
+
const cwd = process.cwd();
|
|
578
|
+
const stackHints = [];
|
|
579
|
+
if (fs.existsSync(path.join(cwd, "package.json"))) {
|
|
580
|
+
try {
|
|
581
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf-8"));
|
|
582
|
+
if (pkg.dependencies?.next)
|
|
583
|
+
stackHints.push("Next.js");
|
|
584
|
+
if (pkg.dependencies?.react)
|
|
585
|
+
stackHints.push("React");
|
|
586
|
+
if (pkg.dependencies?.typescript || pkg.devDependencies?.typescript)
|
|
587
|
+
stackHints.push("TypeScript");
|
|
588
|
+
if (pkg.dependencies?.convex)
|
|
589
|
+
stackHints.push("Convex");
|
|
590
|
+
}
|
|
591
|
+
catch { /* skip */ }
|
|
592
|
+
}
|
|
593
|
+
if (fs.existsSync(path.join(cwd, "Cargo.toml")))
|
|
594
|
+
stackHints.push("Rust");
|
|
595
|
+
if (fs.existsSync(path.join(cwd, "go.mod")))
|
|
596
|
+
stackHints.push("Go");
|
|
597
|
+
if (fs.existsSync(path.join(cwd, "pyproject.toml")))
|
|
598
|
+
stackHints.push("Python");
|
|
599
|
+
if (stackHints.length > 0) {
|
|
600
|
+
parts.push(`- **Stack:** ${stackHints.join(", ")}\n`);
|
|
601
|
+
}
|
|
602
|
+
return parts.join("\n");
|
|
603
|
+
}
|
|
604
|
+
function generateClaudeMd(projectName) {
|
|
605
|
+
return [
|
|
606
|
+
`# ${projectName} — Claude Entry Point`,
|
|
607
|
+
"",
|
|
608
|
+
"> This repo uses `AGENTS.md` as the canonical human-owned instruction layer.",
|
|
609
|
+
"> Read `AGENTS.md`, `project-context/`, and `.you/` before substantial work.",
|
|
610
|
+
"",
|
|
611
|
+
].join("\n");
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Generate an additive managed bootstrap block for existing top-level
|
|
615
|
+
* instruction files without rewriting user-owned content.
|
|
616
|
+
*/
|
|
617
|
+
function generateBootstrapBlock(target, includeCompanion) {
|
|
618
|
+
const companionLine = includeCompanion && target === "CLAUDE.md"
|
|
619
|
+
? "- Also read `AGENTS.md` for repo-native operating instructions.\n"
|
|
620
|
+
: includeCompanion && target === "AGENTS.md"
|
|
621
|
+
? "- Keep `CLAUDE.md` as the Claude-specific entrypoint if this repo uses it.\n"
|
|
622
|
+
: "";
|
|
623
|
+
return [
|
|
624
|
+
BOOTSTRAP_START,
|
|
625
|
+
`<!-- Auto-generated by You.md. Re-run \`youmd skill init-project\` to refresh this managed block. -->`,
|
|
626
|
+
"",
|
|
627
|
+
"## You.md Bootstrap",
|
|
628
|
+
"",
|
|
629
|
+
"- Read `project-context/CURRENT_STATE.md` and `project-context/feature-requests-active.md` before substantial work.",
|
|
630
|
+
"- Track multi-part asks in `project-context/feature-requests-active.md` rather than silently handling only one part.",
|
|
631
|
+
"- Treat updates to `project-context/TODO.md`, `FEATURES.md`, `CHANGELOG.md`, `feature-requests-active.md`, and `PROMPTS.md` as part of done after meaningful development sessions.",
|
|
632
|
+
"- Read `.you/AGENT.md`, `.you/STACK-MAP.md`, and `.you/project-context/` for additive You.md-generated context.",
|
|
633
|
+
companionLine.trimEnd(),
|
|
634
|
+
"",
|
|
635
|
+
BOOTSTRAP_END,
|
|
636
|
+
"",
|
|
637
|
+
]
|
|
638
|
+
.filter(Boolean)
|
|
639
|
+
.join("\n");
|
|
640
|
+
}
|
|
641
|
+
function upsertManagedBootstrap(existing, target, includeCompanion) {
|
|
642
|
+
const block = generateBootstrapBlock(target, includeCompanion).trim();
|
|
643
|
+
const pattern = new RegExp(`${escapeRegex(BOOTSTRAP_START)}[\\s\\S]*?${escapeRegex(BOOTSTRAP_END)}`, "m");
|
|
644
|
+
if (pattern.test(existing)) {
|
|
645
|
+
const replaced = existing.replace(pattern, block);
|
|
646
|
+
return {
|
|
647
|
+
content: replaced === existing ? existing : replaced,
|
|
648
|
+
status: replaced === existing ? "unchanged" : "updated",
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
const trimmed = existing.trimEnd();
|
|
652
|
+
return {
|
|
653
|
+
content: trimmed ? `${trimmed}\n\n${block}\n` : `${block}\n`,
|
|
654
|
+
status: "added",
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function ensureAgentInstructionFiles(args) {
|
|
658
|
+
const agentsPath = path.join(args.cwd, "AGENTS.md");
|
|
659
|
+
const claudePath = path.join(args.cwd, "CLAUDE.md");
|
|
660
|
+
const hasAgents = fs.existsSync(agentsPath);
|
|
661
|
+
const hasClaude = fs.existsSync(claudePath);
|
|
662
|
+
const notes = [];
|
|
663
|
+
if (!hasAgents) {
|
|
664
|
+
fs.writeFileSync(agentsPath, generateAgentMd(args.identity, args.projectName));
|
|
665
|
+
notes.push("created AGENTS.md");
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
const existing = fs.readFileSync(agentsPath, "utf-8");
|
|
669
|
+
const update = upsertManagedBootstrap(existing, "AGENTS.md", hasClaude);
|
|
670
|
+
fs.writeFileSync(agentsPath, update.content);
|
|
671
|
+
notes.push(`${update.status} AGENTS.md bootstrap`);
|
|
672
|
+
}
|
|
673
|
+
if (!hasClaude) {
|
|
674
|
+
fs.writeFileSync(claudePath, `${generateClaudeMd(args.projectName)}\n${generateBootstrapBlock("CLAUDE.md", true)}`);
|
|
675
|
+
notes.push("created CLAUDE.md");
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
const existing = fs.readFileSync(claudePath, "utf-8");
|
|
679
|
+
const update = upsertManagedBootstrap(existing, "CLAUDE.md", true);
|
|
680
|
+
fs.writeFileSync(claudePath, update.content);
|
|
681
|
+
notes.push(`${update.status} CLAUDE.md bootstrap`);
|
|
682
|
+
}
|
|
683
|
+
return notes.join("; ");
|
|
684
|
+
}
|
|
685
|
+
function scaffoldProjectContext(dir, identity, projectName) {
|
|
686
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
687
|
+
const owner = identity.username || "you";
|
|
688
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
689
|
+
const files = {
|
|
690
|
+
"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`,
|
|
691
|
+
"TODO.md": `# ${projectName} — Task Tracking\n\n> Updated: ${date}\n\n## In Progress\n\n## Up Next\n\n## Done\n`,
|
|
692
|
+
"FEATURES.md": `# ${projectName} — Feature Inventory\n\n> Updated: ${date}\n\n| Feature | Status | Notes |\n|---|---|---|\n`,
|
|
693
|
+
"CHANGELOG.md": `# ${projectName} — Changelog\n\n## ${date}\n\n- Project initialized with You.md skill system\n`,
|
|
694
|
+
"ARCHITECTURE.md": `# ${projectName} — Architecture\n\n> Updated: ${date}\n\n## Overview\n\n(describe system architecture)\n\n## Stack\n\n(list technologies)\n`,
|
|
695
|
+
"CURRENT_STATE.md": `# ${projectName} — Current State\n\n> Updated: ${date}\n\n## Deployed\n\n## Known Issues\n\n## Next Priorities\n`,
|
|
696
|
+
"STYLE_GUIDE.md": `# ${projectName} — Style Guide\n\n> Updated: ${date}\n\n(describe design system, colors, typography)\n`,
|
|
697
|
+
"feature-requests-active.md": `# ${projectName} — Active Feature Requests\n\n> Updated: ${date}\n\n| Request | Status | Source | Notes |\n|---|---|---|---|\n`,
|
|
698
|
+
"PROMPTS.md": `# ${projectName} — Prompt Archive\n\n> Updated: ${date}\n> Total sessions: 0\n> Total prompts: 0\n\n## Notes\n\nAppend user messages here after each development session so future agents can search exact wording.\n`,
|
|
699
|
+
};
|
|
700
|
+
const created = [];
|
|
701
|
+
const skipped = [];
|
|
702
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
703
|
+
const filePath = path.join(dir, filename);
|
|
704
|
+
if (!fs.existsSync(filePath)) {
|
|
705
|
+
fs.writeFileSync(filePath, content);
|
|
706
|
+
created.push(filename);
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
skipped.push(filename);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (created.length === 0) {
|
|
713
|
+
return `all canonical files already present (${skipped.length} checked)`;
|
|
714
|
+
}
|
|
715
|
+
return `created ${created.length} file(s): ${created.join(", ")}`;
|
|
716
|
+
}
|
|
717
|
+
function scaffoldYouLayer(dir, identity, projectName) {
|
|
718
|
+
const projectContextDir = path.join(dir, "project-context");
|
|
719
|
+
fs.mkdirSync(projectContextDir, { recursive: true });
|
|
720
|
+
const writes = [
|
|
721
|
+
[path.join(dir, "AGENT.md"), generateYouAgentMd(identity, projectName)],
|
|
722
|
+
[path.join(dir, "STACK-MAP.md"), generateStackMap(projectName)],
|
|
723
|
+
[
|
|
724
|
+
path.join(projectContextDir, "README.md"),
|
|
725
|
+
generateYouProjectContextReadme(projectName),
|
|
726
|
+
],
|
|
727
|
+
];
|
|
728
|
+
const created = [];
|
|
729
|
+
const updated = [];
|
|
730
|
+
for (const [filePath, content] of writes) {
|
|
731
|
+
const existed = fs.existsSync(filePath);
|
|
732
|
+
fs.writeFileSync(filePath, content);
|
|
733
|
+
(existed ? updated : created).push(path.relative(dir, filePath));
|
|
734
|
+
}
|
|
735
|
+
const parts = [];
|
|
736
|
+
if (created.length > 0)
|
|
737
|
+
parts.push(`created ${created.join(", ")}`);
|
|
738
|
+
if (updated.length > 0)
|
|
739
|
+
parts.push(`updated ${updated.join(", ")}`);
|
|
740
|
+
return parts.join("; ");
|
|
741
|
+
}
|
|
742
|
+
function generateYouAgentMd(identity, projectName) {
|
|
743
|
+
const lines = [
|
|
744
|
+
`# You.md Generated Agent Context`,
|
|
745
|
+
"",
|
|
746
|
+
`> This is the You.md-owned additive layer for \`${projectName}\`. Top-level repo files stay user-owned.`,
|
|
747
|
+
"",
|
|
748
|
+
"## Purpose",
|
|
749
|
+
"",
|
|
750
|
+
"- Provide additive identity and workflow context without overwriting human-maintained docs.",
|
|
751
|
+
"- Keep cross-agent expectations consistent across Claude, Codex, Cursor, and similar tools.",
|
|
752
|
+
"",
|
|
753
|
+
];
|
|
754
|
+
if (identity.profile.about) {
|
|
755
|
+
lines.push("## Identity", "", identity.profile.about, "");
|
|
756
|
+
}
|
|
757
|
+
if (identity.preferences.agent) {
|
|
758
|
+
lines.push("## Agent Preferences", "", identity.preferences.agent, "");
|
|
759
|
+
}
|
|
760
|
+
if (identity.directives.agent) {
|
|
761
|
+
lines.push("## Directives", "", identity.directives.agent, "");
|
|
762
|
+
}
|
|
763
|
+
if (identity.voice.overall) {
|
|
764
|
+
lines.push("## Voice", "", identity.voice.overall, "");
|
|
765
|
+
}
|
|
766
|
+
lines.push("## Operating Expectations", "");
|
|
767
|
+
lines.push("- Read the repo-visible instruction file(s) first.");
|
|
768
|
+
lines.push("- Treat `project-context/` as the canonical shared working context.");
|
|
769
|
+
lines.push("- Use this `.you/` layer as additive guidance and generated metadata, not as a replacement for repo-owned docs.");
|
|
770
|
+
lines.push("");
|
|
771
|
+
return lines.join("\n");
|
|
772
|
+
}
|
|
773
|
+
function generateStackMap(projectName) {
|
|
774
|
+
return [
|
|
775
|
+
"# You.md Stack Map",
|
|
776
|
+
"",
|
|
777
|
+
`> Generated for \`${projectName}\`. This file explains what You.md owns versus what the repo/user owns.`,
|
|
778
|
+
"",
|
|
779
|
+
"## Ownership",
|
|
780
|
+
"",
|
|
781
|
+
"- `AGENTS.md` / `CLAUDE.md`: human-owned entrypoints with a managed additive You.md bootstrap block.",
|
|
782
|
+
"- `project-context/`: canonical shared repo context. You.md only fills missing files unless a human explicitly asks for deeper changes.",
|
|
783
|
+
"- `.you/`: You.md-owned generated layer for additive cross-agent context.",
|
|
784
|
+
"- `.claude/skills/youmd`, `.codex/skills/youmd`, `.cursor/rules/youmd.md`: host-specific rendered skill surfaces when linked.",
|
|
785
|
+
"",
|
|
786
|
+
"## Maintenance Rules",
|
|
787
|
+
"",
|
|
788
|
+
"- Update only the managed You.md block inside top-level instruction files.",
|
|
789
|
+
"- Never delete or rewrite user-authored repo context without explicit approval.",
|
|
790
|
+
"- Prefer additive file creation and managed-block refreshes over broad merges.",
|
|
791
|
+
"",
|
|
792
|
+
].join("\n");
|
|
793
|
+
}
|
|
794
|
+
function generateYouProjectContextReadme(projectName) {
|
|
795
|
+
return [
|
|
796
|
+
"# You.md Supplemental Project Context",
|
|
797
|
+
"",
|
|
798
|
+
`This directory holds additive, generated context for \`${projectName}\`.`,
|
|
799
|
+
"",
|
|
800
|
+
"- Keep canonical project docs in `project-context/`.",
|
|
801
|
+
"- Keep generated cross-agent context in `.you/`.",
|
|
802
|
+
"- Use this layer when You.md needs to preserve shared behavior without clobbering hand-written repo docs.",
|
|
803
|
+
"",
|
|
804
|
+
].join("\n");
|
|
805
|
+
}
|
|
806
|
+
function escapeRegex(input) {
|
|
807
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
808
|
+
}
|
|
809
|
+
// ─── Remote Sync (non-blocking) ───────────────────────────────────────
|
|
810
|
+
/**
|
|
811
|
+
* Sync a local skill install to Convex. Fire-and-forget.
|
|
812
|
+
*/
|
|
813
|
+
function syncInstallToRemote(entry) {
|
|
814
|
+
if (!(0, config_2.isAuthenticated)())
|
|
815
|
+
return;
|
|
816
|
+
(0, api_1.recordSkillInstall)({
|
|
817
|
+
skillName: entry.name,
|
|
818
|
+
source: entry.source,
|
|
819
|
+
scope: entry.scope,
|
|
820
|
+
identityFields: entry.identity_fields,
|
|
821
|
+
}).catch((err) => {
|
|
822
|
+
// Dim warning instead of silent swallow
|
|
823
|
+
const chalk = require("chalk");
|
|
824
|
+
console.log(chalk.dim(` sync: ${entry.name} remote sync failed (non-fatal)`));
|
|
825
|
+
if (process.env.DEBUG)
|
|
826
|
+
console.error(`[skill sync] install sync failed: ${err}`);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
function readMetrics() {
|
|
830
|
+
const metricsPath = (0, config_1.getSkillMetricsPath)();
|
|
831
|
+
if (fs.existsSync(metricsPath)) {
|
|
832
|
+
try {
|
|
833
|
+
return JSON.parse(fs.readFileSync(metricsPath, "utf-8"));
|
|
834
|
+
}
|
|
835
|
+
catch {
|
|
836
|
+
// reset
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return { skills: {}, identityFields: {}, lastUpdated: new Date().toISOString() };
|
|
840
|
+
}
|
|
841
|
+
function writeMetrics(metrics) {
|
|
842
|
+
(0, config_1.ensureSkillsDir)();
|
|
843
|
+
metrics.lastUpdated = new Date().toISOString();
|
|
844
|
+
fs.writeFileSync((0, config_1.getSkillMetricsPath)(), JSON.stringify(metrics, null, 2) + "\n");
|
|
845
|
+
}
|
|
846
|
+
function trackSkillEvent(skillName, event) {
|
|
847
|
+
const metrics = readMetrics();
|
|
848
|
+
if (!metrics.skills[skillName]) {
|
|
849
|
+
metrics.skills[skillName] = { uses: 0, installs: 0, lastUsed: "" };
|
|
850
|
+
}
|
|
851
|
+
const now = new Date().toISOString();
|
|
852
|
+
if (event === "use") {
|
|
853
|
+
metrics.skills[skillName].uses++;
|
|
854
|
+
metrics.skills[skillName].lastUsed = now;
|
|
855
|
+
}
|
|
856
|
+
else if (event === "install") {
|
|
857
|
+
metrics.skills[skillName].installs++;
|
|
858
|
+
metrics.skills[skillName].lastInstalled = now;
|
|
859
|
+
}
|
|
860
|
+
// Track identity field references from the skill's catalog entry
|
|
861
|
+
const catalog = (0, skill_catalog_1.readSkillCatalog)();
|
|
862
|
+
const entry = (0, skill_catalog_1.findSkill)(catalog, skillName);
|
|
863
|
+
if (entry && event === "use") {
|
|
864
|
+
for (const field of entry.identity_fields) {
|
|
865
|
+
if (!metrics.identityFields[field]) {
|
|
866
|
+
metrics.identityFields[field] = { references: 0 };
|
|
867
|
+
}
|
|
868
|
+
metrics.identityFields[field].references++;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
writeMetrics(metrics);
|
|
872
|
+
}
|
|
873
|
+
function getMetrics() {
|
|
874
|
+
return readMetrics();
|
|
875
|
+
}
|
|
876
|
+
//# sourceMappingURL=skills.js.map
|