youmd 0.6.21 → 0.6.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/commands/build.d.ts.map +1 -1
  2. package/dist/commands/build.js +0 -1
  3. package/dist/commands/build.js.map +1 -1
  4. package/dist/commands/chat.d.ts.map +1 -1
  5. package/dist/commands/chat.js +283 -184
  6. package/dist/commands/chat.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +21 -1
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/keys.d.ts.map +1 -1
  11. package/dist/commands/keys.js +3 -3
  12. package/dist/commands/keys.js.map +1 -1
  13. package/dist/commands/link.d.ts.map +1 -1
  14. package/dist/commands/link.js +3 -3
  15. package/dist/commands/link.js.map +1 -1
  16. package/dist/commands/login.d.ts +29 -0
  17. package/dist/commands/login.d.ts.map +1 -1
  18. package/dist/commands/login.js +256 -127
  19. package/dist/commands/login.js.map +1 -1
  20. package/dist/commands/logs.js +0 -1
  21. package/dist/commands/logs.js.map +1 -1
  22. package/dist/commands/mcp.d.ts +14 -0
  23. package/dist/commands/mcp.d.ts.map +1 -1
  24. package/dist/commands/mcp.js +284 -28
  25. package/dist/commands/mcp.js.map +1 -1
  26. package/dist/commands/private.d.ts.map +1 -1
  27. package/dist/commands/private.js +6 -5
  28. package/dist/commands/private.js.map +1 -1
  29. package/dist/commands/project.d.ts.map +1 -1
  30. package/dist/commands/project.js +9 -5
  31. package/dist/commands/project.js.map +1 -1
  32. package/dist/commands/publish.d.ts.map +1 -1
  33. package/dist/commands/publish.js +0 -1
  34. package/dist/commands/publish.js.map +1 -1
  35. package/dist/commands/pull.d.ts +24 -1
  36. package/dist/commands/pull.d.ts.map +1 -1
  37. package/dist/commands/pull.js +114 -14
  38. package/dist/commands/pull.js.map +1 -1
  39. package/dist/commands/push.d.ts +1 -0
  40. package/dist/commands/push.d.ts.map +1 -1
  41. package/dist/commands/push.js +33 -13
  42. package/dist/commands/push.js.map +1 -1
  43. package/dist/commands/register.d.ts +6 -0
  44. package/dist/commands/register.d.ts.map +1 -1
  45. package/dist/commands/register.js +179 -79
  46. package/dist/commands/register.js.map +1 -1
  47. package/dist/commands/skill.d.ts.map +1 -1
  48. package/dist/commands/skill.js +121 -50
  49. package/dist/commands/skill.js.map +1 -1
  50. package/dist/commands/stack.d.ts +16 -0
  51. package/dist/commands/stack.d.ts.map +1 -0
  52. package/dist/commands/stack.js +670 -0
  53. package/dist/commands/stack.js.map +1 -0
  54. package/dist/commands/status.d.ts.map +1 -1
  55. package/dist/commands/status.js +25 -5
  56. package/dist/commands/status.js.map +1 -1
  57. package/dist/commands/sync.d.ts +23 -0
  58. package/dist/commands/sync.d.ts.map +1 -1
  59. package/dist/commands/sync.js +193 -12
  60. package/dist/commands/sync.js.map +1 -1
  61. package/dist/commands/whoami.d.ts.map +1 -1
  62. package/dist/commands/whoami.js +21 -16
  63. package/dist/commands/whoami.js.map +1 -1
  64. package/dist/create.js +0 -0
  65. package/dist/index.js +50 -7
  66. package/dist/index.js.map +1 -1
  67. package/dist/lib/api.d.ts +94 -5
  68. package/dist/lib/api.d.ts.map +1 -1
  69. package/dist/lib/api.js +122 -5
  70. package/dist/lib/api.js.map +1 -1
  71. package/dist/lib/ascii.d.ts +1 -0
  72. package/dist/lib/ascii.d.ts.map +1 -1
  73. package/dist/lib/ascii.js +9 -26
  74. package/dist/lib/ascii.js.map +1 -1
  75. package/dist/lib/canonical-json.d.ts +8 -0
  76. package/dist/lib/canonical-json.d.ts.map +1 -0
  77. package/dist/lib/canonical-json.js +29 -0
  78. package/dist/lib/canonical-json.js.map +1 -0
  79. package/dist/lib/capability-router.d.ts +58 -0
  80. package/dist/lib/capability-router.d.ts.map +1 -0
  81. package/dist/lib/capability-router.js +54 -0
  82. package/dist/lib/capability-router.js.map +1 -0
  83. package/dist/lib/compiler.d.ts +32 -0
  84. package/dist/lib/compiler.d.ts.map +1 -1
  85. package/dist/lib/compiler.js +150 -0
  86. package/dist/lib/compiler.js.map +1 -1
  87. package/dist/lib/config.d.ts +71 -35
  88. package/dist/lib/config.d.ts.map +1 -1
  89. package/dist/lib/config.js +334 -124
  90. package/dist/lib/config.js.map +1 -1
  91. package/dist/lib/decompile.d.ts +6 -1
  92. package/dist/lib/decompile.d.ts.map +1 -1
  93. package/dist/lib/decompile.js +120 -11
  94. package/dist/lib/decompile.js.map +1 -1
  95. package/dist/lib/hash.d.ts.map +1 -1
  96. package/dist/lib/hash.js +2 -19
  97. package/dist/lib/hash.js.map +1 -1
  98. package/dist/lib/heartbeat.d.ts +38 -0
  99. package/dist/lib/heartbeat.d.ts.map +1 -0
  100. package/dist/lib/heartbeat.js +70 -0
  101. package/dist/lib/heartbeat.js.map +1 -0
  102. package/dist/lib/host-link.d.ts +123 -0
  103. package/dist/lib/host-link.d.ts.map +1 -0
  104. package/dist/lib/host-link.js +350 -0
  105. package/dist/lib/host-link.js.map +1 -0
  106. package/dist/lib/merge.d.ts +50 -0
  107. package/dist/lib/merge.d.ts.map +1 -0
  108. package/dist/lib/merge.js +167 -0
  109. package/dist/lib/merge.js.map +1 -0
  110. package/dist/lib/onboarding.d.ts +17 -0
  111. package/dist/lib/onboarding.d.ts.map +1 -1
  112. package/dist/lib/onboarding.js +224 -207
  113. package/dist/lib/onboarding.js.map +1 -1
  114. package/dist/lib/project.d.ts +5 -0
  115. package/dist/lib/project.d.ts.map +1 -1
  116. package/dist/lib/project.js +8 -0
  117. package/dist/lib/project.js.map +1 -1
  118. package/dist/lib/projectContext.d.ts +201 -0
  119. package/dist/lib/projectContext.d.ts.map +1 -0
  120. package/dist/lib/projectContext.js +597 -0
  121. package/dist/lib/projectContext.js.map +1 -0
  122. package/dist/lib/render.d.ts +38 -1
  123. package/dist/lib/render.d.ts.map +1 -1
  124. package/dist/lib/render.js +83 -18
  125. package/dist/lib/render.js.map +1 -1
  126. package/dist/lib/skill-catalog.d.ts.map +1 -1
  127. package/dist/lib/skill-catalog.js +22 -2
  128. package/dist/lib/skill-catalog.js.map +1 -1
  129. package/dist/lib/skills.d.ts +20 -5
  130. package/dist/lib/skills.d.ts.map +1 -1
  131. package/dist/lib/skills.js +116 -58
  132. package/dist/lib/skills.js.map +1 -1
  133. package/dist/lib/stackEval.d.ts +87 -0
  134. package/dist/lib/stackEval.d.ts.map +1 -0
  135. package/dist/lib/stackEval.js +233 -0
  136. package/dist/lib/stackEval.js.map +1 -0
  137. package/dist/lib/stackImprove.d.ts +49 -0
  138. package/dist/lib/stackImprove.d.ts.map +1 -0
  139. package/dist/lib/stackImprove.js +313 -0
  140. package/dist/lib/stackImprove.js.map +1 -0
  141. package/dist/lib/stackInstall.d.ts +59 -0
  142. package/dist/lib/stackInstall.d.ts.map +1 -0
  143. package/dist/lib/stackInstall.js +162 -0
  144. package/dist/lib/stackInstall.js.map +1 -0
  145. package/dist/lib/stackSafety.d.ts +126 -0
  146. package/dist/lib/stackSafety.d.ts.map +1 -0
  147. package/dist/lib/stackSafety.js +486 -0
  148. package/dist/lib/stackSafety.js.map +1 -0
  149. package/dist/lib/stackUpdate.d.ts +44 -0
  150. package/dist/lib/stackUpdate.d.ts.map +1 -0
  151. package/dist/lib/stackUpdate.js +256 -0
  152. package/dist/lib/stackUpdate.js.map +1 -0
  153. package/dist/lib/stream.d.ts +116 -0
  154. package/dist/lib/stream.d.ts.map +1 -0
  155. package/dist/lib/stream.js +368 -0
  156. package/dist/lib/stream.js.map +1 -0
  157. package/dist/lib/youstack.d.ts +219 -0
  158. package/dist/lib/youstack.d.ts.map +1 -0
  159. package/dist/lib/youstack.js +1049 -0
  160. package/dist/lib/youstack.js.map +1 -0
  161. package/dist/mcp/registry.d.ts +133 -0
  162. package/dist/mcp/registry.d.ts.map +1 -0
  163. package/dist/mcp/registry.js +833 -0
  164. package/dist/mcp/registry.js.map +1 -0
  165. package/dist/mcp/server.d.ts +106 -5
  166. package/dist/mcp/server.d.ts.map +1 -1
  167. package/dist/mcp/server.js +851 -498
  168. package/dist/mcp/server.js.map +1 -1
  169. package/dist/postinstall.js +11 -2
  170. package/dist/postinstall.js.map +1 -1
  171. package/dist/you.d.ts +1 -0
  172. package/dist/you.js +40 -4
  173. package/dist/you.js.map +1 -1
  174. package/examples/youstack-bamfstack-public/docs/quickstart.md +34 -0
  175. package/examples/youstack-bamfstack-public/prompts/creator-draft-starter.md +15 -0
  176. package/examples/youstack-bamfstack-public/skills/bamfstack-start/SKILL.md +36 -0
  177. package/examples/youstack-bamfstack-public/tests/smoke.md +19 -0
  178. package/examples/youstack-bamfstack-public/workflows/creator-growth-workflow.md +22 -0
  179. package/examples/youstack-bamfstack-public/youstack.json +172 -0
  180. package/examples/youstack-personal/docs/quickstart.md +10 -0
  181. package/examples/youstack-personal/skills/youstack-start/SKILL.md +9 -0
  182. package/examples/youstack-personal/tests/smoke.md +9 -0
  183. package/examples/youstack-personal/workflows/startup.md +7 -0
  184. package/examples/youstack-personal/youstack.json +123 -0
  185. package/package.json +3 -3
  186. package/skills/claude-md-generator.md +1 -1
  187. package/skills/meta-improve.md +8 -3
  188. package/skills/proactive-context-fill.md +1 -1
  189. package/skills/youstack-maintainer.md +128 -0
  190. package/skills/youstack-start.md +78 -0
@@ -37,6 +37,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.RESEARCH_URL = exports.SCRAPE_URL = exports.CHAT_PROXY_URL = exports.BUNDLE_SECTIONS = exports.SYSTEM_PROMPT = exports.Spinner = void 0;
40
+ exports.detectWrapUpOffer = detectWrapUpOffer;
41
+ exports.isDoneIntent = isDoneIntent;
42
+ exports.isBackCommand = isBackCommand;
40
43
  exports.runOnboarding = runOnboarding;
41
44
  exports.createBundle = createBundle;
42
45
  exports.callLLM = callLLM;
@@ -59,6 +62,7 @@ const config_1 = require("./config");
59
62
  const compiler_1 = require("./compiler");
60
63
  const render_1 = require("./render");
61
64
  Object.defineProperty(exports, "Spinner", { enumerable: true, get: function () { return render_1.BrailleSpinner; } });
65
+ const stream_1 = require("./stream");
62
66
  const ascii_1 = require("./ascii");
63
67
  const project_1 = require("./project");
64
68
  // ─── Constants ────────────────────────────────────────────────────────
@@ -127,32 +131,12 @@ const PORTRAIT_COMMENTS = [
127
131
  function randomPortraitComment() {
128
132
  return PORTRAIT_COMMENTS[Math.floor(Math.random() * PORTRAIT_COMMENTS.length)];
129
133
  }
130
- function showPortraitPlaceholder(handle) {
131
- const accent = chalk_1.default.hex("#C46A3A");
132
- const dim = chalk_1.default.dim;
133
- const displayHandle = handle.startsWith("@") ? handle : `@${handle}`;
134
- const innerWidth = Math.max(displayHandle.length + 4, 28);
135
- const portraitLine = "\u2591\u2592\u2593\u2588 portrait loaded \u2588\u2593\u2592\u2591";
136
- const portraitPad = Math.max(0, innerWidth - portraitLine.length);
137
- const handlePad = Math.max(0, innerWidth - displayHandle.length);
138
- console.log("");
139
- console.log(" " + dim("\u250C" + "\u2500".repeat(innerWidth + 2) + "\u2510"));
140
- console.log(" " + dim("\u2502") + " " + accent(portraitLine) + " ".repeat(portraitPad) + dim("\u2502"));
141
- console.log(" " + dim("\u2502") + " " + chalk_1.default.white(displayHandle) + " ".repeat(handlePad) + dim("\u2502"));
142
- console.log(" " + dim("\u2514" + "\u2500".repeat(innerWidth + 2) + "\u2518"));
143
- console.log("");
144
- console.log(" " + accent(randomPortraitComment()));
145
- console.log("");
146
- }
147
134
  // ─── ASCII logo ──────────────────────────────────────────────────────
148
135
  const ASCII_LOGO_LINES = [
149
136
  " \u2566 \u2566 \u2554\u2550\u2557 \u2566 \u2566 \u2554\u2566\u2557 \u2554\u2566\u2557",
150
137
  " \u255A\u2566\u255D \u2551 \u2551 \u2551 \u2551 \u2551\u2551\u2551 \u2551\u2551",
151
138
  " \u2569 \u255A\u2550\u255D \u255A\u2550\u255D \u2550\u2569\u255D \u2550\u2569\u255D",
152
139
  ];
153
- function delay(ms) {
154
- return new Promise((resolve) => setTimeout(resolve, ms));
155
- }
156
140
  async function showAsciiLogo() {
157
141
  const accent = chalk_1.default.hex("#C46A3A");
158
142
  console.log("");
@@ -278,27 +262,60 @@ const THINKING_PHRASES = [
278
262
  "translating your experience into structure",
279
263
  "wiring your identity into the network",
280
264
  ];
281
- const DONE_PHRASES = [
265
+ // Explicit done phrases — unambiguous, end the conversation at any point.
266
+ const EXPLICIT_DONE_PHRASES = [
282
267
  "done",
283
- "publish",
284
- "that's it",
285
- "thats it",
286
- "looks good",
287
268
  "i'm done",
288
269
  "im done",
270
+ "publish",
289
271
  "ship it",
290
- "good enough",
291
- "let's go",
292
- "lets go",
293
- "ready",
294
- "finish",
295
- "all good",
272
+ "that's it",
273
+ "thats it",
296
274
  "that's all",
297
275
  "thats all",
276
+ "that's everything",
277
+ "thats everything",
298
278
  "nothing else",
299
- "nah",
300
- "no",
301
- "nope",
279
+ "finish",
280
+ "finished",
281
+ "wrap it up",
282
+ "good enough",
283
+ ];
284
+ // Bare acknowledgments — these only mean "done" right after the agent has
285
+ // offered to wrap up. mid-flow, "ready" or "yes" is an answer, not an exit.
286
+ const WRAP_UP_ACKS = [
287
+ "yes",
288
+ "yeah",
289
+ "yep",
290
+ "yea",
291
+ "y",
292
+ "sure",
293
+ "ok",
294
+ "okay",
295
+ "ready",
296
+ "looks good",
297
+ "all good",
298
+ "sounds good",
299
+ "perfect",
300
+ "let's go",
301
+ "lets go",
302
+ "go for it",
303
+ "do it",
304
+ "ship",
305
+ ];
306
+ // Agent phrasings that count as an offer to wrap up the conversation.
307
+ const WRAP_UP_OFFER_PATTERNS = [
308
+ /ready to publish/,
309
+ /ready to go/,
310
+ /ready to ship/,
311
+ /looking solid/,
312
+ /wrap (this |it )?up/,
313
+ /call it (done|good|a day)/,
314
+ /good to publish/,
315
+ /shall we (finish|publish|ship)/,
316
+ /want to (finish|publish|wrap)/,
317
+ /finish up/,
318
+ /we're done/,
302
319
  ];
303
320
  const BUNDLE_SECTIONS = [
304
321
  "profile/about.md",
@@ -438,9 +455,47 @@ function ask(rl, question) {
438
455
  function randomThinking() {
439
456
  return THINKING_PHRASES[Math.floor(Math.random() * THINKING_PHRASES.length)];
440
457
  }
441
- function isDonePhrase(input) {
458
+ /**
459
+ * Did the agent's last message offer to wrap up?
460
+ * Gates the bare-acknowledgment exit path in isDoneIntent.
461
+ */
462
+ function detectWrapUpOffer(agentMessage) {
463
+ const lower = (agentMessage || "").toLowerCase();
464
+ return WRAP_UP_OFFER_PATTERNS.some((re) => re.test(lower));
465
+ }
466
+ /**
467
+ * Does this user input mean "i'm done"?
468
+ * Explicit phrases ("i'm done", "that's everything", "publish") work anytime.
469
+ * Bare acknowledgments ("yes", "ready", "ok") only count when the agent has
470
+ * just offered to wrap up — otherwise "no" / "ready" mid-flow are answers,
471
+ * not exits.
472
+ */
473
+ function isDoneIntent(input, wrapUpOffered) {
474
+ const lower = input
475
+ .toLowerCase()
476
+ .trim()
477
+ .replace(/[.!?]+$/, "")
478
+ .trim();
479
+ if (!lower)
480
+ return false;
481
+ if (EXPLICIT_DONE_PHRASES.some((p) => lower === p || lower.startsWith(p + " "))) {
482
+ return true;
483
+ }
484
+ if (wrapUpOffered) {
485
+ if (WRAP_UP_ACKS.some((p) => lower === p))
486
+ return true;
487
+ // affirmative-with-extras after an offer ("yeah let's publish", "ship it now")
488
+ if (/\b(publish|ship it|go ahead)\b/.test(lower))
489
+ return true;
490
+ }
491
+ return false;
492
+ }
493
+ /**
494
+ * "back" / "/back" re-asks the previous sign-up question.
495
+ */
496
+ function isBackCommand(input) {
442
497
  const lower = input.toLowerCase().trim();
443
- return DONE_PHRASES.some((p) => lower === p || lower.startsWith(p + " "));
498
+ return lower === "back" || lower === "/back";
444
499
  }
445
500
  function validateUsernameLocal(username) {
446
501
  if (username.length < 3)
@@ -570,58 +625,11 @@ function displayScrapeResult(label, data) {
570
625
  console.log("");
571
626
  }
572
627
  /**
573
- * Stream a chat response token-by-token via the you.md SSE proxy.
574
- * Calls onChunk for each text delta. Returns the full assembled response.
575
- *
576
- * Throws if streaming fails — callers should catch and fall back to callLLM.
628
+ * Streaming for onboarding turns goes through the shared helpers in
629
+ * lib/stream.ts (same path `youmd chat` uses), which also filter ```json
630
+ * directive blocks out of the live display. URL for the SSE proxy:
577
631
  */
578
- async function streamLLM(messages, onChunk) {
579
- const streamUrl = `${_SITE}/api/v1/chat/stream`;
580
- const res = await fetch(streamUrl, {
581
- method: "POST",
582
- headers: { "Content-Type": "application/json" },
583
- body: JSON.stringify({ messages }),
584
- signal: AbortSignal.timeout(60000),
585
- });
586
- if (!res.ok || !res.body) {
587
- throw new Error(`stream failed: ${res.status}`);
588
- }
589
- const reader = res.body.getReader();
590
- const decoder = new TextDecoder();
591
- let buf = "";
592
- let full = "";
593
- while (true) {
594
- const { done, value } = await reader.read();
595
- if (done)
596
- break;
597
- buf += decoder.decode(value, { stream: true });
598
- const lines = buf.split("\n");
599
- buf = lines.pop() || "";
600
- for (const line of lines) {
601
- if (!line.startsWith("data: "))
602
- continue;
603
- const data = line.slice(6).trim();
604
- if (!data)
605
- continue;
606
- if (data === "[DONE]")
607
- continue;
608
- try {
609
- const parsed = JSON.parse(data);
610
- if (parsed.text) {
611
- full += parsed.text;
612
- onChunk(parsed.text);
613
- }
614
- }
615
- catch {
616
- // skip malformed line
617
- }
618
- }
619
- }
620
- if (!full) {
621
- throw new Error("empty stream response");
622
- }
623
- return full;
624
- }
632
+ const STREAM_CHAT_URL = `${_SITE}/api/v1/chat/stream`;
625
633
  async function callLLM(apiKey, messages) {
626
634
  // Try the you.md proxy first (no API key needed)
627
635
  try {
@@ -950,13 +958,21 @@ generate initial profile sections from what you know, show a brief summary, and
950
958
  console.log("");
951
959
  let response;
952
960
  let spinner;
961
+ let streamedFirstTurn = false;
953
962
  try {
954
963
  // Try streaming first — first response feels alive instead of stalled.
955
- process.stdout.write(" ");
956
- response = await streamLLM(messages, (chunk) => {
957
- process.stdout.write(chunk);
958
- });
959
- process.stdout.write("\n\n");
964
+ // The writer filters ```json directive blocks out of the live display;
965
+ // `response` keeps the full raw text for parseUpdatesFromResponse.
966
+ const writer = (0, stream_1.createFilteredStdoutWriter)();
967
+ response = await (0, stream_1.streamSSEText)(STREAM_CHAT_URL, messages, (chunk) => {
968
+ writer.write(chunk);
969
+ }, 60000);
970
+ if (!response.trim()) {
971
+ throw new Error("empty stream response");
972
+ }
973
+ writer.finish();
974
+ process.stdout.write("\n");
975
+ streamedFirstTurn = true;
960
976
  }
961
977
  catch {
962
978
  // Stream failed — fall back to non-streaming with a spinner.
@@ -985,8 +1001,11 @@ generate initial profile sections from what you know, show a brief summary, and
985
1001
  console.log(chalk_1.default.cyan(` [wrote ${initial.updates.length} section${initial.updates.length === 1 ? "" : "s"}: ${initial.updates.map((u) => sectionLabel(u.section)).join(", ")}]`));
986
1002
  console.log("");
987
1003
  }
988
- // Show agent message
989
- printAgentMessage(initial.display);
1004
+ // Show agent message (already shown live when the first turn streamed —
1005
+ // re-printing it would duplicate the response on screen)
1006
+ if (!streamedFirstTurn) {
1007
+ printAgentMessage(initial.display);
1008
+ }
990
1009
  // Show mini profile box after initial generation
991
1010
  if (initial.updates.length > 0) {
992
1011
  const aboutUpdate = initial.updates.find((u) => u.section === "profile/about.md");
@@ -1008,9 +1027,12 @@ generate initial profile sections from what you know, show a brief summary, and
1008
1027
  // ── Conversation loop ──────────────────────────────────────────────
1009
1028
  let exchangeCount = 0;
1010
1029
  let skipCount = 0;
1030
+ // Bare acks ("yes", "ready") only end the conversation when the agent's
1031
+ // previous message offered to wrap up.
1032
+ let wrapUpOffered = detectWrapUpOffer(initial.display);
1011
1033
  while (true) {
1012
1034
  const userInput = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") + "");
1013
- if (isDonePhrase(userInput)) {
1035
+ if (isDoneIntent(userInput, wrapUpOffered)) {
1014
1036
  break;
1015
1037
  }
1016
1038
  // Handle skip: empty input or "skip"
@@ -1114,13 +1136,23 @@ generate initial profile sections from what you know, show a brief summary, and
1114
1136
  // Also count the skip system message as ephemeral
1115
1137
  if (isSkip)
1116
1138
  ephemeralCount++;
1117
- spinner = new render_1.BrailleSpinner(randomLabel("llm"));
1118
- spinner.start();
1139
+ // Turns 2+ stream through the same shared helper `youmd chat` uses
1140
+ // (U16): BrailleSpinner while waiting for the first token, then live
1141
+ // tokens with ```json directive blocks filtered from the display.
1142
+ // `response` keeps the full raw text for parseUpdatesFromResponse.
1143
+ let streamedTurn = false;
1119
1144
  try {
1120
- response = await callLLM(apiKey, messages);
1145
+ const turn = await (0, stream_1.streamAssistantTurn)({
1146
+ streamUrl: STREAM_CHAT_URL,
1147
+ messages,
1148
+ spinnerLabel: randomLabel("llm"),
1149
+ timeoutMs: 60000,
1150
+ fallback: () => callLLM(apiKey, messages),
1151
+ });
1152
+ response = turn.text;
1153
+ streamedTurn = turn.streamed;
1121
1154
  }
1122
1155
  catch (err) {
1123
- spinner.stop();
1124
1156
  console.log(chalk_1.default.red(` AI error: ${err instanceof Error ? err.message : String(err)}`));
1125
1157
  console.log(chalk_1.default.dim(" try again, or type 'done' to finish."));
1126
1158
  console.log("");
@@ -1131,7 +1163,6 @@ generate initial profile sections from what you know, show a brief summary, and
1131
1163
  messages.pop(); // also remove the "(skip)" user message
1132
1164
  continue;
1133
1165
  }
1134
- spinner.stop();
1135
1166
  // Remove ephemeral system messages from history (don't pollute context)
1136
1167
  // They sit between the user message and the response we're about to push
1137
1168
  for (let j = 0; j < ephemeralCount; j++) {
@@ -1147,47 +1178,13 @@ generate initial profile sections from what you know, show a brief summary, and
1147
1178
  console.log(chalk_1.default.cyan(` [updated: ${parsed.updates.map((u) => sectionLabel(u.section)).join(", ")}]`));
1148
1179
  console.log("");
1149
1180
  }
1150
- // Show agent message
1151
- printAgentMessage(parsed.display);
1152
- // Check if agent is suggesting we're done
1153
- const lowerDisplay = parsed.display.toLowerCase();
1154
- if (lowerDisplay.includes("ready to publish") ||
1155
- lowerDisplay.includes("bundle is looking solid") ||
1156
- lowerDisplay.includes("ready to go")) {
1157
- const answer = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") + "");
1158
- if (isDonePhrase(answer) ||
1159
- answer.toLowerCase().includes("publish") ||
1160
- answer.toLowerCase().includes("yes") ||
1161
- answer.toLowerCase().includes("yeah") ||
1162
- answer.toLowerCase().includes("yep")) {
1163
- break;
1164
- }
1165
- else {
1166
- messages.push({ role: "user", content: answer });
1167
- spinner = new render_1.BrailleSpinner(randomLabel("llm"));
1168
- spinner.start();
1169
- try {
1170
- response = await callLLM(apiKey, messages);
1171
- }
1172
- catch (err) {
1173
- spinner.stop();
1174
- console.log(chalk_1.default.red(` AI error: ${err instanceof Error ? err.message : String(err)}`));
1175
- messages.pop();
1176
- continue;
1177
- }
1178
- spinner.stop();
1179
- messages.push({ role: "assistant", content: response });
1180
- const more = parseUpdatesFromResponse(response);
1181
- if (more.updates.length > 0) {
1182
- for (const update of more.updates) {
1183
- writeSectionFile(bundleDir, update.section, update.content);
1184
- }
1185
- console.log(chalk_1.default.cyan(` [updated: ${more.updates.map((u) => sectionLabel(u.section)).join(", ")}]`));
1186
- console.log("");
1187
- }
1188
- printAgentMessage(more.display);
1189
- }
1181
+ // Show agent message (only when the turn didn't already stream live)
1182
+ if (!streamedTurn) {
1183
+ printAgentMessage(parsed.display);
1190
1184
  }
1185
+ // If the agent just offered to wrap up, the next bare "yes"/"ready"
1186
+ // counts as done — handled at the top of the loop.
1187
+ wrapUpOffered = detectWrapUpOffer(parsed.display);
1191
1188
  }
1192
1189
  // Finish up
1193
1190
  await finishBundle(bundleDir, info.username, info.name);
@@ -1199,8 +1196,7 @@ function printAgentMessage(text) {
1199
1196
  const normalizedText = text
1200
1197
  .replace(/\n{3,}/g, "\n\n") // collapse 3+ newlines to 2
1201
1198
  .replace(/([.?!])\n([a-z])/g, "$1\n\n$2"); // add blank line between sentences that run together
1202
- const { renderRichResponse } = require("./render");
1203
- const rendered = renderRichResponse(normalizedText);
1199
+ const rendered = (0, render_1.renderRichResponse)(normalizedText);
1204
1200
  // Find the last line that ends with "?" and highlight it in accent color
1205
1201
  const lines = rendered.split("\n");
1206
1202
  let lastQuestionIdx = -1;
@@ -1265,7 +1261,7 @@ async function finishBundle(bundleDir, username, name) {
1265
1261
  }
1266
1262
  console.log(" " + accent("agent tools:"));
1267
1263
  console.log("");
1268
- console.log(` ${chalk_1.default.cyan("youmd skill link claude")} ${chalk_1.default.dim("-- sync skills to .claude/skills/youmd/")}`);
1264
+ console.log(` ${chalk_1.default.cyan("youmd skill link claude")} ${chalk_1.default.dim("-- sync skills to .claude/skills/<name>/SKILL.md")}`);
1269
1265
  console.log(` ${chalk_1.default.cyan("youmd skill link cursor")} ${chalk_1.default.dim("-- sync skills to .cursor/rules/youmd.md")}`);
1270
1266
  console.log(` ${chalk_1.default.cyan("youmd link create")} ${chalk_1.default.dim("-- shareable context link for any agent")}`);
1271
1267
  console.log(` ${chalk_1.default.cyan("you")} ${chalk_1.default.dim("-- main live U entrypoint")}`);
@@ -1278,74 +1274,116 @@ async function finishBundle(bundleDir, username, name) {
1278
1274
  console.log("");
1279
1275
  }
1280
1276
  async function runOnboarding() {
1277
+ // Onboarding is fully conversational — bail fast on piped/CI stdin
1278
+ (0, render_1.requireInteractiveTTY)();
1281
1279
  const rl = createRL();
1282
1280
  // ── ASCII logo splash ──────────────────────────────────────────────
1283
1281
  // Real YOU logo — same block-character font as the homepage hero
1284
1282
  await showAsciiLogo();
1285
1283
  // ── Phase 1: Identity basics (fast, no LLM) ────────────────────────
1286
- // Username
1284
+ // One question at a time; "back" re-asks the previous question.
1287
1285
  let username = "";
1288
- let usernameValid = false;
1289
- while (!usernameValid) {
1290
- username = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") + "pick a username: ");
1291
- if (!username) {
1292
- console.log(chalk_1.default.red(" username is required"));
1293
- continue;
1294
- }
1295
- const localError = validateUsernameLocal(username.toLowerCase());
1296
- if (localError) {
1297
- console.log(chalk_1.default.red(" " + localError));
1298
- continue;
1299
- }
1300
- username = username.toLowerCase();
1301
- process.stdout.write(chalk_1.default.dim(" checking... "));
1302
- const result = await checkUsernameRemote(username);
1303
- if (result.available) {
1286
+ let name = "";
1287
+ let website = "";
1288
+ let twitter = "";
1289
+ let github = "";
1290
+ let linkedin = "";
1291
+ const IDENTITY_STEPS = [
1292
+ "username",
1293
+ "name",
1294
+ "website",
1295
+ "twitter",
1296
+ "github",
1297
+ "linkedin",
1298
+ ];
1299
+ let stepIdx = 0;
1300
+ let backHintShown = false;
1301
+ while (stepIdx < IDENTITY_STEPS.length) {
1302
+ const step = IDENTITY_STEPS[stepIdx];
1303
+ if (step === "username") {
1304
+ const raw = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") + "pick a username: ");
1305
+ if (isBackCommand(raw)) {
1306
+ console.log(chalk_1.default.dim(" first question — nowhere to go back to."));
1307
+ continue;
1308
+ }
1309
+ if (!raw) {
1310
+ console.log(chalk_1.default.red(" username is required"));
1311
+ continue;
1312
+ }
1313
+ const candidate = raw.toLowerCase();
1314
+ const localError = validateUsernameLocal(candidate);
1315
+ if (localError) {
1316
+ console.log(chalk_1.default.red(" " + localError));
1317
+ continue;
1318
+ }
1319
+ process.stdout.write(chalk_1.default.dim(" checking... "));
1320
+ const result = await checkUsernameRemote(candidate);
1321
+ if (!result.available) {
1322
+ console.log(chalk_1.default.red(candidate + " is taken.") +
1323
+ (result.reason ? " " + result.reason : ""));
1324
+ continue;
1325
+ }
1304
1326
  // Check if a profile already exists on you.md for this username
1305
1327
  let profileExists = false;
1306
1328
  try {
1307
- const profileRes = await fetch(`https://you.md/${encodeURIComponent(username)}/context`, { method: "HEAD", signal: AbortSignal.timeout(5000) });
1329
+ const profileRes = await fetch(`https://you.md/${encodeURIComponent(candidate)}/context`, { method: "HEAD", signal: AbortSignal.timeout(5000) });
1308
1330
  profileExists = profileRes.ok;
1309
1331
  }
1310
1332
  catch {
1311
1333
  // ignore -- assume no profile
1312
1334
  }
1313
1335
  if (profileExists) {
1314
- console.log(chalk_1.default.green(username + " is available."));
1315
- console.log(chalk_1.default.dim(` a profile for @${username} already exists on you.md -- you can claim it after login with `) +
1336
+ console.log(chalk_1.default.green(candidate + " is available."));
1337
+ console.log(chalk_1.default.dim(` a profile for @${candidate} already exists on you.md -- you can claim it after login with `) +
1316
1338
  chalk_1.default.cyan("youmd login") +
1317
1339
  chalk_1.default.dim(" then ") +
1318
1340
  chalk_1.default.cyan("youmd pull"));
1319
1341
  }
1320
1342
  else {
1321
- console.log(chalk_1.default.green(username + " is yours."));
1343
+ console.log(chalk_1.default.green(candidate + " is yours."));
1322
1344
  }
1323
- usernameValid = true;
1345
+ username = candidate;
1346
+ console.log("");
1347
+ if (!backHintShown) {
1348
+ console.log(chalk_1.default.dim(' (type "back" anytime to fix a previous answer)'));
1349
+ console.log("");
1350
+ backHintShown = true;
1351
+ }
1352
+ stepIdx++;
1353
+ continue;
1324
1354
  }
1325
- else {
1326
- console.log(chalk_1.default.red(username + " is taken.") +
1327
- (result.reason ? " " + result.reason : ""));
1355
+ const stepPrompts = {
1356
+ name: "what's your name? ",
1357
+ website: "website URL " + chalk_1.default.dim("(optional)") + ": ",
1358
+ twitter: "X/Twitter username " + chalk_1.default.dim("(optional, e.g. @houston)") + ": ",
1359
+ github: "GitHub username " + chalk_1.default.dim("(optional)") + ": ",
1360
+ linkedin: "LinkedIn URL " + chalk_1.default.dim("(optional)") + ": ",
1361
+ };
1362
+ const raw = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") + stepPrompts[step]);
1363
+ if (isBackCommand(raw)) {
1364
+ stepIdx--;
1365
+ continue;
1328
1366
  }
1367
+ switch (step) {
1368
+ case "name":
1369
+ name = raw;
1370
+ break;
1371
+ case "website":
1372
+ website = raw;
1373
+ break;
1374
+ case "twitter":
1375
+ twitter = raw;
1376
+ break;
1377
+ case "github":
1378
+ github = raw;
1379
+ break;
1380
+ case "linkedin":
1381
+ linkedin = raw;
1382
+ break;
1383
+ }
1384
+ stepIdx++;
1329
1385
  }
1330
1386
  console.log("");
1331
- const name = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") + "what's your name? ");
1332
- const website = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") +
1333
- "website URL " +
1334
- chalk_1.default.dim("(optional)") +
1335
- ": ");
1336
- const twitter = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") +
1337
- "X/Twitter username " +
1338
- chalk_1.default.dim("(optional, e.g. @houston)") +
1339
- ": ");
1340
- const github = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") +
1341
- "GitHub username " +
1342
- chalk_1.default.dim("(optional)") +
1343
- ": ");
1344
- const linkedin = await ask(rl, chalk_1.default.hex("#C46A3A")(" > ") +
1345
- "LinkedIn URL " +
1346
- chalk_1.default.dim("(optional)") +
1347
- ": ");
1348
- console.log("");
1349
1387
  // ── Render REAL ASCII portrait from first social handle ──────────
1350
1388
  const earlyTwitter = (twitter || "").replace(/^@/, "").trim();
1351
1389
  const earlyGithub = (github || "").trim();
@@ -1372,33 +1410,12 @@ async function runOnboarding() {
1372
1410
  img.brightness(0.05);
1373
1411
  portraitLines = [];
1374
1412
  for (let y = 0; y < rows; y++) {
1375
- let coloredLine = "";
1376
1413
  let plainLine = "";
1377
1414
  for (let x = 0; x < cols; x++) {
1378
1415
  const pixel = Jimp.intToRGBA(img.getPixelColor(x, y));
1379
1416
  const lum = 0.299 * pixel.r + 0.587 * pixel.g + 0.114 * pixel.b;
1380
1417
  const ch = RAMP[Math.floor((lum / 255) * (RAMP.length - 1))];
1381
1418
  plainLine += ch;
1382
- // Orange-tinted colors based on luminance
1383
- const brightness = Math.floor((lum / 255) * 100);
1384
- if (brightness < 10) {
1385
- coloredLine += chalk_1.default.hidden(ch);
1386
- }
1387
- else if (brightness < 30) {
1388
- coloredLine += chalk_1.default.hex("#5A3018")(ch);
1389
- }
1390
- else if (brightness < 50) {
1391
- coloredLine += chalk_1.default.hex("#8A4828")(ch);
1392
- }
1393
- else if (brightness < 70) {
1394
- coloredLine += chalk_1.default.hex("#B06038")(ch);
1395
- }
1396
- else if (brightness < 85) {
1397
- coloredLine += chalk_1.default.hex("#C46A3A")(ch);
1398
- }
1399
- else {
1400
- coloredLine += chalk_1.default.hex("#E09060")(ch);
1401
- }
1402
1419
  }
1403
1420
  portraitLines.push(plainLine);
1404
1421
  }