youmd 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,15 +36,195 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.RESEARCH_URL = exports.SCRAPE_URL = exports.CHAT_PROXY_URL = exports.BUNDLE_SECTIONS = exports.SYSTEM_PROMPT = exports.Spinner = void 0;
39
40
  exports.runOnboarding = runOnboarding;
40
41
  exports.createBundle = createBundle;
42
+ exports.callLLM = callLLM;
43
+ exports.parseUpdatesFromResponse = parseUpdatesFromResponse;
44
+ exports.writeSectionFile = writeSectionFile;
45
+ exports.sectionLabel = sectionLabel;
46
+ exports.showBundlePreview = showBundlePreview;
47
+ exports.fetchWebsiteContent = fetchWebsiteContent;
48
+ exports.scrapeProfile = scrapeProfile;
49
+ exports.researchUser = researchUser;
50
+ exports.getOpenRouterKey = getOpenRouterKey;
51
+ exports.randomThinking = randomThinking;
41
52
  const readline = __importStar(require("readline"));
42
53
  const fs = __importStar(require("fs"));
43
54
  const path = __importStar(require("path"));
55
+ const os = __importStar(require("os"));
44
56
  const chalk_1 = __importDefault(require("chalk"));
45
57
  const config_1 = require("./config");
46
58
  const compiler_1 = require("./compiler");
47
- // ─── Prompt helpers ──────────────────────────────────────────────────
59
+ // ─── Constants ────────────────────────────────────────────────────────
60
+ const CHAT_PROXY_URL = "https://kindly-cassowary-600.convex.site/api/v1/chat";
61
+ exports.CHAT_PROXY_URL = CHAT_PROXY_URL;
62
+ const SCRAPE_URL = "https://kindly-cassowary-600.convex.site/api/v1/scrape";
63
+ exports.SCRAPE_URL = SCRAPE_URL;
64
+ const RESEARCH_URL = "https://kindly-cassowary-600.convex.site/api/v1/research";
65
+ exports.RESEARCH_URL = RESEARCH_URL;
66
+ const OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions";
67
+ const OPENROUTER_MODEL = "anthropic/claude-sonnet-4";
68
+ const USERNAME_RE = /^[a-z0-9][a-z0-9_-]{1,38}[a-z0-9]$/;
69
+ const THINKING_PHRASES = [
70
+ "reading your about page like a respectful detective",
71
+ "absorbing your linkedin energy",
72
+ "scanning your timeline vibe",
73
+ "learning how you think out loud",
74
+ "decoding your digital footprint",
75
+ "mapping your professional universe",
76
+ "translating you into agent-speak",
77
+ "assembling the puzzle pieces",
78
+ "teaching LLMs about you",
79
+ "studying your sentence structure",
80
+ "finding your narrative thread",
81
+ "distilling your essence",
82
+ "cataloging your side projects",
83
+ "indexing your strong opinions",
84
+ "capturing your voice signature",
85
+ "building your identity constellation",
86
+ "compiling your context bundle",
87
+ "downloading your professional soul",
88
+ "converting vibes to structured data",
89
+ "weaving your story",
90
+ "crystallizing your identity",
91
+ "grokking your whole deal",
92
+ "connecting the dots",
93
+ "processing your essence",
94
+ "structuring your identity",
95
+ "learning you",
96
+ "calibrating to your wavelength",
97
+ "reading between your lines",
98
+ "mapping your expertise graph",
99
+ "analyzing your voice patterns",
100
+ "extracting your narrative arcs",
101
+ "building your knowledge panel",
102
+ "computing your identity fingerprint",
103
+ "synthesizing your public presence",
104
+ "parsing your career trajectory",
105
+ "understanding your builder instinct",
106
+ "decrypting your communication style",
107
+ "profiling your creative output",
108
+ "charting your professional constellation",
109
+ "rendering you in agent-speak",
110
+ "encoding your identity bundle",
111
+ "absorbing your origin story",
112
+ "discovering what makes you tick",
113
+ "mapping your values system",
114
+ "tracing your impact footprint",
115
+ "archiving your digital presence",
116
+ "curating your identity artifacts",
117
+ "assembling your context mosaic",
118
+ "beaming you up to the agent internet",
119
+ "initializing your identity protocol",
120
+ "reverse-engineering your personality",
121
+ "calculating your main character energy",
122
+ "triangulating your vibe",
123
+ "cross-referencing your footnotes",
124
+ "auditing your digital paper trail",
125
+ "interviewing your web presence",
126
+ "surveying your corner of the internet",
127
+ "speed-reading your life story",
128
+ "taking notes on your trajectory",
129
+ "measuring your signal-to-noise ratio",
130
+ "detecting your communication wavelength",
131
+ "parsing your professional DNA",
132
+ "sketching your identity blueprint",
133
+ "constructing your context scaffold",
134
+ "loading your universe into memory",
135
+ "generating your identity hash",
136
+ "composing your digital portrait",
137
+ "calibrating the you.md protocol",
138
+ "translating your experience into structure",
139
+ "wiring your identity into the network",
140
+ ];
141
+ const DONE_PHRASES = [
142
+ "done",
143
+ "publish",
144
+ "that's it",
145
+ "thats it",
146
+ "looks good",
147
+ "i'm done",
148
+ "im done",
149
+ "ship it",
150
+ "good enough",
151
+ "let's go",
152
+ "lets go",
153
+ "ready",
154
+ "finish",
155
+ "all good",
156
+ "that's all",
157
+ "thats all",
158
+ "nothing else",
159
+ "nah",
160
+ "no",
161
+ "nope",
162
+ ];
163
+ const BUNDLE_SECTIONS = [
164
+ "profile/about.md",
165
+ "profile/now.md",
166
+ "profile/projects.md",
167
+ "profile/values.md",
168
+ "profile/links.md",
169
+ "preferences/agent.md",
170
+ "preferences/writing.md",
171
+ ];
172
+ exports.BUNDLE_SECTIONS = BUNDLE_SECTIONS;
173
+ const SYSTEM_PROMPT = `you are the you.md agent. you help humans build their identity file for the agent internet. you are their first AI that truly knows them.
174
+
175
+ personality:
176
+ - warm but not gushy. direct. a dash of dry wit when it lands naturally.
177
+ - genuinely curious about people — you actually want to learn what makes them tick.
178
+ - terminal-native tone: lowercase, no exclamation marks, no emoji, short sentences.
179
+ - proactive — don't just wait for answers, connect dots, make observations, suggest things.
180
+ - reference specific things you learn about them. make them feel seen.
181
+ - you're like a sharp coworker who's also a great listener.
182
+
183
+ you're building a you-md/v1 identity bundle. the sections are:
184
+ - profile/about.md — bio, background, narrative
185
+ - profile/now.md — current focus, what they're working on right now
186
+ - profile/projects.md — active projects with details
187
+ - profile/values.md — core values and principles
188
+ - profile/links.md — annotated links (website, socials, repos)
189
+ - preferences/agent.md — how AI agents should interact with them
190
+ - preferences/writing.md — their communication style
191
+
192
+ your job:
193
+ 1. analyze what you know about the person from their URLs and conversation
194
+ 2. ask follow-up questions to fill gaps — be conversational, not interrogative
195
+ 3. after each exchange, output structured updates as JSON blocks:
196
+ \`\`\`json
197
+ {"updates": [{"section": "profile/about.md", "content": "...markdown content..."}]}
198
+ \`\`\`
199
+ 4. keep the conversation going until you have enough for a rich identity bundle
200
+ 5. never tell the user to edit markdown files themselves — you handle all of that
201
+ 6. reference specific things you learned about them
202
+ 7. if someone shares links, immediately offer to pull context from them
203
+ 8. be proactive: "i noticed you mentioned X — want me to add that to your projects?"
204
+ 9. occasionally remind them of what you've captured so far
205
+
206
+ conversational style examples:
207
+ - "cool. let me go read your site."
208
+ - "ok so you're basically a linkedin whisperer. noted."
209
+ - "that's a solid stack. let me capture that."
210
+ - "interesting — so you're more on the strategy side than pure engineering?"
211
+ - "i've got a good picture of what you do. want to tell me what you actually care about?"
212
+ - "that's a lot of projects. which one keeps you up at night?"
213
+ - "your bundle is looking solid. ready to publish, or should we keep going?"
214
+
215
+ rules for content in updates:
216
+ - each section must start with a YAML frontmatter block (--- title: "SectionTitle" ---)
217
+ - content should be real markdown, not HTML comments or placeholders
218
+ - be substantive. write real prose based on what you know.
219
+ - for links.md, format as: - **Label**: URL — brief annotation
220
+ - for agent.md, describe how agents should interact with this person
221
+ - for writing.md, capture their tone/style from how they've been talking to you
222
+
223
+ when you think the profile is rich enough (at least about, now, projects, and values have substance), suggest finishing by saying something like "your bundle is looking solid. ready to publish, or want to keep going?"
224
+
225
+ important: keep responses concise. 2-4 sentences max per turn. ask one good question at a time, not a list. be a conversation, not a questionnaire.`;
226
+ exports.SYSTEM_PROMPT = SYSTEM_PROMPT;
227
+ // ─── Helpers ──────────────────────────────────────────────────────────
48
228
  function createRL() {
49
229
  return readline.createInterface({
50
230
  input: process.stdin,
@@ -58,18 +238,18 @@ function ask(rl, question) {
58
238
  });
59
239
  });
60
240
  }
61
- function sleep(ms) {
62
- return new Promise((resolve) => setTimeout(resolve, ms));
241
+ function randomThinking() {
242
+ return THINKING_PHRASES[Math.floor(Math.random() * THINKING_PHRASES.length)];
243
+ }
244
+ function isDonePhrase(input) {
245
+ const lower = input.toLowerCase().trim();
246
+ return DONE_PHRASES.some((p) => lower === p || lower.startsWith(p + " "));
63
247
  }
64
- // ─── Username validation ─────────────────────────────────────────────
65
- const USERNAME_RE = /^[a-z0-9][a-z0-9_-]{1,38}[a-z0-9]$/;
66
248
  function validateUsernameLocal(username) {
67
- if (username.length < 3) {
249
+ if (username.length < 3)
68
250
  return "must be at least 3 characters";
69
- }
70
- if (username.length > 40) {
251
+ if (username.length > 40)
71
252
  return "must be 40 characters or fewer";
72
- }
73
253
  if (!USERNAME_RE.test(username)) {
74
254
  return "lowercase letters, numbers, hyphens, and underscores only";
75
255
  }
@@ -79,110 +259,635 @@ async function checkUsernameRemote(username) {
79
259
  try {
80
260
  const url = `https://kindly-cassowary-600.convex.site/api/v1/check-username?username=${encodeURIComponent(username)}`;
81
261
  const res = await fetch(url);
82
- if (!res.ok) {
83
- return { available: true, reason: null }; // fail open
84
- }
262
+ if (!res.ok)
263
+ return { available: true, reason: null };
85
264
  const data = await res.json();
86
265
  return data;
87
266
  }
88
267
  catch {
89
- // Network error -- don't block onboarding
90
268
  return { available: true, reason: null };
91
269
  }
92
270
  }
93
- // ─── File content generators ─────────────────────────────────────────
94
- function generateAboutMd(name, tagline) {
95
- return `---
96
- title: "About"
97
- ---
98
-
99
- # ${name}
100
-
101
- ${tagline}
102
- `;
271
+ function getOpenRouterKey() {
272
+ if (process.env.OPENROUTER_API_KEY) {
273
+ return process.env.OPENROUTER_API_KEY;
274
+ }
275
+ const configPath = path.join(os.homedir(), ".youmd", "config.json");
276
+ try {
277
+ if (fs.existsSync(configPath)) {
278
+ const raw = fs.readFileSync(configPath, "utf-8");
279
+ const config = JSON.parse(raw);
280
+ if (config.openrouterKey)
281
+ return config.openrouterKey;
282
+ }
283
+ }
284
+ catch {
285
+ // ignore
286
+ }
287
+ return null;
103
288
  }
104
- function generateLinksMd(links) {
105
- const lines = [
106
- '---',
107
- 'title: "Links"',
108
- '---',
109
- '',
110
- ];
111
- if (links.website) {
112
- lines.push(`- **Website**: ${links.website}`);
289
+ // ─── Website fetcher ──────────────────────────────────────────────────
290
+ async function fetchWebsiteContent(url) {
291
+ try {
292
+ const normalizedUrl = url.startsWith("http://") || url.startsWith("https://")
293
+ ? url
294
+ : "https://" + url;
295
+ const res = await fetch(normalizedUrl, {
296
+ headers: { "User-Agent": "youmd-bot/1.0" },
297
+ signal: AbortSignal.timeout(10000),
298
+ });
299
+ const html = await res.text();
300
+ const text = html
301
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
302
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
303
+ .replace(/<[^>]+>/g, " ")
304
+ .replace(/\s+/g, " ")
305
+ .trim();
306
+ return text.slice(0, 4000);
113
307
  }
114
- if (links.linkedin) {
115
- lines.push(`- **LinkedIn**: ${links.linkedin}`);
308
+ catch {
309
+ return "";
116
310
  }
117
- if (links.twitter) {
118
- lines.push(`- **X/Twitter**: ${links.twitter}`);
311
+ }
312
+ async function scrapeProfile(url) {
313
+ try {
314
+ const res = await fetch(SCRAPE_URL, {
315
+ method: "POST",
316
+ headers: { "Content-Type": "application/json" },
317
+ body: JSON.stringify({ url }),
318
+ signal: AbortSignal.timeout(30000),
319
+ });
320
+ if (!res.ok)
321
+ return null;
322
+ const data = await res.json();
323
+ return data;
119
324
  }
120
- if (!links.website && !links.linkedin && !links.twitter) {
121
- lines.push("<!-- Add your links here -->");
325
+ catch {
326
+ return null;
122
327
  }
123
- lines.push("");
124
- return lines.join("\n");
125
328
  }
126
- function generateNowMd() {
127
- return `---
128
- title: "Now"
129
- ---
130
-
131
- <!-- What are you working on right now? What's top of mind? -->
132
- `;
329
+ async function researchUser(params) {
330
+ try {
331
+ const res = await fetch(RESEARCH_URL, {
332
+ method: "POST",
333
+ headers: { "Content-Type": "application/json" },
334
+ body: JSON.stringify(params),
335
+ signal: AbortSignal.timeout(60000),
336
+ });
337
+ if (!res.ok)
338
+ return null;
339
+ const data = await res.json();
340
+ return data;
341
+ }
342
+ catch {
343
+ return null;
344
+ }
133
345
  }
134
- function generateProjectsMd() {
135
- return `---
136
- title: "Projects"
137
- ---
138
-
139
- <!-- List your current and past projects. What have you built? -->
140
- `;
346
+ function displayScrapeResult(label, data) {
347
+ console.log("");
348
+ console.log(" " + chalk_1.default.bold(`${label} profile:`));
349
+ if (data.name)
350
+ console.log(" name: " + chalk_1.default.cyan(data.name));
351
+ if (data.bio)
352
+ console.log(" bio: " + chalk_1.default.dim(data.bio.slice(0, 120)));
353
+ if (data.followers !== undefined)
354
+ console.log(" followers: " + chalk_1.default.cyan(String(data.followers)));
355
+ if (data.following !== undefined)
356
+ console.log(" following: " + chalk_1.default.cyan(String(data.following)));
357
+ if (data.location)
358
+ console.log(" location: " + chalk_1.default.dim(data.location));
359
+ if (data.website)
360
+ console.log(" website: " + chalk_1.default.dim(data.website));
361
+ console.log("");
141
362
  }
142
- function generateValuesMd() {
143
- return `---
144
- title: "Values"
145
- ---
146
-
147
- <!-- What do you care about? What principles guide your work? -->
148
- `;
363
+ // ─── Spinner ──────────────────────────────────────────────────────────
364
+ class Spinner {
365
+ constructor(label) {
366
+ this.interval = null;
367
+ this.frames = [" ", ". ", ".. ", "..."];
368
+ this.frameIndex = 0;
369
+ this.label = label || randomThinking();
370
+ }
371
+ start() {
372
+ process.stdout.write(chalk_1.default.dim(` ${this.label}...`));
373
+ this.interval = setInterval(() => {
374
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
375
+ const f = this.frames[this.frameIndex];
376
+ process.stdout.write(`\r${chalk_1.default.dim(` ${this.label}${f}`)}`);
377
+ }, 300);
378
+ }
379
+ stop() {
380
+ if (this.interval) {
381
+ clearInterval(this.interval);
382
+ this.interval = null;
383
+ }
384
+ process.stdout.write("\r" + " ".repeat(80) + "\r");
385
+ }
149
386
  }
150
- function generateAgentMd() {
151
- return `---
152
- title: "Agent"
153
- ---
154
-
155
- <!-- How should AI agents interact with your identity? -->
156
- <!-- What context should they have? What permissions do they get? -->
157
- `;
387
+ exports.Spinner = Spinner;
388
+ async function callLLM(apiKey, messages) {
389
+ // Try the you.md proxy first (no API key needed)
390
+ try {
391
+ const proxyRes = await fetch(CHAT_PROXY_URL, {
392
+ method: "POST",
393
+ headers: { "Content-Type": "application/json" },
394
+ body: JSON.stringify({ messages }),
395
+ signal: AbortSignal.timeout(60000),
396
+ });
397
+ if (proxyRes.ok) {
398
+ const proxyData = (await proxyRes.json());
399
+ if (proxyData.content)
400
+ return proxyData.content;
401
+ }
402
+ }
403
+ catch {
404
+ // Proxy failed — fall through to direct call if key available
405
+ }
406
+ // Fall back to direct OpenRouter call if user has their own key
407
+ if (!apiKey) {
408
+ throw new Error("Chat service unavailable");
409
+ }
410
+ const res = await fetch(OPENROUTER_URL, {
411
+ method: "POST",
412
+ headers: {
413
+ Authorization: `Bearer ${apiKey}`,
414
+ "Content-Type": "application/json",
415
+ "HTTP-Referer": "https://you.md",
416
+ "X-Title": "you.md CLI",
417
+ },
418
+ body: JSON.stringify({
419
+ model: OPENROUTER_MODEL,
420
+ messages,
421
+ max_tokens: 4096,
422
+ temperature: 0.7,
423
+ }),
424
+ signal: AbortSignal.timeout(60000),
425
+ });
426
+ if (!res.ok) {
427
+ const body = await res.text();
428
+ throw new Error(`OpenRouter API error (${res.status}): ${body}`);
429
+ }
430
+ const data = (await res.json());
431
+ if (!data.choices || data.choices.length === 0) {
432
+ throw new Error("Empty response from OpenRouter");
433
+ }
434
+ return data.choices[0].message.content;
435
+ }
436
+ function parseUpdatesFromResponse(text) {
437
+ // Try to extract JSON block with {"updates": [...]}
438
+ const jsonMatch = text.match(/```json\s*\n([\s\S]*?)\n```/);
439
+ let updates = [];
440
+ let display = text;
441
+ if (jsonMatch) {
442
+ // Remove the JSON block from display text
443
+ display = text.replace(/```json\s*\n[\s\S]*?\n```/, "").trim();
444
+ try {
445
+ const parsed = JSON.parse(jsonMatch[1]);
446
+ // Handle both formats: {"updates": [...]} and bare [...]
447
+ const arr = Array.isArray(parsed)
448
+ ? parsed
449
+ : Array.isArray(parsed.updates)
450
+ ? parsed.updates
451
+ : [];
452
+ updates = arr.filter((u) => u &&
453
+ typeof u.section === "string" &&
454
+ typeof u.content === "string" &&
455
+ BUNDLE_SECTIONS.includes(u.section));
456
+ }
457
+ catch {
458
+ // Failed to parse JSON updates -- continue without them
459
+ }
460
+ }
461
+ return { display, updates };
462
+ }
463
+ function writeSectionFile(bundleDir, section, content) {
464
+ const filePath = path.join(bundleDir, section);
465
+ const dir = path.dirname(filePath);
466
+ if (!fs.existsSync(dir)) {
467
+ fs.mkdirSync(dir, { recursive: true });
468
+ }
469
+ fs.writeFileSync(filePath, content);
470
+ }
471
+ function sectionLabel(section) {
472
+ const name = path.basename(section, ".md");
473
+ const dir = path.dirname(section);
474
+ return `${dir}/${name}`;
158
475
  }
159
- function generateWritingMd() {
160
- return `---
161
- title: "Writing"
476
+ // ─── Profile preview box ──────────────────────────────────────────────
477
+ function showProfileBox(name, headline, tags) {
478
+ const lines = [name, headline, tags].filter(Boolean);
479
+ const maxLen = Math.max(...lines.map((l) => l.length), 40);
480
+ const width = maxLen + 4;
481
+ console.log("");
482
+ console.log(" " + chalk_1.default.dim("\u250C" + "\u2500".repeat(width) + "\u2510"));
483
+ for (const line of lines) {
484
+ const padded = line + " ".repeat(width - line.length - 2);
485
+ console.log(" " + chalk_1.default.dim("\u2502") + " " + padded + chalk_1.default.dim("\u2502"));
486
+ }
487
+ console.log(" " + chalk_1.default.dim("\u2514" + "\u2500".repeat(width) + "\u2518"));
488
+ console.log("");
489
+ }
490
+ // ─── Bundle preview ───────────────────────────────────────────────────
491
+ function showBundlePreview(bundleDir) {
492
+ let fileCount = 0;
493
+ let filledCount = 0;
494
+ console.log("");
495
+ console.log(" " + chalk_1.default.bold("your identity bundle:"));
496
+ console.log("");
497
+ const sections = [
498
+ { dir: "profile", label: "profile" },
499
+ { dir: "preferences", label: "preferences" },
500
+ ];
501
+ for (const { dir, label } of sections) {
502
+ const dirPath = path.join(bundleDir, dir);
503
+ if (!fs.existsSync(dirPath))
504
+ continue;
505
+ const files = fs
506
+ .readdirSync(dirPath)
507
+ .filter((f) => f.endsWith(".md"))
508
+ .sort();
509
+ if (files.length === 0)
510
+ continue;
511
+ console.log(" " + chalk_1.default.bold(label));
512
+ for (let i = 0; i < files.length; i++) {
513
+ const file = files[i];
514
+ fileCount++;
515
+ const filePath = path.join(dirPath, file);
516
+ const raw = fs.readFileSync(filePath, "utf-8");
517
+ const contentLines = raw
518
+ .replace(/---[\s\S]*?---/, "")
519
+ .trim()
520
+ .split("\n");
521
+ const preview = contentLines[0] && contentLines[0].replace(/^#+\s*/, "").trim();
522
+ const isLast = i === files.length - 1;
523
+ const connector = isLast ? "\\--" : "|--";
524
+ const name = path.basename(file, ".md");
525
+ const hasContent = contentLines.filter((l) => l.trim() && !l.startsWith("<!--")).length > 0;
526
+ if (hasContent)
527
+ filledCount++;
528
+ console.log(` ${connector} ${chalk_1.default.cyan(name + ".md")}${hasContent ? chalk_1.default.dim(" -- " + (preview || "").slice(0, 50)) : chalk_1.default.dim(" (empty)")}`);
529
+ }
530
+ console.log("");
531
+ }
532
+ return { fileCount, filledCount };
533
+ }
534
+ async function runFallbackMode(rl, info) {
535
+ const bundleDir = (0, config_1.getLocalBundleDir)();
536
+ const profileDir = path.join(bundleDir, "profile");
537
+ const preferencesDir = path.join(bundleDir, "preferences");
538
+ fs.mkdirSync(profileDir, { recursive: true });
539
+ fs.mkdirSync(preferencesDir, { recursive: true });
540
+ console.log("");
541
+ console.log(chalk_1.default.dim(" chat service unavailable. running in manual mode."));
542
+ console.log("");
543
+ const tagline = await ask(rl, chalk_1.default.green(" > ") + "give me your one-liner. what do you do? ");
544
+ const nowFocus = await ask(rl, chalk_1.default.green(" > ") + "what are you focused on right now? ");
545
+ const projects = await ask(rl, chalk_1.default.green(" > ") + "name your top projects (comma-separated): ");
546
+ const values = await ask(rl, chalk_1.default.green(" > ") + "what principles guide your work? ");
547
+ const agentPrefs = await ask(rl, chalk_1.default.green(" > ") +
548
+ "how should AI agents talk to you? (e.g., direct, casual, formal): ");
549
+ writeSectionFile(bundleDir, "profile/about.md", `---\ntitle: "About"\n---\n\n# ${info.name}\n\n${tagline}\n`);
550
+ writeSectionFile(bundleDir, "profile/now.md", `---\ntitle: "Now"\n---\n\n${nowFocus || "<!-- What are you working on right now? -->"}\n`);
551
+ const projectList = projects
552
+ ? projects
553
+ .split(",")
554
+ .map((p) => p.trim())
555
+ .filter(Boolean)
556
+ .map((p) => `## ${p}\n`)
557
+ .join("\n")
558
+ : "<!-- List your projects here -->\n";
559
+ writeSectionFile(bundleDir, "profile/projects.md", `---\ntitle: "Projects"\n---\n\n${projectList}`);
560
+ writeSectionFile(bundleDir, "profile/values.md", `---\ntitle: "Values"\n---\n\n${values || "<!-- What do you care about? -->"}\n`);
561
+ const linkLines = [];
562
+ if (info.website)
563
+ linkLines.push(`- **Website**: ${info.website}`);
564
+ if (info.linkedin)
565
+ linkLines.push(`- **LinkedIn**: ${info.linkedin}`);
566
+ if (info.twitter)
567
+ linkLines.push(`- **X/Twitter**: ${info.twitter}`);
568
+ if (info.github)
569
+ linkLines.push(`- **GitHub**: ${info.github}`);
570
+ writeSectionFile(bundleDir, "profile/links.md", `---\ntitle: "Links"\n---\n\n${linkLines.length > 0 ? linkLines.join("\n") : "<!-- Add your links here -->"}\n`);
571
+ writeSectionFile(bundleDir, "preferences/agent.md", `---\ntitle: "Agent"\n---\n\n${agentPrefs ? `Communication style: ${agentPrefs}` : "<!-- How should AI agents interact with you? -->"}\n`);
572
+ writeSectionFile(bundleDir, "preferences/writing.md", `---\ntitle: "Writing"\n---\n\n<!-- Your writing style and tone preferences. -->\n`);
573
+ (0, config_1.writeLocalConfig)({ version: 0, sources: [] });
574
+ await finishBundle(bundleDir, info.username, info.name);
575
+ }
576
+ // ─── AI conversation mode ─────────────────────────────────────────────
577
+ async function runAIMode(rl, info, apiKey, scraped, research) {
578
+ const bundleDir = (0, config_1.getLocalBundleDir)();
579
+ const profileDir = path.join(bundleDir, "profile");
580
+ const preferencesDir = path.join(bundleDir, "preferences");
581
+ fs.mkdirSync(profileDir, { recursive: true });
582
+ fs.mkdirSync(preferencesDir, { recursive: true });
583
+ (0, config_1.writeLocalConfig)({ version: 0, sources: [] });
584
+ // ── Fetch website content if provided ──────────────────────────────
585
+ let websiteContent = "";
586
+ if (info.website) {
587
+ const fetchSpinner = new Spinner("reading your site");
588
+ fetchSpinner.start();
589
+ websiteContent = await fetchWebsiteContent(info.website);
590
+ fetchSpinner.stop();
591
+ if (websiteContent) {
592
+ console.log(chalk_1.default.dim(" pulled content from ") +
593
+ chalk_1.default.cyan(info.website) +
594
+ chalk_1.default.dim(` (${websiteContent.length} chars)`));
595
+ console.log("");
596
+ }
597
+ else {
598
+ console.log(chalk_1.default.dim(" couldn't reach ") +
599
+ chalk_1.default.cyan(info.website) +
600
+ chalk_1.default.dim(" -- no worries, we'll work with what you tell me."));
601
+ console.log("");
602
+ }
603
+ }
604
+ // ── Build initial context ──────────────────────────────────────────
605
+ const linksInfo = [];
606
+ if (info.website)
607
+ linksInfo.push(`Website: ${info.website}`);
608
+ if (info.linkedin)
609
+ linksInfo.push(`LinkedIn: ${info.linkedin}`);
610
+ if (info.twitter)
611
+ linksInfo.push(`X/Twitter: ${info.twitter}`);
612
+ if (info.github)
613
+ linksInfo.push(`GitHub: ${info.github}`);
614
+ let initialUserMessage = `here's what i know so far:
615
+ - name: ${info.name}
616
+ - username: ${info.username}
617
+ ${linksInfo.length > 0 ? linksInfo.map((l) => `- ${l}`).join("\n") : "- no links provided"}`;
618
+ // Add scraped social profile data
619
+ if (scraped?.twitter) {
620
+ const t = scraped.twitter;
621
+ initialUserMessage += `\n\ni scraped their X/Twitter profile:`;
622
+ if (t.name)
623
+ initialUserMessage += `\n- display name: ${t.name}`;
624
+ if (t.bio)
625
+ initialUserMessage += `\n- bio: ${t.bio}`;
626
+ if (t.followers !== undefined)
627
+ initialUserMessage += `\n- followers: ${t.followers}`;
628
+ if (t.location)
629
+ initialUserMessage += `\n- location: ${t.location}`;
630
+ if (t.posts && t.posts.length > 0) {
631
+ initialUserMessage += `\n- recent posts:`;
632
+ for (const post of t.posts.slice(0, 5)) {
633
+ initialUserMessage += `\n - "${post.text.slice(0, 200)}"`;
634
+ }
635
+ }
636
+ }
637
+ if (scraped?.github) {
638
+ const g = scraped.github;
639
+ initialUserMessage += `\n\ni scraped their GitHub profile:`;
640
+ if (g.name)
641
+ initialUserMessage += `\n- display name: ${g.name}`;
642
+ if (g.bio)
643
+ initialUserMessage += `\n- bio: ${g.bio}`;
644
+ if (g.followers !== undefined)
645
+ initialUserMessage += `\n- followers: ${g.followers}`;
646
+ if (g.location)
647
+ initialUserMessage += `\n- location: ${g.location}`;
648
+ }
649
+ // Add Perplexity research results
650
+ if (research) {
651
+ const researchText = research.summary ||
652
+ research.content ||
653
+ (research.findings ? research.findings.join("\n") : null);
654
+ if (researchText) {
655
+ initialUserMessage += `\n\ni also ran deep research (via Perplexity) on this person. here's what i found:\n---\n${researchText.slice(0, 4000)}\n---`;
656
+ }
657
+ }
658
+ if (websiteContent) {
659
+ initialUserMessage += `
660
+
661
+ i also fetched their website content. here's what the site says:
662
+ ---
663
+ ${websiteContent}
162
664
  ---
163
665
 
164
- <!-- Your writing style, tone, and preferences. -->
165
- <!-- Agents use this to communicate on your behalf. -->
166
- `;
666
+ analyze everything you know -- the scraped profiles, research, and website content. comment on what you found -- be specific about their work, role, company, anything interesting. then generate initial profile sections from everything you know. after showing what you found, ask what else they want to add.`;
667
+ }
668
+ else if (scraped?.twitter || scraped?.github || research) {
669
+ initialUserMessage += `
670
+
671
+ analyze everything you know from the scraped profiles and research. comment on what you found -- be specific about their work, background, anything interesting. then generate initial profile sections. after showing what you found, ask what else they want to add.`;
672
+ }
673
+ else {
674
+ initialUserMessage += `
675
+
676
+ generate initial profile sections from what you know, show a brief summary, and ask conversational follow-up questions to learn more.`;
677
+ }
678
+ const messages = [
679
+ { role: "system", content: SYSTEM_PROMPT },
680
+ { role: "user", content: initialUserMessage },
681
+ ];
682
+ // ── Initial LLM call ──────────────────────────────────────────────
683
+ let spinner = new Spinner(randomThinking());
684
+ spinner.start();
685
+ let response;
686
+ try {
687
+ response = await callLLM(apiKey, messages);
688
+ }
689
+ catch (err) {
690
+ spinner.stop();
691
+ console.log(chalk_1.default.red(` failed to connect to AI: ${err instanceof Error ? err.message : String(err)}`));
692
+ console.log(chalk_1.default.dim(" falling back to manual mode."));
693
+ console.log("");
694
+ await runFallbackMode(rl, info);
695
+ return;
696
+ }
697
+ spinner.stop();
698
+ messages.push({ role: "assistant", content: response });
699
+ const initial = parseUpdatesFromResponse(response);
700
+ // Write initial sections
701
+ if (initial.updates.length > 0) {
702
+ for (const update of initial.updates) {
703
+ writeSectionFile(bundleDir, update.section, update.content);
704
+ }
705
+ console.log(chalk_1.default.cyan(` [wrote ${initial.updates.length} section${initial.updates.length === 1 ? "" : "s"}: ${initial.updates.map((u) => sectionLabel(u.section)).join(", ")}]`));
706
+ console.log("");
707
+ }
708
+ // Show agent message
709
+ printAgentMessage(initial.display);
710
+ // Show mini profile box after initial generation
711
+ if (initial.updates.length > 0) {
712
+ const aboutUpdate = initial.updates.find((u) => u.section === "profile/about.md");
713
+ if (aboutUpdate) {
714
+ // Extract headline from about content
715
+ const lines = aboutUpdate.content
716
+ .replace(/---[\s\S]*?---/, "")
717
+ .trim()
718
+ .split("\n")
719
+ .filter((l) => l.trim() && !l.startsWith("#"));
720
+ const headline = lines[0] || "";
721
+ showProfileBox(info.name, headline.slice(0, 60), "");
722
+ }
723
+ }
724
+ console.log(chalk_1.default.dim(" want to tell me more? i can ask about your projects, what you're working on now,"));
725
+ console.log(chalk_1.default.dim(' your values, how you like AI to talk to you -- or just tell me anything.'));
726
+ console.log(chalk_1.default.dim(' type "done" when you\'re ready to publish.'));
727
+ console.log("");
728
+ // ── Conversation loop ──────────────────────────────────────────────
729
+ let exchangeCount = 0;
730
+ while (true) {
731
+ const userInput = await ask(rl, chalk_1.default.green(" > ") + "");
732
+ if (!userInput)
733
+ continue;
734
+ if (isDonePhrase(userInput)) {
735
+ break;
736
+ }
737
+ messages.push({ role: "user", content: userInput });
738
+ exchangeCount++;
739
+ // After 3+ exchanges, hint to the agent it can suggest wrapping up
740
+ if (exchangeCount >= 3) {
741
+ const hintMsg = {
742
+ role: "system",
743
+ content: "the user has provided several rounds of input. if the profile feels rich enough (about, now, projects, values all have substance), you can suggest wrapping up. but if there are obvious gaps, keep asking.",
744
+ };
745
+ messages.push(hintMsg);
746
+ }
747
+ spinner = new Spinner(randomThinking());
748
+ spinner.start();
749
+ try {
750
+ response = await callLLM(apiKey, messages);
751
+ }
752
+ catch (err) {
753
+ spinner.stop();
754
+ console.log(chalk_1.default.red(` AI error: ${err instanceof Error ? err.message : String(err)}`));
755
+ console.log(chalk_1.default.dim(" try again, or type 'done' to finish."));
756
+ console.log("");
757
+ // Remove failed messages
758
+ messages.pop(); // remove hint if added
759
+ if (exchangeCount >= 3)
760
+ messages.pop();
761
+ continue;
762
+ }
763
+ spinner.stop();
764
+ // Remove the hint message from history (don't pollute context)
765
+ if (exchangeCount >= 3) {
766
+ messages.pop(); // remove the hint
767
+ }
768
+ messages.push({ role: "assistant", content: response });
769
+ const parsed = parseUpdatesFromResponse(response);
770
+ // Write updates
771
+ if (parsed.updates.length > 0) {
772
+ for (const update of parsed.updates) {
773
+ writeSectionFile(bundleDir, update.section, update.content);
774
+ }
775
+ console.log(chalk_1.default.cyan(` [updated: ${parsed.updates.map((u) => sectionLabel(u.section)).join(", ")}]`));
776
+ console.log("");
777
+ }
778
+ // Show agent message
779
+ printAgentMessage(parsed.display);
780
+ // Check if agent is suggesting we're done
781
+ const lowerDisplay = parsed.display.toLowerCase();
782
+ if (lowerDisplay.includes("ready to publish") ||
783
+ lowerDisplay.includes("bundle is looking solid") ||
784
+ lowerDisplay.includes("ready to go")) {
785
+ const answer = await ask(rl, chalk_1.default.green(" > ") + "");
786
+ if (isDonePhrase(answer) ||
787
+ answer.toLowerCase().includes("publish") ||
788
+ answer.toLowerCase().includes("yes") ||
789
+ answer.toLowerCase().includes("yeah") ||
790
+ answer.toLowerCase().includes("yep")) {
791
+ break;
792
+ }
793
+ else {
794
+ messages.push({ role: "user", content: answer });
795
+ spinner = new Spinner(randomThinking());
796
+ spinner.start();
797
+ try {
798
+ response = await callLLM(apiKey, messages);
799
+ }
800
+ catch (err) {
801
+ spinner.stop();
802
+ console.log(chalk_1.default.red(` AI error: ${err instanceof Error ? err.message : String(err)}`));
803
+ messages.pop();
804
+ continue;
805
+ }
806
+ spinner.stop();
807
+ messages.push({ role: "assistant", content: response });
808
+ const more = parseUpdatesFromResponse(response);
809
+ if (more.updates.length > 0) {
810
+ for (const update of more.updates) {
811
+ writeSectionFile(bundleDir, update.section, update.content);
812
+ }
813
+ console.log(chalk_1.default.cyan(` [updated: ${more.updates.map((u) => sectionLabel(u.section)).join(", ")}]`));
814
+ console.log("");
815
+ }
816
+ printAgentMessage(more.display);
817
+ }
818
+ }
819
+ }
820
+ // Finish up
821
+ await finishBundle(bundleDir, info.username, info.name);
167
822
  }
168
- // ─── Tree progress display ───────────────────────────────────────────
169
- function treeStep(label, isLast) {
170
- const connector = isLast ? "\\--" : "|--";
171
- console.log(` ${connector} ${label}`);
823
+ function printAgentMessage(text) {
824
+ if (!text)
825
+ return;
826
+ const lines = text.split("\n");
827
+ for (const line of lines) {
828
+ console.log(" " + line);
829
+ }
830
+ console.log("");
831
+ }
832
+ // ─── Finish and compile ───────────────────────────────────────────────
833
+ async function finishBundle(bundleDir, username, name) {
834
+ console.log("");
835
+ const compileSpinner = new Spinner("compiling your context bundle");
836
+ compileSpinner.start();
837
+ await new Promise((r) => setTimeout(r, 600));
838
+ const result = (0, compiler_1.compileBundle)(bundleDir);
839
+ (0, compiler_1.writeBundle)(bundleDir, result);
840
+ compileSpinner.stop();
841
+ console.log(" " +
842
+ chalk_1.default.green("done") +
843
+ chalk_1.default.dim(` -- bundle compiled (v${result.bundle.version})`));
844
+ // Show final preview with stats
845
+ const stats = showBundlePreview(bundleDir);
846
+ console.log(chalk_1.default.dim(` ${stats.fileCount} files, ${stats.filledCount} sections filled`));
847
+ console.log("");
848
+ // Context link
849
+ console.log(" " + chalk_1.default.bold("your context file is ready:"));
850
+ console.log(" " + chalk_1.default.cyan(`https://you.md/${username}/context`));
851
+ console.log("");
852
+ // Publish flow
853
+ if ((0, config_1.isAuthenticated)()) {
854
+ console.log(" you're authenticated. publish with: " +
855
+ chalk_1.default.cyan("youmd publish"));
856
+ }
857
+ else {
858
+ console.log(" " + chalk_1.default.bold("to go live:"));
859
+ console.log(" 1. claim your username at " +
860
+ chalk_1.default.cyan("https://you.md/claim"));
861
+ console.log(" 2. " + chalk_1.default.cyan("youmd login --key <your-api-key>"));
862
+ console.log(" 3. " + chalk_1.default.cyan("youmd publish"));
863
+ }
864
+ console.log("");
865
+ console.log(" " + chalk_1.default.bold("using your identity with AI agents:"));
866
+ console.log(" add this to your system prompt or CLAUDE.md:");
867
+ console.log(chalk_1.default.dim(` "my identity file: https://you.md/${username}/context"`));
868
+ console.log("");
869
+ console.log(" " +
870
+ chalk_1.default.dim("run ") +
871
+ chalk_1.default.cyan("youmd build") +
872
+ chalk_1.default.dim(" anytime to recompile, or ") +
873
+ chalk_1.default.cyan("youmd chat") +
874
+ chalk_1.default.dim(" to keep editing with AI."));
875
+ console.log("");
876
+ console.log(" " + chalk_1.default.bold(`welcome to the agent internet, ${name}.`));
877
+ console.log("");
172
878
  }
173
879
  async function runOnboarding() {
174
880
  const rl = createRL();
175
881
  console.log("");
176
882
  console.log(" " + chalk_1.default.bold("you.md"));
177
- console.log(" your identity file for the agent internet");
178
- console.log("");
179
- console.log(" Agents have soul.md. Humans need you.md.");
883
+ console.log(" " + chalk_1.default.dim("your identity file for the agent internet"));
180
884
  console.log("");
181
- // ── Username ─────────────────────────────────────────────────────
885
+ // ── Phase 1: Identity basics (fast, no LLM) ────────────────────────
886
+ // Username
182
887
  let username = "";
183
888
  let usernameValid = false;
184
889
  while (!usernameValid) {
185
- username = await ask(rl, chalk_1.default.green(" ? ") + "Choose a username: ");
890
+ username = await ask(rl, chalk_1.default.green(" > ") + "pick a username: ");
186
891
  if (!username) {
187
892
  console.log(chalk_1.default.red(" username is required"));
188
893
  continue;
@@ -193,10 +898,10 @@ async function runOnboarding() {
193
898
  continue;
194
899
  }
195
900
  username = username.toLowerCase();
196
- process.stdout.write(" checking... ");
901
+ process.stdout.write(chalk_1.default.dim(" checking... "));
197
902
  const result = await checkUsernameRemote(username);
198
903
  if (result.available) {
199
- console.log(chalk_1.default.green(username + " is available."));
904
+ console.log(chalk_1.default.green(username + " is yours."));
200
905
  usernameValid = true;
201
906
  }
202
907
  else {
@@ -205,98 +910,144 @@ async function runOnboarding() {
205
910
  }
206
911
  }
207
912
  console.log("");
208
- // ── Profile info ─────────────────────────────────────────────────
209
- const name = await ask(rl, chalk_1.default.green(" ? ") + "Your name: ");
210
- const website = await ask(rl, chalk_1.default.green(" ? ") + "Your website (optional): ");
211
- const linkedin = await ask(rl, chalk_1.default.green(" ? ") + "Your LinkedIn URL (optional): ");
212
- const twitter = await ask(rl, chalk_1.default.green(" ? ") + "Your X/Twitter URL (optional): ");
213
- const tagline = await ask(rl, chalk_1.default.green(" ? ") + "One-line tagline: ");
214
- rl.close();
913
+ const name = await ask(rl, chalk_1.default.green(" > ") + "what's your name? ");
914
+ const website = await ask(rl, chalk_1.default.green(" > ") +
915
+ "website URL " +
916
+ chalk_1.default.dim("(optional)") +
917
+ ": ");
918
+ const twitter = await ask(rl, chalk_1.default.green(" > ") +
919
+ "X/Twitter username " +
920
+ chalk_1.default.dim("(optional, e.g. @houston)") +
921
+ ": ");
922
+ const github = await ask(rl, chalk_1.default.green(" > ") +
923
+ "GitHub username " +
924
+ chalk_1.default.dim("(optional)") +
925
+ ": ");
926
+ const linkedin = await ask(rl, chalk_1.default.green(" > ") +
927
+ "LinkedIn URL " +
928
+ chalk_1.default.dim("(optional)") +
929
+ ": ");
215
930
  console.log("");
216
- // ── Build the bundle ─────────────────────────────────────────────
217
- await createBundle({
931
+ // ── Scrape social profiles ────────────────────────────────────────
932
+ const twitterHandle = (twitter || "").replace(/^@/, "").trim();
933
+ const githubHandle = (github || "").trim();
934
+ let twitterData = null;
935
+ let githubData = null;
936
+ if (twitterHandle) {
937
+ const scrapeSpinner = new Spinner("scanning your X profile");
938
+ scrapeSpinner.start();
939
+ twitterData = await scrapeProfile(`https://x.com/${twitterHandle}`);
940
+ scrapeSpinner.stop();
941
+ if (twitterData) {
942
+ displayScrapeResult("X", twitterData);
943
+ }
944
+ else {
945
+ console.log(chalk_1.default.dim(" couldn't pull X profile -- no worries."));
946
+ console.log("");
947
+ }
948
+ }
949
+ if (githubHandle) {
950
+ const scrapeSpinner = new Spinner("reading your GitHub");
951
+ scrapeSpinner.start();
952
+ githubData = await scrapeProfile(`https://github.com/${githubHandle}`);
953
+ scrapeSpinner.stop();
954
+ if (githubData) {
955
+ displayScrapeResult("GitHub", githubData);
956
+ }
957
+ else {
958
+ console.log(chalk_1.default.dim(" couldn't pull GitHub profile -- no worries."));
959
+ console.log("");
960
+ }
961
+ }
962
+ // ── Research the user via Perplexity ──────────────────────────────
963
+ const links = [];
964
+ if (website)
965
+ links.push(website);
966
+ if (twitterHandle)
967
+ links.push(`https://x.com/${twitterHandle}`);
968
+ if (githubHandle)
969
+ links.push(`https://github.com/${githubHandle}`);
970
+ if (linkedin)
971
+ links.push(linkedin);
972
+ let researchData = null;
973
+ if (name || twitterHandle || githubHandle) {
974
+ const researchSpinner = new Spinner("researching you across the internet");
975
+ researchSpinner.start();
976
+ researchData = await researchUser({
977
+ name: name || username,
978
+ username: twitterHandle || githubHandle || username,
979
+ links: links.length > 0 ? links : undefined,
980
+ });
981
+ researchSpinner.stop();
982
+ if (researchData) {
983
+ const summary = researchData.summary ||
984
+ researchData.content ||
985
+ (researchData.findings ? researchData.findings.join(" ") : null);
986
+ if (summary) {
987
+ console.log(chalk_1.default.dim(" research complete -- found context about you."));
988
+ console.log("");
989
+ }
990
+ }
991
+ }
992
+ const twitterUrl = twitterHandle ? `https://x.com/${twitterHandle}` : "";
993
+ const githubUrl = githubHandle ? `https://github.com/${githubHandle}` : "";
994
+ const basicInfo = {
218
995
  username,
219
996
  name: name || username,
220
- tagline: tagline || "",
221
- website: website || undefined,
222
- linkedin: linkedin || undefined,
223
- twitter: twitter || undefined,
224
- });
997
+ website: website || "",
998
+ linkedin: linkedin || "",
999
+ twitter: twitterUrl,
1000
+ github: githubUrl,
1001
+ };
1002
+ // Check for existing bundle
1003
+ const bundleDir = (0, config_1.getLocalBundleDir)();
1004
+ if (fs.existsSync(bundleDir)) {
1005
+ console.log(chalk_1.default.yellow(" .youmd/ already exists. overwriting profile files."));
1006
+ console.log("");
1007
+ }
1008
+ // ── Phase 2: AI conversation ──────────────────────────────────────
1009
+ const userApiKey = getOpenRouterKey();
1010
+ console.log(chalk_1.default.dim(" cool. let's build your identity."));
1011
+ console.log("");
1012
+ try {
1013
+ await runAIMode(rl, basicInfo, userApiKey, { twitter: twitterData, github: githubData }, researchData);
1014
+ }
1015
+ catch {
1016
+ console.log(chalk_1.default.dim(" switching to manual mode."));
1017
+ await runFallbackMode(rl, basicInfo);
1018
+ }
1019
+ rl.close();
225
1020
  }
1021
+ // Re-export for backward compatibility with create.ts
226
1022
  async function createBundle(info) {
227
1023
  const bundleDir = (0, config_1.getLocalBundleDir)();
228
1024
  const profileDir = path.join(bundleDir, "profile");
229
1025
  const preferencesDir = path.join(bundleDir, "preferences");
230
- // Check if directory already exists
231
- if (fs.existsSync(bundleDir)) {
232
- console.log(chalk_1.default.yellow(" warning: .youmd/ directory already exists. Overwriting profile files."));
233
- console.log("");
234
- }
235
- console.log(" Building your identity bundle...");
236
- // Create directories
237
1026
  fs.mkdirSync(profileDir, { recursive: true });
238
1027
  fs.mkdirSync(preferencesDir, { recursive: true });
239
- treeStep("Initializing .youmd/ directory", false);
240
- // Write profile files with actual user data
241
- fs.writeFileSync(path.join(profileDir, "about.md"), generateAboutMd(info.name, info.tagline));
242
- treeStep("Writing profile/about.md", false);
243
- fs.writeFileSync(path.join(profileDir, "links.md"), generateLinksMd({
244
- website: info.website,
245
- linkedin: info.linkedin,
246
- twitter: info.twitter,
247
- }));
248
- treeStep("Writing profile/links.md", false);
249
- // Write remaining profile stubs
250
- fs.writeFileSync(path.join(profileDir, "now.md"), generateNowMd());
251
- fs.writeFileSync(path.join(profileDir, "projects.md"), generateProjectsMd());
252
- fs.writeFileSync(path.join(profileDir, "values.md"), generateValuesMd());
253
- // Write preference stubs
254
- fs.writeFileSync(path.join(preferencesDir, "agent.md"), generateAgentMd());
255
- treeStep("Writing preferences/agent.md", false);
256
- fs.writeFileSync(path.join(preferencesDir, "writing.md"), generateWritingMd());
257
- // Write local config
258
- (0, config_1.writeLocalConfig)({
259
- version: 0,
260
- sources: [],
261
- });
262
- // Compile the bundle
1028
+ writeSectionFile(bundleDir, "profile/about.md", `---\ntitle: "About"\n---\n\n# ${info.name}\n\n${info.tagline}\n`);
1029
+ const linkLines = [];
1030
+ if (info.website)
1031
+ linkLines.push(`- **Website**: ${info.website}`);
1032
+ if (info.linkedin)
1033
+ linkLines.push(`- **LinkedIn**: ${info.linkedin}`);
1034
+ if (info.twitter)
1035
+ linkLines.push(`- **X/Twitter**: ${info.twitter}`);
1036
+ if (info.github)
1037
+ linkLines.push(`- **GitHub**: ${info.github}`);
1038
+ writeSectionFile(bundleDir, "profile/links.md", `---\ntitle: "Links"\n---\n\n${linkLines.length > 0 ? linkLines.join("\n") : "<!-- Add your links here -->"}\n`);
1039
+ writeSectionFile(bundleDir, "profile/now.md", `---\ntitle: "Now"\n---\n\n<!-- What are you working on right now? -->\n`);
1040
+ writeSectionFile(bundleDir, "profile/projects.md", `---\ntitle: "Projects"\n---\n\n<!-- Your projects go here -->\n`);
1041
+ writeSectionFile(bundleDir, "profile/values.md", `---\ntitle: "Values"\n---\n\n<!-- What principles guide your work? -->\n`);
1042
+ writeSectionFile(bundleDir, "preferences/agent.md", `---\ntitle: "Agent"\n---\n\n<!-- How should AI agents interact with you? -->\n`);
1043
+ writeSectionFile(bundleDir, "preferences/writing.md", `---\ntitle: "Writing"\n---\n\n<!-- Your writing style and tone. -->\n`);
1044
+ (0, config_1.writeLocalConfig)({ version: 0, sources: [] });
263
1045
  const result = (0, compiler_1.compileBundle)(bundleDir);
264
- treeStep("Compiling you.json", false);
265
- treeStep("Generating you.md", false);
266
- // Write compiled output
267
1046
  (0, compiler_1.writeBundle)(bundleDir, result);
268
- treeStep("Writing manifest.json", true);
269
- console.log("");
270
1047
  console.log(" " +
271
1048
  chalk_1.default.green("done") +
272
- " -- bundle compiled (v" +
273
- result.bundle.version +
274
- ")");
275
- console.log("");
276
- // ── Preview ────────────────────────────────────────────────────────
277
- console.log(" " + chalk_1.default.bold(info.name));
278
- console.log(" " + info.tagline);
279
- console.log("");
280
- // ── Next steps ─────────────────────────────────────────────────────
281
- console.log(" Next steps:");
282
- console.log("");
283
- console.log(" 1. Edit your profile in .youmd/profile/");
284
- console.log(" Flesh out about.md, now.md, projects.md, values.md");
285
- console.log("");
286
- console.log(" 2. Build and preview");
287
- console.log(" " + chalk_1.default.cyan("youmd build"));
288
- console.log(" " + chalk_1.default.cyan("youmd preview"));
289
- console.log("");
290
- console.log(" 3. Create an account to publish");
291
- console.log(" Visit " +
292
- chalk_1.default.cyan("https://you.md/claim") +
293
- ' to claim "' +
294
- info.username +
295
- '"');
296
- console.log(" Then: " + chalk_1.default.cyan("youmd login --key <your-api-key>"));
297
- console.log(" Then: " + chalk_1.default.cyan("youmd publish"));
298
- console.log("");
299
- console.log(" Your bundle is ready. Make it yours.");
1049
+ chalk_1.default.dim(` -- bundle compiled (v${result.bundle.version})`));
300
1050
  console.log("");
1051
+ showBundlePreview(bundleDir);
301
1052
  }
302
1053
  //# sourceMappingURL=onboarding.js.map