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
package/dist/lib/compiler.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Bundle compiler — reads markdown files from profile/, preferences/, voice/,
|
|
4
|
+
* directives/ and compiles them into the nested server format (you-md/v1).
|
|
5
|
+
*
|
|
6
|
+
* Output matches convex/lib/compile.ts so the server stores it correctly
|
|
7
|
+
* and the web can render it without transformation.
|
|
8
|
+
*/
|
|
2
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
10
|
if (k2 === undefined) k2 = k;
|
|
4
11
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -36,67 +43,335 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
43
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
44
|
};
|
|
38
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.readDirectory = readDirectory;
|
|
40
46
|
exports.compileBundle = compileBundle;
|
|
41
47
|
exports.writeBundle = writeBundle;
|
|
42
48
|
const fs = __importStar(require("fs"));
|
|
43
49
|
const path = __importStar(require("path"));
|
|
44
50
|
const gray_matter_1 = __importDefault(require("gray-matter"));
|
|
51
|
+
const config_1 = require("./config");
|
|
52
|
+
// ─── Helpers ───────────────────────────────────────────────────────
|
|
45
53
|
function simpleHash(content) {
|
|
46
54
|
let hash = 0;
|
|
47
55
|
for (let i = 0; i < content.length; i++) {
|
|
48
56
|
const char = content.charCodeAt(i);
|
|
49
57
|
hash = ((hash << 5) - hash) + char;
|
|
50
|
-
hash = hash & hash;
|
|
58
|
+
hash = hash & hash;
|
|
51
59
|
}
|
|
52
60
|
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
53
61
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return slug
|
|
59
|
-
.split(/[-_]/)
|
|
60
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
61
|
-
.join(" ");
|
|
62
|
+
function readDirectory(dirPath) {
|
|
63
|
+
if (!fs.existsSync(dirPath))
|
|
64
|
+
return [];
|
|
65
|
+
return fs.readdirSync(dirPath).filter((f) => f.endsWith(".md")).sort();
|
|
62
66
|
}
|
|
63
67
|
function readMarkdownFile(filePath) {
|
|
64
68
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
65
69
|
const { data, content } = (0, gray_matter_1.default)(raw);
|
|
66
|
-
const slug =
|
|
67
|
-
const title = data.title ||
|
|
68
|
-
return {
|
|
69
|
-
slug,
|
|
70
|
-
title,
|
|
71
|
-
content: content.trim(),
|
|
72
|
-
metadata: data,
|
|
73
|
-
};
|
|
70
|
+
const slug = path.basename(filePath, ".md");
|
|
71
|
+
const title = data.title || slug.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
72
|
+
return { slug, title, content: content.trim(), metadata: data };
|
|
74
73
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
// ─── Section parsers ───────────────────────────────────────────────
|
|
75
|
+
function parseAboutMd(content) {
|
|
76
|
+
const lines = content.split("\n");
|
|
77
|
+
let name = "";
|
|
78
|
+
let tagline = "";
|
|
79
|
+
let location = "";
|
|
80
|
+
const bodyLines = [];
|
|
81
|
+
let foundName = false;
|
|
82
|
+
let seenNonEmpty = false;
|
|
83
|
+
for (const line of lines) {
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
if (trimmed.startsWith("# ")) {
|
|
86
|
+
name = trimmed.slice(2).trim();
|
|
87
|
+
foundName = true;
|
|
88
|
+
}
|
|
89
|
+
else if (trimmed.startsWith("*") && trimmed.endsWith("*") && !trimmed.startsWith("**")) {
|
|
90
|
+
location = trimmed.replace(/^\*|\*$/g, "").trim();
|
|
91
|
+
}
|
|
92
|
+
else if (!foundName && !trimmed) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
else if (foundName && !seenNonEmpty && !tagline && trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("-") && !trimmed.startsWith("**")) {
|
|
96
|
+
// First non-empty line after name that isn't location → tagline candidate
|
|
97
|
+
// Accept taglines even if they end with period (common in real usage)
|
|
98
|
+
if (trimmed.length < 120) {
|
|
99
|
+
tagline = trimmed;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
bodyLines.push(trimmed);
|
|
103
|
+
}
|
|
104
|
+
seenNonEmpty = true;
|
|
105
|
+
}
|
|
106
|
+
else if (trimmed) {
|
|
107
|
+
bodyLines.push(trimmed);
|
|
108
|
+
seenNonEmpty = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const long = bodyLines.join("\n").trim();
|
|
112
|
+
const medium = long.split("\n").slice(0, 3).join("\n").trim();
|
|
113
|
+
const short = long.split(/\.\s/)[0]?.trim() || medium.split("\n")[0]?.trim() || "";
|
|
114
|
+
// Restore the period if we split on it
|
|
115
|
+
const shortWithPeriod = short && long.startsWith(short) && long[short.length] === "." ? short + "." : short;
|
|
116
|
+
return { name, tagline, location, bio: { short: shortWithPeriod, medium, long } };
|
|
117
|
+
}
|
|
118
|
+
function parseProjectsMd(content) {
|
|
119
|
+
const projects = [];
|
|
120
|
+
const sections = content.split(/^## /m).filter(Boolean);
|
|
121
|
+
for (const section of sections) {
|
|
122
|
+
const lines = section.split("\n");
|
|
123
|
+
const name = lines[0]?.trim() || "";
|
|
124
|
+
if (!name || name.startsWith("#"))
|
|
125
|
+
continue;
|
|
126
|
+
let role = "";
|
|
127
|
+
let status = "active";
|
|
128
|
+
let url = "";
|
|
129
|
+
const descLines = [];
|
|
130
|
+
for (const line of lines.slice(1)) {
|
|
131
|
+
const trimmed = line.trim();
|
|
132
|
+
if (trimmed.match(/^\*?\*?Role:?\*?\*?\s*/i)) {
|
|
133
|
+
role = trimmed.replace(/^\*?\*?Role:?\*?\*?\s*/i, "").trim();
|
|
134
|
+
}
|
|
135
|
+
else if (trimmed.match(/^\*?\*?Status:?\*?\*?\s*/i)) {
|
|
136
|
+
status = trimmed.replace(/^\*?\*?Status:?\*?\*?\s*/i, "").trim();
|
|
137
|
+
}
|
|
138
|
+
else if (trimmed.match(/^\*?\*?URL:?\*?\*?\s*/i)) {
|
|
139
|
+
url = trimmed.replace(/^\*?\*?URL:?\*?\*?\s*/i, "").trim();
|
|
140
|
+
}
|
|
141
|
+
else if (trimmed.match(/^- Role:\s*/i)) {
|
|
142
|
+
role = trimmed.replace(/^- Role:\s*/i, "").trim();
|
|
143
|
+
}
|
|
144
|
+
else if (trimmed.match(/^- Status:\s*/i)) {
|
|
145
|
+
status = trimmed.replace(/^- Status:\s*/i, "").trim();
|
|
146
|
+
}
|
|
147
|
+
else if (trimmed.match(/^- URL:\s*/i)) {
|
|
148
|
+
url = trimmed.replace(/^- URL:\s*/i, "").trim();
|
|
149
|
+
}
|
|
150
|
+
else if (trimmed) {
|
|
151
|
+
descLines.push(trimmed);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
projects.push({
|
|
155
|
+
name,
|
|
156
|
+
role,
|
|
157
|
+
status,
|
|
158
|
+
url,
|
|
159
|
+
description: descLines.join("\n").trim(),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return projects;
|
|
163
|
+
}
|
|
164
|
+
function parseListMd(content) {
|
|
165
|
+
// Primary: bullet items (-, *, +)
|
|
166
|
+
const bullets = content.split("\n")
|
|
167
|
+
.map((l) => l.trim())
|
|
168
|
+
.filter((l) => /^[-*+]\s/.test(l))
|
|
169
|
+
.map((l) => l.replace(/^[-*+]\s+/, "").trim())
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
if (bullets.length > 0)
|
|
172
|
+
return bullets;
|
|
173
|
+
// Fallback: ## headings (common in values sections from onboarding)
|
|
174
|
+
const headings = content.split("\n")
|
|
175
|
+
.map((l) => l.trim())
|
|
176
|
+
.filter((l) => l.startsWith("## "))
|
|
177
|
+
.map((l) => l.slice(3).trim())
|
|
178
|
+
.filter(Boolean);
|
|
179
|
+
if (headings.length > 0)
|
|
180
|
+
return headings;
|
|
181
|
+
// Last resort: non-empty paragraphs that aren't headings or frontmatter
|
|
182
|
+
const paragraphs = content.split("\n")
|
|
183
|
+
.map((l) => l.trim())
|
|
184
|
+
.filter((l) => l && !l.startsWith("#") && !l.startsWith("---") && !l.startsWith("<!--") && !l.startsWith("("))
|
|
185
|
+
.map((l) => l.trim());
|
|
186
|
+
return paragraphs.length > 0 ? paragraphs : [];
|
|
187
|
+
}
|
|
188
|
+
function parseLinksMd(content) {
|
|
189
|
+
const links = {};
|
|
190
|
+
for (const line of content.split("\n")) {
|
|
191
|
+
const trimmed = line.trim();
|
|
192
|
+
// "- **platform**: url" or "- **platform:** url" or "- platform: url"
|
|
193
|
+
const boldMatch = trimmed.match(/^-\s+\*\*(.+?)\*\*:?\s+(.+)$/);
|
|
194
|
+
if (boldMatch) {
|
|
195
|
+
links[boldMatch[1].replace(/:$/, "").trim()] = boldMatch[2].trim();
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const simpleMatch = trimmed.match(/^-\s+(.+?):\s+(.+)$/);
|
|
199
|
+
if (simpleMatch) {
|
|
200
|
+
links[simpleMatch[1].trim()] = simpleMatch[2].trim();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return links;
|
|
204
|
+
}
|
|
205
|
+
function parseAgentPrefsMd(content) {
|
|
206
|
+
let tone = "";
|
|
207
|
+
let formality = "casual-professional";
|
|
208
|
+
let avoid = [];
|
|
209
|
+
for (const line of content.split("\n")) {
|
|
210
|
+
const trimmed = line.trim();
|
|
211
|
+
if (trimmed.match(/^\*?\*?Tone:?\*?\*?\s*/i)) {
|
|
212
|
+
tone = trimmed.replace(/^\*?\*?Tone:?\*?\*?\s*/i, "").trim();
|
|
213
|
+
}
|
|
214
|
+
else if (trimmed.match(/^\*?\*?Formality:?\*?\*?\s*/i)) {
|
|
215
|
+
formality = trimmed.replace(/^\*?\*?Formality:?\*?\*?\s*/i, "").trim();
|
|
216
|
+
}
|
|
217
|
+
else if (trimmed.match(/^\*?\*?Avoid:?\*?\*?\s*/i)) {
|
|
218
|
+
const avoidStr = trimmed.replace(/^\*?\*?Avoid:?\*?\*?\s*/i, "").trim();
|
|
219
|
+
avoid = avoidStr.split(",").map((a) => a.trim()).filter(Boolean);
|
|
220
|
+
}
|
|
221
|
+
else if (trimmed.match(/^Tone:\s*/i)) {
|
|
222
|
+
tone = trimmed.replace(/^Tone:\s*/i, "").trim();
|
|
223
|
+
}
|
|
224
|
+
else if (trimmed.match(/^Formality:\s*/i)) {
|
|
225
|
+
formality = trimmed.replace(/^Formality:\s*/i, "").trim();
|
|
226
|
+
}
|
|
227
|
+
else if (trimmed.match(/^Avoid:\s*/i)) {
|
|
228
|
+
const avoidStr = trimmed.replace(/^Avoid:\s*/i, "").trim();
|
|
229
|
+
avoid = avoidStr.split(",").map((a) => a.trim()).filter(Boolean);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { tone, formality, avoid };
|
|
233
|
+
}
|
|
234
|
+
function parseWritingPrefsMd(content) {
|
|
235
|
+
let style = "";
|
|
236
|
+
let format = "";
|
|
237
|
+
for (const line of content.split("\n")) {
|
|
238
|
+
const trimmed = line.trim();
|
|
239
|
+
if (trimmed.match(/^\*?\*?Style:?\*?\*?\s*/i)) {
|
|
240
|
+
style = trimmed.replace(/^\*?\*?Style:?\*?\*?\s*/i, "").trim();
|
|
241
|
+
}
|
|
242
|
+
else if (trimmed.match(/^\*?\*?Format:?\*?\*?\s*/i)) {
|
|
243
|
+
format = trimmed.replace(/^\*?\*?Format:?\*?\*?\s*/i, "").trim();
|
|
244
|
+
}
|
|
245
|
+
else if (trimmed.match(/^Style:\s*/i)) {
|
|
246
|
+
style = trimmed.replace(/^Style:\s*/i, "").trim();
|
|
247
|
+
}
|
|
248
|
+
else if (trimmed.match(/^Format:\s*/i)) {
|
|
249
|
+
format = trimmed.replace(/^Format:\s*/i, "").trim();
|
|
250
|
+
}
|
|
78
251
|
}
|
|
79
|
-
return
|
|
80
|
-
.readdirSync(dirPath)
|
|
81
|
-
.filter((f) => f.endsWith(".md"))
|
|
82
|
-
.sort();
|
|
252
|
+
return { style, format };
|
|
83
253
|
}
|
|
254
|
+
function parseVoiceMd(content) {
|
|
255
|
+
// Strip frontmatter heading, return the body
|
|
256
|
+
return content.replace(/^#\s+.+\n*/m, "").trim();
|
|
257
|
+
}
|
|
258
|
+
function parseDirectivesMd(content) {
|
|
259
|
+
let communication_style = "";
|
|
260
|
+
let negative_prompts = [];
|
|
261
|
+
let default_stack = "";
|
|
262
|
+
let decision_framework = "";
|
|
263
|
+
let current_goal = "";
|
|
264
|
+
const allLines = content.split("\n");
|
|
265
|
+
let inNeverBlock = false;
|
|
266
|
+
for (const line of allLines) {
|
|
267
|
+
const trimmed = line.trim();
|
|
268
|
+
if (trimmed.match(/^\*?\*?Communication Style:?\*?\*?\s*/i)) {
|
|
269
|
+
communication_style = trimmed.replace(/^\*?\*?Communication Style:?\*?\*?\s*/i, "").trim();
|
|
270
|
+
inNeverBlock = false;
|
|
271
|
+
}
|
|
272
|
+
else if (trimmed.match(/^\*?\*?Never:?\*?\*?\s*/i)) {
|
|
273
|
+
const inline = trimmed.replace(/^\*?\*?Never:?\*?\*?\s*/i, "").trim();
|
|
274
|
+
if (inline) {
|
|
275
|
+
// Inline format: "Never: don't ask permission. avoid jargon."
|
|
276
|
+
// Split on sentence boundaries but be careful with abbreviations
|
|
277
|
+
// Use comma-separation if commas present, otherwise sentence-split
|
|
278
|
+
if (inline.includes(",")) {
|
|
279
|
+
negative_prompts = inline.split(",").map((s) => s.trim().replace(/\.$/, "")).filter(Boolean);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
negative_prompts = inline.split(/(?<=[a-z])\.\s+/i).map((s) => s.trim().replace(/\.$/, "")).filter(Boolean);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
inNeverBlock = true;
|
|
286
|
+
}
|
|
287
|
+
else if (trimmed.match(/^\*?\*?Default Stack:?\*?\*?\s*/i)) {
|
|
288
|
+
default_stack = trimmed.replace(/^\*?\*?Default Stack:?\*?\*?\s*/i, "").trim();
|
|
289
|
+
inNeverBlock = false;
|
|
290
|
+
}
|
|
291
|
+
else if (trimmed.match(/^\*?\*?Decision Framework:?\*?\*?\s*/i)) {
|
|
292
|
+
decision_framework = trimmed.replace(/^\*?\*?Decision Framework:?\*?\*?\s*/i, "").trim();
|
|
293
|
+
inNeverBlock = false;
|
|
294
|
+
}
|
|
295
|
+
else if (trimmed.match(/^\*?\*?Current Goal:?\*?\*?\s*/i)) {
|
|
296
|
+
current_goal = trimmed.replace(/^\*?\*?Current Goal:?\*?\*?\s*/i, "").trim();
|
|
297
|
+
inNeverBlock = false;
|
|
298
|
+
}
|
|
299
|
+
else if (/^[-*+]\s/.test(trimmed)) {
|
|
300
|
+
// List item — collect as negative prompt if we're in a Never block,
|
|
301
|
+
// otherwise as a generic directive
|
|
302
|
+
const item = trimmed.replace(/^[-*+]\s+/, "").trim();
|
|
303
|
+
if (item) {
|
|
304
|
+
if (inNeverBlock) {
|
|
305
|
+
negative_prompts.push(item);
|
|
306
|
+
}
|
|
307
|
+
else if (!communication_style && !default_stack) {
|
|
308
|
+
// Unlabeled list items before any labeled field → generic directives
|
|
309
|
+
// Treat as communication style hints
|
|
310
|
+
negative_prompts.push(item);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else if (trimmed && !trimmed.startsWith("#")) {
|
|
315
|
+
// Non-labeled, non-list line — if we're in a never block, treat as continuation
|
|
316
|
+
if (inNeverBlock && trimmed) {
|
|
317
|
+
negative_prompts.push(trimmed);
|
|
318
|
+
}
|
|
319
|
+
// A labeled field resets the never block
|
|
320
|
+
if (trimmed.includes(":")) {
|
|
321
|
+
inNeverBlock = false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return { communication_style, negative_prompts, default_stack, decision_framework, current_goal };
|
|
326
|
+
}
|
|
327
|
+
// ─── Main compilation ──────────────────────────────────────────────
|
|
84
328
|
function compileBundle(bundleDir) {
|
|
85
329
|
const profileDir = path.join(bundleDir, "profile");
|
|
86
330
|
const preferencesDir = path.join(bundleDir, "preferences");
|
|
331
|
+
const voiceDir = path.join(bundleDir, "voice");
|
|
332
|
+
const directivesDir = path.join(bundleDir, "directives");
|
|
87
333
|
const profileFiles = readDirectory(profileDir);
|
|
88
334
|
const preferenceFiles = readDirectory(preferencesDir);
|
|
335
|
+
const voiceFiles = readDirectory(voiceDir);
|
|
336
|
+
const directiveFiles = readDirectory(directivesDir);
|
|
89
337
|
const filesRead = [];
|
|
90
|
-
// Read
|
|
338
|
+
// Read all files
|
|
91
339
|
const profileSections = profileFiles.map((file) => {
|
|
92
340
|
filesRead.push({ type: "profile", file });
|
|
93
341
|
return readMarkdownFile(path.join(profileDir, file));
|
|
94
342
|
});
|
|
95
|
-
|
|
96
|
-
const preferenceSections = preferenceFiles.map((file) => {
|
|
343
|
+
const prefSections = preferenceFiles.map((file) => {
|
|
97
344
|
filesRead.push({ type: "preference", file });
|
|
98
345
|
return readMarkdownFile(path.join(preferencesDir, file));
|
|
99
346
|
});
|
|
347
|
+
const voiceSections = voiceFiles.map((file) => {
|
|
348
|
+
filesRead.push({ type: "voice", file });
|
|
349
|
+
return readMarkdownFile(path.join(voiceDir, file));
|
|
350
|
+
});
|
|
351
|
+
const directiveSections = directiveFiles.map((file) => {
|
|
352
|
+
filesRead.push({ type: "directive", file });
|
|
353
|
+
return readMarkdownFile(path.join(directivesDir, file));
|
|
354
|
+
});
|
|
355
|
+
// Load existing skeleton from you.json or base.json to preserve fields we don't model
|
|
356
|
+
let skeleton = {};
|
|
357
|
+
const youJsonPath = path.join(bundleDir, "you.json");
|
|
358
|
+
const baseJsonPath = path.join(bundleDir, "base.json");
|
|
359
|
+
if (fs.existsSync(youJsonPath)) {
|
|
360
|
+
try {
|
|
361
|
+
const existing = JSON.parse(fs.readFileSync(youJsonPath, "utf-8"));
|
|
362
|
+
// Only use as skeleton if it's nested format (has identity or schema)
|
|
363
|
+
if (existing.identity || existing.schema === "you-md/v1") {
|
|
364
|
+
skeleton = existing;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch { /* ignore corrupt */ }
|
|
368
|
+
}
|
|
369
|
+
if (Object.keys(skeleton).length === 0 && fs.existsSync(baseJsonPath)) {
|
|
370
|
+
try {
|
|
371
|
+
skeleton = JSON.parse(fs.readFileSync(baseJsonPath, "utf-8"));
|
|
372
|
+
}
|
|
373
|
+
catch { /* ignore */ }
|
|
374
|
+
}
|
|
100
375
|
// Determine version
|
|
101
376
|
const manifestPath = path.join(bundleDir, "manifest.json");
|
|
102
377
|
let version = 1;
|
|
@@ -105,74 +380,214 @@ function compileBundle(bundleDir) {
|
|
|
105
380
|
const existingManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
106
381
|
version = (existingManifest.version || 0) + 1;
|
|
107
382
|
}
|
|
383
|
+
catch { /* version 1 */ }
|
|
384
|
+
}
|
|
385
|
+
const now = new Date().toISOString();
|
|
386
|
+
// Read username from skeleton, falling back to global config
|
|
387
|
+
let username = skeleton.username || "";
|
|
388
|
+
if (!username) {
|
|
389
|
+
try {
|
|
390
|
+
const globalConfig = (0, config_1.readGlobalConfig)();
|
|
391
|
+
username = globalConfig.username || "";
|
|
392
|
+
}
|
|
108
393
|
catch {
|
|
109
|
-
//
|
|
394
|
+
// config not available
|
|
110
395
|
}
|
|
111
396
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
397
|
+
// ── Parse profile files ────────────────────────────────────────
|
|
398
|
+
const aboutSection = profileSections.find((s) => s.slug === "about");
|
|
399
|
+
const projectsSection = profileSections.find((s) => s.slug === "projects");
|
|
400
|
+
const nowSection = profileSections.find((s) => s.slug === "now");
|
|
401
|
+
const valuesSection = profileSections.find((s) => s.slug === "values");
|
|
402
|
+
const linksSection = profileSections.find((s) => s.slug === "links");
|
|
403
|
+
const about = aboutSection ? parseAboutMd(aboutSection.content) : { name: "", tagline: "", location: "", bio: { short: "", medium: "", long: "" } };
|
|
404
|
+
const projects = projectsSection ? parseProjectsMd(projectsSection.content) : [];
|
|
405
|
+
const nowItems = nowSection ? parseListMd(nowSection.content) : [];
|
|
406
|
+
const values = valuesSection ? parseListMd(valuesSection.content) : [];
|
|
407
|
+
const links = linksSection ? parseLinksMd(linksSection.content) : {};
|
|
408
|
+
// Collect custom sections (any profile file not in the standard set)
|
|
409
|
+
const standardSlugs = new Set(["about", "now", "projects", "values", "links", "skills", "experience"]);
|
|
410
|
+
const customSections = profileSections
|
|
411
|
+
.filter((s) => !standardSlugs.has(s.slug))
|
|
412
|
+
.map((s) => ({ id: s.slug, title: s.title, content: s.content }));
|
|
413
|
+
// ── Parse preferences ──────────────────────────────────────────
|
|
414
|
+
const agentPrefSection = prefSections.find((s) => s.slug === "agent");
|
|
415
|
+
const writingPrefSection = prefSections.find((s) => s.slug === "writing");
|
|
416
|
+
const agentPrefs = agentPrefSection ? parseAgentPrefsMd(agentPrefSection.content) : { tone: "", formality: "casual-professional", avoid: [] };
|
|
417
|
+
const writingPrefs = writingPrefSection ? parseWritingPrefsMd(writingPrefSection.content) : { style: "", format: "" };
|
|
418
|
+
// ── Parse voice ────────────────────────────────────────────────
|
|
419
|
+
const voiceOverall = voiceSections.find((s) => s.slug === "voice");
|
|
420
|
+
const voicePlatforms = {};
|
|
421
|
+
for (const v of voiceSections) {
|
|
422
|
+
if (v.slug.startsWith("voice.")) {
|
|
423
|
+
const platform = v.slug.slice("voice.".length);
|
|
424
|
+
voicePlatforms[platform] = parseVoiceMd(v.content);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// ── Parse directives ───────────────────────────────────────────
|
|
428
|
+
const agentDirective = directiveSections.find((s) => s.slug === "agent");
|
|
429
|
+
const directives = agentDirective ? parseDirectivesMd(agentDirective.content) : {
|
|
430
|
+
communication_style: "", negative_prompts: [], default_stack: "", decision_framework: "", current_goal: ""
|
|
119
431
|
};
|
|
120
|
-
// Build
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
432
|
+
// ── Build youJson (nested server format) ───────────────────────
|
|
433
|
+
const youJson = {
|
|
434
|
+
schema: "you-md/v1",
|
|
435
|
+
username,
|
|
436
|
+
generated_at: now,
|
|
437
|
+
identity: {
|
|
438
|
+
name: about.name || skeleton?.identity?.name || "",
|
|
439
|
+
tagline: about.tagline || skeleton?.identity?.tagline || "",
|
|
440
|
+
location: about.location || skeleton?.identity?.location || "",
|
|
441
|
+
bio: {
|
|
442
|
+
short: about.bio.short || skeleton?.identity?.bio?.short || "",
|
|
443
|
+
medium: about.bio.medium || skeleton?.identity?.bio?.medium || "",
|
|
444
|
+
long: about.bio.long || skeleton?.identity?.bio?.long || "",
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
now: {
|
|
448
|
+
focus: nowItems.length > 0 ? nowItems : (skeleton?.now?.focus || []),
|
|
449
|
+
updated_at: now.split("T")[0],
|
|
450
|
+
},
|
|
451
|
+
projects: projects.length > 0 ? projects : (skeleton?.projects || []),
|
|
452
|
+
values: values.length > 0 ? values : (skeleton?.values || []),
|
|
453
|
+
links: Object.keys(links).length > 0 ? links : (skeleton?.links || {}),
|
|
454
|
+
preferences: {
|
|
455
|
+
agent: {
|
|
456
|
+
tone: agentPrefs.tone || skeleton?.preferences?.agent?.tone || "",
|
|
457
|
+
formality: agentPrefs.formality || skeleton?.preferences?.agent?.formality || "casual-professional",
|
|
458
|
+
avoid: agentPrefs.avoid.length > 0 ? agentPrefs.avoid : (skeleton?.preferences?.agent?.avoid || []),
|
|
459
|
+
},
|
|
460
|
+
writing: {
|
|
461
|
+
style: writingPrefs.style || skeleton?.preferences?.writing?.style || "",
|
|
462
|
+
format: writingPrefs.format || skeleton?.preferences?.writing?.format || "",
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
voice: {
|
|
466
|
+
overall: voiceOverall ? parseVoiceMd(voiceOverall.content) : (skeleton?.voice?.overall || ""),
|
|
467
|
+
platforms: {
|
|
468
|
+
linkedin: voicePlatforms.linkedin || skeleton?.voice?.platforms?.linkedin || null,
|
|
469
|
+
x: voicePlatforms.x || skeleton?.voice?.platforms?.x || null,
|
|
470
|
+
blog: voicePlatforms.blog || skeleton?.voice?.platforms?.blog || null,
|
|
471
|
+
...(Object.fromEntries(Object.entries(voicePlatforms).filter(([k]) => !["linkedin", "x", "blog"].includes(k)))),
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
analysis: skeleton?.analysis || {
|
|
475
|
+
topics: [],
|
|
476
|
+
voice_summary: voiceOverall ? parseVoiceMd(voiceOverall.content) : "",
|
|
477
|
+
credibility_signals: [],
|
|
478
|
+
},
|
|
479
|
+
social_images: skeleton?.social_images || {},
|
|
480
|
+
agent_directives: {
|
|
481
|
+
communication_style: directives.communication_style || skeleton?.agent_directives?.communication_style || "",
|
|
482
|
+
negative_prompts: directives.negative_prompts.length > 0 ? directives.negative_prompts : (skeleton?.agent_directives?.negative_prompts || []),
|
|
483
|
+
default_stack: directives.default_stack || skeleton?.agent_directives?.default_stack || "",
|
|
484
|
+
decision_framework: directives.decision_framework || skeleton?.agent_directives?.decision_framework || "",
|
|
485
|
+
current_goal: directives.current_goal || skeleton?.agent_directives?.current_goal || "",
|
|
486
|
+
},
|
|
487
|
+
agent_guide: skeleton?.agent_guide || {
|
|
488
|
+
summary: "this is a you-md/v1 identity context protocol. use it to understand who this person is before working with them.",
|
|
489
|
+
quick_context: [
|
|
490
|
+
"identity.bio.short -- one-line summary",
|
|
491
|
+
"now.focus -- what they're working on right now",
|
|
492
|
+
"agent_directives -- behavioral instructions for how to interact",
|
|
493
|
+
"preferences.agent -- communication tone preferences",
|
|
494
|
+
"projects -- their active projects with context",
|
|
495
|
+
"voice.overall -- their communication style",
|
|
496
|
+
],
|
|
497
|
+
for_writing: "check preferences.writing and voice.platforms for platform-specific style",
|
|
498
|
+
for_coding: "check projects for tech stack context, agent_directives.default_stack for preferred stack",
|
|
499
|
+
for_research: "check analysis.topics and links for their areas of expertise",
|
|
500
|
+
},
|
|
501
|
+
custom_sections: customSections.length > 0 ? customSections : (skeleton?.custom_sections || []),
|
|
502
|
+
meta: {
|
|
503
|
+
sources_used: skeleton?.meta?.sources_used || [],
|
|
504
|
+
last_updated: now,
|
|
505
|
+
compiler_version: "0.5.0",
|
|
506
|
+
},
|
|
507
|
+
verification: skeleton?.verification || null,
|
|
508
|
+
};
|
|
509
|
+
// ── Build you.md ───────────────────────────────────────────────
|
|
510
|
+
const mdParts = [];
|
|
511
|
+
mdParts.push(`---\nschema: you-md/v1\nname: ${about.name || username}\nusername: ${username}\ngenerated_at: ${now}\n---`);
|
|
512
|
+
mdParts.push(`\n# ${about.name || username}`);
|
|
513
|
+
if (about.tagline)
|
|
514
|
+
mdParts.push(about.tagline);
|
|
515
|
+
if (about.location)
|
|
516
|
+
mdParts.push(`*${about.location}*`);
|
|
517
|
+
if (about.bio.long)
|
|
518
|
+
mdParts.push(`\n## About\n\n${about.bio.long}`);
|
|
519
|
+
if (nowItems.length > 0)
|
|
520
|
+
mdParts.push(`\n## Now\n\n${nowItems.map((f) => `- ${f}`).join("\n")}`);
|
|
521
|
+
if (projects.length > 0) {
|
|
522
|
+
const pLines = projects.map((p) => `- **${p.name}**${p.description ? ` -- ${p.description}` : ""}${p.role ? ` (${p.role})` : ""}`);
|
|
523
|
+
mdParts.push(`\n## Projects\n\n${pLines.join("\n")}`);
|
|
157
524
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
525
|
+
if (values.length > 0)
|
|
526
|
+
mdParts.push(`\n## Values\n\n${values.map((v) => `- ${v}`).join("\n")}`);
|
|
527
|
+
if (agentPrefs.tone) {
|
|
528
|
+
const prefLines = [];
|
|
529
|
+
prefLines.push(`Tone: ${agentPrefs.tone}`);
|
|
530
|
+
if (agentPrefs.avoid.length > 0)
|
|
531
|
+
prefLines.push(`Avoid: ${agentPrefs.avoid.join(", ")}`);
|
|
532
|
+
mdParts.push(`\n## Agent Preferences\n\n${prefLines.join("\n")}`);
|
|
533
|
+
}
|
|
534
|
+
const linkEntries = Object.entries(links).filter(([, url]) => url);
|
|
535
|
+
if (linkEntries.length > 0)
|
|
536
|
+
mdParts.push(`\n## Links\n\n${linkEntries.map(([p, u]) => `- ${p}: ${u}`).join("\n")}`);
|
|
537
|
+
if (voiceOverall)
|
|
538
|
+
mdParts.push(`\n## Voice\n\n${parseVoiceMd(voiceOverall.content)}`);
|
|
539
|
+
for (const cs of customSections) {
|
|
540
|
+
mdParts.push(`\n## ${cs.title}\n\n${cs.content}`);
|
|
541
|
+
}
|
|
542
|
+
mdParts.push(`\n---\n\n> **For agents**: this is a you-md/v1 identity context protocol.\n> Quick context: check identity.bio.short, now.focus, and preferences.agent.\n> For writing help: check voice section and preferences.writing.\n> Full structured data: see you.json.`);
|
|
543
|
+
const markdown = mdParts.join("\n") + "\n";
|
|
544
|
+
// ── Build manifest ─────────────────────────────────────────────
|
|
545
|
+
const manifestEntries = [];
|
|
546
|
+
const allDirs = [
|
|
547
|
+
[profileDir, "profile", profileFiles],
|
|
548
|
+
[preferencesDir, "preference", preferenceFiles],
|
|
549
|
+
[voiceDir, "voice", voiceFiles],
|
|
550
|
+
[directivesDir, "directive", directiveFiles],
|
|
551
|
+
];
|
|
552
|
+
for (const [dir, type, files] of allDirs) {
|
|
553
|
+
for (const file of files) {
|
|
554
|
+
const content = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
555
|
+
const dirName = path.basename(dir);
|
|
556
|
+
manifestEntries.push({
|
|
557
|
+
file: `${dirName}/${file}`,
|
|
558
|
+
type,
|
|
559
|
+
slug: path.basename(file, ".md"),
|
|
560
|
+
hash: simpleHash(content),
|
|
561
|
+
});
|
|
562
|
+
}
|
|
166
563
|
}
|
|
167
564
|
const manifest = {
|
|
168
565
|
version,
|
|
169
|
-
generatedAt,
|
|
566
|
+
generatedAt: now,
|
|
170
567
|
entries: manifestEntries,
|
|
171
568
|
};
|
|
172
|
-
|
|
569
|
+
// Count filled sections
|
|
570
|
+
const allSections = [...profileSections, ...prefSections, ...voiceSections, ...directiveSections];
|
|
571
|
+
const filledSections = allSections.filter((s) => s.content.split("\n").filter((l) => l.trim() && !l.startsWith("<!--") && !l.startsWith("(")).length > 0).length;
|
|
572
|
+
const directories = ["profile", "preferences", "voice", "directives"].filter((d) => {
|
|
573
|
+
const dir = path.join(bundleDir, d);
|
|
574
|
+
return fs.existsSync(dir) && readDirectory(dir).length > 0;
|
|
575
|
+
});
|
|
576
|
+
return {
|
|
577
|
+
youJson,
|
|
578
|
+
markdown,
|
|
579
|
+
manifest,
|
|
580
|
+
filesRead,
|
|
581
|
+
stats: {
|
|
582
|
+
version,
|
|
583
|
+
totalSections: allSections.length,
|
|
584
|
+
filledSections,
|
|
585
|
+
directories,
|
|
586
|
+
},
|
|
587
|
+
};
|
|
173
588
|
}
|
|
174
589
|
function writeBundle(bundleDir, result) {
|
|
175
|
-
fs.writeFileSync(path.join(bundleDir, "you.json"), JSON.stringify(result.
|
|
590
|
+
fs.writeFileSync(path.join(bundleDir, "you.json"), JSON.stringify(result.youJson, null, 2) + "\n");
|
|
176
591
|
fs.writeFileSync(path.join(bundleDir, "you.md"), result.markdown);
|
|
177
592
|
fs.writeFileSync(path.join(bundleDir, "manifest.json"), JSON.stringify(result.manifest, null, 2) + "\n");
|
|
178
593
|
}
|