youmd 0.6.2 → 0.6.11

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 (67) hide show
  1. package/dist/commands/chat.d.ts.map +1 -1
  2. package/dist/commands/chat.js +334 -44
  3. package/dist/commands/chat.js.map +1 -1
  4. package/dist/commands/diff.js +3 -3
  5. package/dist/commands/diff.js.map +1 -1
  6. package/dist/commands/export.js +3 -3
  7. package/dist/commands/export.js.map +1 -1
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +9 -1
  10. package/dist/commands/init.js.map +1 -1
  11. package/dist/commands/login.d.ts.map +1 -1
  12. package/dist/commands/login.js +5 -0
  13. package/dist/commands/login.js.map +1 -1
  14. package/dist/commands/preview.js +3 -3
  15. package/dist/commands/preview.js.map +1 -1
  16. package/dist/commands/pull.d.ts.map +1 -1
  17. package/dist/commands/pull.js +6 -0
  18. package/dist/commands/pull.js.map +1 -1
  19. package/dist/commands/register.js +1 -1
  20. package/dist/commands/register.js.map +1 -1
  21. package/dist/commands/status.d.ts.map +1 -1
  22. package/dist/commands/status.js +16 -10
  23. package/dist/commands/status.js.map +1 -1
  24. package/dist/index.js +180 -45
  25. package/dist/index.js.map +1 -1
  26. package/dist/lib/api.d.ts.map +1 -1
  27. package/dist/lib/api.js +2 -1
  28. package/dist/lib/api.js.map +1 -1
  29. package/dist/lib/ascii.d.ts +15 -0
  30. package/dist/lib/ascii.d.ts.map +1 -1
  31. package/dist/lib/ascii.js +244 -11
  32. package/dist/lib/ascii.js.map +1 -1
  33. package/dist/lib/compiler.d.ts.map +1 -1
  34. package/dist/lib/compiler.js +4 -0
  35. package/dist/lib/compiler.js.map +1 -1
  36. package/dist/lib/config.d.ts +7 -0
  37. package/dist/lib/config.d.ts.map +1 -1
  38. package/dist/lib/config.js +40 -2
  39. package/dist/lib/config.js.map +1 -1
  40. package/dist/lib/decompile.d.ts.map +1 -1
  41. package/dist/lib/decompile.js +48 -29
  42. package/dist/lib/decompile.js.map +1 -1
  43. package/dist/lib/onboarding.d.ts.map +1 -1
  44. package/dist/lib/onboarding.js +33 -8
  45. package/dist/lib/onboarding.js.map +1 -1
  46. package/dist/lib/project.d.ts +15 -0
  47. package/dist/lib/project.d.ts.map +1 -1
  48. package/dist/lib/project.js +257 -0
  49. package/dist/lib/project.js.map +1 -1
  50. package/dist/lib/skill-renderer.d.ts +2 -2
  51. package/dist/lib/skill-renderer.d.ts.map +1 -1
  52. package/dist/lib/skill-renderer.js +5 -14
  53. package/dist/lib/skill-renderer.js.map +1 -1
  54. package/dist/lib/update.d.ts +3 -0
  55. package/dist/lib/update.d.ts.map +1 -0
  56. package/dist/lib/update.js +55 -0
  57. package/dist/lib/update.js.map +1 -0
  58. package/dist/mcp/server.js +1 -1
  59. package/dist/mcp/server.js.map +1 -1
  60. package/dist/postinstall.d.ts +1 -0
  61. package/dist/postinstall.js +18 -2
  62. package/dist/postinstall.js.map +1 -1
  63. package/dist/you.d.ts +2 -0
  64. package/dist/you.d.ts.map +1 -0
  65. package/dist/you.js +15 -0
  66. package/dist/you.js.map +1 -0
  67. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAy9BA,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAmkBjD"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AA6vCA,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CA6lBjD"}
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.chatCommand = chatCommand;
40
40
  const readline = __importStar(require("readline"));
41
41
  const fs = __importStar(require("fs"));
42
+ const os = __importStar(require("os"));
42
43
  const path = __importStar(require("path"));
43
44
  const chalk_1 = __importDefault(require("chalk"));
44
45
  const config_1 = require("../lib/config");
@@ -47,10 +48,16 @@ const compiler_1 = require("../lib/compiler");
47
48
  const api_1 = require("../lib/api");
48
49
  const render_1 = require("../lib/render");
49
50
  const onboarding_1 = require("../lib/onboarding");
51
+ const ascii_1 = require("../lib/ascii");
52
+ const update_1 = require("../lib/update");
50
53
  // ─── URL Detection + Scraping (mirrors web useYouAgent) ──────────────
51
54
  const config_2 = require("../lib/config");
52
55
  const CONVEX_SITE_URL = (0, config_2.getConvexSiteUrl)();
53
56
  const STREAM_URL = `${CONVEX_SITE_URL}/api/v1/chat/stream`;
57
+ const CURRENT_VERSION = "0.6.11";
58
+ function delay(ms) {
59
+ return new Promise((resolve) => setTimeout(resolve, ms));
60
+ }
54
61
  // ─── Streaming LLM client ─────────────────────────────────────────────
55
62
  async function streamLLM(_apiKey, messages, onToken) {
56
63
  const res = await fetch(STREAM_URL, {
@@ -148,7 +155,7 @@ async function callLLMWithStreaming(apiKey, messages, spinnerLabel) {
148
155
  // No tokens received -- clear spinner
149
156
  thinkSpinner.stop();
150
157
  }
151
- return response;
158
+ return { text: response, streamed: !firstToken };
152
159
  }
153
160
  catch {
154
161
  // Streaming failed -- fall back to blocking call
@@ -156,7 +163,7 @@ async function callLLMWithStreaming(apiKey, messages, spinnerLabel) {
156
163
  try {
157
164
  const response = await (0, onboarding_1.callLLM)(apiKey, messages);
158
165
  thinkSpinner.stop();
159
- return response;
166
+ return { text: response, streamed: false };
160
167
  }
161
168
  catch (err) {
162
169
  thinkSpinner.fail(err instanceof Error ? err.message : "failed");
@@ -839,9 +846,261 @@ function extractProfileHint(bundleDir) {
839
846
  }
840
847
  return null;
841
848
  }
849
+ function repoNeedsBootstrap(projectRoot) {
850
+ return (!fs.existsSync(path.join(projectRoot, "AGENTS.md")) ||
851
+ !fs.existsSync(path.join(projectRoot, "project-context")));
852
+ }
853
+ function resolveBundleDirForChat() {
854
+ const localDir = (0, config_1.getLocalBundleDir)();
855
+ if ((0, config_1.bundleLooksInitialized)(localDir))
856
+ return localDir;
857
+ const homeDir = (0, config_1.getHomeBundleDir)();
858
+ if ((0, config_1.bundleLooksInitialized)(homeDir))
859
+ return homeDir;
860
+ return null;
861
+ }
862
+ function readDisplayName(bundleDir) {
863
+ const youJsonPath = path.join(bundleDir, "you.json");
864
+ if (fs.existsSync(youJsonPath)) {
865
+ try {
866
+ const parsed = JSON.parse(fs.readFileSync(youJsonPath, "utf-8"));
867
+ if (parsed.identity?.name)
868
+ return parsed.identity.name;
869
+ }
870
+ catch {
871
+ // non-fatal
872
+ }
873
+ }
874
+ const aboutPath = path.join(bundleDir, "profile", "about.md");
875
+ if (fs.existsSync(aboutPath)) {
876
+ const content = fs.readFileSync(aboutPath, "utf-8");
877
+ const heading = content.split("\n").find((line) => line.startsWith("# "));
878
+ if (heading)
879
+ return heading.slice(2).trim();
880
+ }
881
+ return (0, config_1.readGlobalConfig)().username || "friend";
882
+ }
883
+ function getRecentProjectNames(limit = 3) {
884
+ return (0, project_1.getRecentProjectInsights)(process.cwd(), limit).map((item) => item.name);
885
+ }
886
+ async function printUpdateHint() {
887
+ const latest = await (0, update_1.checkForCliUpdate)(CURRENT_VERSION);
888
+ if (!latest)
889
+ return;
890
+ console.log(" " + chalk_1.default.yellow(`update available: ${CURRENT_VERSION} → ${latest}`));
891
+ console.log(" " + chalk_1.default.dim("refresh U with: ") + chalk_1.default.cyan("curl -fsSL https://you.md/install.sh | bash"));
892
+ console.log(" " + chalk_1.default.dim("or: ") + chalk_1.default.cyan(`npm install -g youmd@${latest}`));
893
+ console.log("");
894
+ }
895
+ function countMarkdownFiles(dir) {
896
+ if (!fs.existsSync(dir))
897
+ return 0;
898
+ try {
899
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
900
+ let total = 0;
901
+ for (const entry of entries) {
902
+ if (entry.name.startsWith("."))
903
+ continue;
904
+ const fullPath = path.join(dir, entry.name);
905
+ if (entry.isDirectory()) {
906
+ total += countMarkdownFiles(fullPath);
907
+ }
908
+ else if (entry.isFile() && entry.name.endsWith(".md")) {
909
+ total += 1;
910
+ }
911
+ }
912
+ return total;
913
+ }
914
+ catch {
915
+ return 0;
916
+ }
917
+ }
918
+ async function runYouLaunchInvestigation(bundleDir, projectCtx, recentProjects) {
919
+ const labels = [
920
+ "sipping bitbucks frappaccino while i look around",
921
+ "checking local AGENTS and CLAUDE instructions",
922
+ "reading project context and recent work",
923
+ "connecting the dots before we talk",
924
+ ];
925
+ const spinner = new render_1.BrailleSpinner(labels[0]);
926
+ let labelIndex = 1;
927
+ const findings = [];
928
+ const rotation = setInterval(() => {
929
+ spinner.update(labels[labelIndex % labels.length]);
930
+ labelIndex += 1;
931
+ }, 1100);
932
+ spinner.start();
933
+ try {
934
+ await delay(160);
935
+ try {
936
+ const hasPreferences = fs.existsSync(path.join(bundleDir, "preferences", "agent.md"));
937
+ const hasDirectives = fs.existsSync(path.join(bundleDir, "directives", "agent.md"));
938
+ if (hasPreferences && hasDirectives) {
939
+ findings.push("your agent preferences and directives are already loaded from your home bundle.");
940
+ }
941
+ else if (hasPreferences || hasDirectives) {
942
+ findings.push("i found part of your agent guidance locally, but it still wants a little more shape.");
943
+ }
944
+ }
945
+ catch {
946
+ // keep scanning other surfaces
947
+ }
948
+ if (projectCtx) {
949
+ try {
950
+ const hasAgents = fs.existsSync(path.join(projectCtx.root, "AGENTS.md"));
951
+ const hasClaude = fs.existsSync(path.join(projectCtx.root, "CLAUDE.md"));
952
+ const projectContextDir = path.join(projectCtx.root, "project-context");
953
+ const projectContextFiles = countMarkdownFiles(projectContextDir);
954
+ if (hasAgents && hasClaude) {
955
+ findings.push(`${projectCtx.name} already has both AGENTS.md and CLAUDE.md in place.`);
956
+ }
957
+ else if (hasAgents || hasClaude) {
958
+ findings.push(`${projectCtx.name} has some local agent wiring, but not the full stack yet.`);
959
+ }
960
+ if (projectContextFiles > 0) {
961
+ findings.push(`${projectCtx.name} is carrying ${projectContextFiles} project-context file${projectContextFiles === 1 ? "" : "s"} already.`);
962
+ }
963
+ else if (fs.existsSync(projectContextDir)) {
964
+ findings.push(`${projectCtx.name} has a project-context directory, but it still feels mostly empty.`);
965
+ }
966
+ else {
967
+ findings.push(`${projectCtx.name} still wants a real project-context spine.`);
968
+ }
969
+ }
970
+ catch {
971
+ findings.push(`${projectCtx.name} is open, but one of the local context probes tripped over itself.`);
972
+ }
973
+ }
974
+ else if (recentProjects.length > 0) {
975
+ const insights = (0, project_1.getRecentProjectInsights)(process.cwd(), 6);
976
+ const featuredRecent = (0, project_1.getFeaturedRecentProjectNames)(insights, 3);
977
+ findings.push(`recent orbit: ${featuredRecent.join(", ")}.`);
978
+ const opportunities = insights.filter((item) => item.signals.length > 0).slice(0, 2);
979
+ for (const opportunity of opportunities) {
980
+ findings.push(opportunity.summary);
981
+ }
982
+ if (opportunities.length === 0 && insights.length > 0) {
983
+ findings.push(`${insights[0].name} already looks pretty well-shaped, so i can go deeper instead of scaffolding basics.`);
984
+ }
985
+ }
986
+ else {
987
+ findings.push("i've got your home bundle loaded, even though we're not inside a project yet.");
988
+ }
989
+ await delay(220);
990
+ spinner.stop("looked through local context");
991
+ return { findings: findings.slice(0, 4) };
992
+ }
993
+ finally {
994
+ clearInterval(rotation);
995
+ }
996
+ }
997
+ async function printChatOpening(bundleDir, projectCtx) {
998
+ const ACCENT = chalk_1.default.hex("#C46A3A");
999
+ const DIM = chalk_1.default.dim;
1000
+ const cfg = (0, config_1.readGlobalConfig)();
1001
+ const user = cfg.username ? `@${cfg.username}` : "you";
1002
+ const displayName = readDisplayName(bundleDir);
1003
+ const recentInsights = (0, project_1.getRecentProjectInsights)(process.cwd(), 6);
1004
+ const recentProjects = (0, project_1.getFeaturedRecentProjectNames)(recentInsights, 6);
1005
+ const launchSurface = process.env.YOUMD_LAUNCH_SURFACE;
1006
+ let investigation = { findings: [] };
1007
+ (0, ascii_1.printYouLogo)();
1008
+ let didShowPortrait = false;
1009
+ if (launchSurface !== "you") {
1010
+ didShowPortrait = (0, ascii_1.printSavedPortrait)(bundleDir, { maxLines: 18 });
1011
+ }
1012
+ if (launchSurface === "you") {
1013
+ const portraitLines = await (0, ascii_1.resolvePortraitLines)(bundleDir);
1014
+ didShowPortrait = portraitLines
1015
+ ? (0, ascii_1.printPortraitEncounter)({
1016
+ bundleDir,
1017
+ displayName,
1018
+ currentProject: projectCtx?.name,
1019
+ recentProjects,
1020
+ portraitLines,
1021
+ })
1022
+ : false;
1023
+ }
1024
+ if (didShowPortrait) {
1025
+ console.log("");
1026
+ console.log(" " + ACCENT("there you are.") + " " + DIM("your portrait is loaded."));
1027
+ }
1028
+ if (launchSurface === "you") {
1029
+ console.log("");
1030
+ investigation = await runYouLaunchInvestigation(bundleDir, projectCtx, recentProjects);
1031
+ if (investigation.findings.length > 0) {
1032
+ console.log("");
1033
+ console.log(" " + ACCENT("i checked a few corners."));
1034
+ for (const finding of investigation.findings) {
1035
+ console.log(" " + DIM("· ") + chalk_1.default.white(finding));
1036
+ }
1037
+ }
1038
+ }
1039
+ console.log("");
1040
+ if (launchSurface === "you") {
1041
+ console.log(" " + ACCENT("u is here.") + " " + DIM(`good to see you, ${displayName}.`));
1042
+ }
1043
+ else {
1044
+ console.log(" " + ACCENT("u is here.") + " " + DIM(`good to see you, ${user}.`));
1045
+ }
1046
+ if (projectCtx) {
1047
+ console.log(" " + DIM("current project: ") + chalk_1.default.white(projectCtx.name) + DIM(` (${projectCtx.root})`));
1048
+ if (repoNeedsBootstrap(projectCtx.root)) {
1049
+ console.log(" " + ACCENT("i spotted an opening.") + " " + DIM("this repo still wants AGENTS/project-context wiring."));
1050
+ console.log(" " + DIM("say the word or run ") + chalk_1.default.cyan("youmd skill init-project") + DIM(" and i'll set it up."));
1051
+ }
1052
+ }
1053
+ else {
1054
+ console.log(" " + DIM("i don't see a repo context here yet, but i can still help with your identity, links, memories, and private context."));
1055
+ }
1056
+ if (recentProjects.length > 0) {
1057
+ console.log(" " + DIM("recently active: ") + recentProjects.map((name) => chalk_1.default.cyan(name)).join(DIM(", ")));
1058
+ }
1059
+ const topOpportunity = !projectCtx ? (0, project_1.getTopProjectOpportunity)(recentInsights) : null;
1060
+ if (topOpportunity) {
1061
+ console.log(" " + ACCENT("next opening i see.") + " " + DIM(topOpportunity.summary));
1062
+ console.log(" " + DIM("run:"));
1063
+ console.log(" " + chalk_1.default.cyan(topOpportunity.suggestedCommand));
1064
+ console.log(" " + DIM("then i'll help tighten it up."));
1065
+ }
1066
+ console.log("");
1067
+ console.log(" " + chalk_1.default.bold("you.md chat"));
1068
+ console.log(" " + DIM("talk naturally. i'll update your identity, spot useful structure, and suggest next moves."));
1069
+ console.log("");
1070
+ return investigation;
1071
+ }
1072
+ function buildYouLaunchIntro(projectCtx, bundleDir, investigation) {
1073
+ const displayName = readDisplayName(bundleDir).split(" ")[0];
1074
+ const recentInsights = (0, project_1.getRecentProjectInsights)(process.cwd(), 6);
1075
+ const recentProjects = (0, project_1.getFeaturedRecentProjectNames)(recentInsights, 6);
1076
+ const lines = [];
1077
+ lines.push(`hi ${displayName}. i'm U — i help other agents know you.`);
1078
+ if (projectCtx) {
1079
+ lines.push(`i already clocked that you're inside ${projectCtx.name}.`);
1080
+ if (repoNeedsBootstrap(projectCtx.root)) {
1081
+ lines.push("this repo still wants cleaner agent wiring, so i can scaffold that whenever you want.");
1082
+ }
1083
+ }
1084
+ else if (recentProjects.length > 0) {
1085
+ lines.push(`recently you've been orbiting ${recentProjects.slice(0, 3).join(", ")}.`);
1086
+ const topOpportunity = (0, project_1.getTopProjectOpportunity)(recentInsights);
1087
+ if (topOpportunity) {
1088
+ lines.push(`biggest opening i see: ${topOpportunity.summary}`);
1089
+ }
1090
+ }
1091
+ else {
1092
+ lines.push("clean slate. we can still shape your identity, private context, or project structure from here.");
1093
+ }
1094
+ if (investigation.findings.length > 0) {
1095
+ lines.push(`quick read: ${investigation.findings.slice(0, 2).join(" ")}`);
1096
+ }
1097
+ lines.push("what are we moving forward right now?");
1098
+ return lines.join("\n\n");
1099
+ }
842
1100
  // ─── Main chat command ────────────────────────────────────────────────
843
1101
  async function chatCommand() {
844
- if (!(0, config_1.localBundleExists)()) {
1102
+ const bundleDir = resolveBundleDirForChat();
1103
+ if (!bundleDir) {
845
1104
  console.log("");
846
1105
  console.log(chalk_1.default.yellow(" no .youmd/ directory found."));
847
1106
  console.log(" run " +
@@ -850,17 +1109,16 @@ async function chatCommand() {
850
1109
  console.log("");
851
1110
  return;
852
1111
  }
853
- const bundleDir = (0, config_1.getLocalBundleDir)();
854
1112
  const apiKey = (0, onboarding_1.getOpenRouterKey)();
855
1113
  const rl = createRL();
856
1114
  // Detect project context (legacy detection from config.ts)
857
- const projectCtx = (0, config_1.detectProjectContext)();
1115
+ const rawProjectCtx = (0, config_1.detectProjectContext)();
1116
+ const projectCtx = rawProjectCtx && path.resolve(rawProjectCtx.root) !== path.resolve(os.homedir())
1117
+ ? rawProjectCtx
1118
+ : null;
858
1119
  let projectContextBlock = "";
859
1120
  let activeProjectDir = null;
860
1121
  if (projectCtx) {
861
- console.log("");
862
- console.log(" " + chalk_1.default.hex("#C46A3A")("project:") + " " + chalk_1.default.white(projectCtx.name) +
863
- chalk_1.default.dim(` (${projectCtx.root})`));
864
1122
  // Try the new file-system project context first
865
1123
  const projectsRoot = (0, project_1.findProjectsRoot)();
866
1124
  if (projectsRoot) {
@@ -887,10 +1145,8 @@ async function chatCommand() {
887
1145
  projectContextBlock = `\n\n--- project context ---\n${parts.join("\n")}`;
888
1146
  }
889
1147
  }
890
- console.log("");
891
- console.log(" " + chalk_1.default.bold("you.md chat"));
892
- console.log(chalk_1.default.dim(" talk to update your profile. /help for commands."));
893
- console.log("");
1148
+ const launchInvestigation = await printChatOpening(bundleDir, projectCtx);
1149
+ await printUpdateHint();
894
1150
  // Load current profile as context
895
1151
  const currentBundle = loadCurrentBundle(bundleDir);
896
1152
  // Load agent directives from you.json if available
@@ -927,6 +1183,9 @@ async function chatCommand() {
927
1183
  if (profileHint) {
928
1184
  greetingInstruction = `greet me like you remember me from last time. reference something specific from my profile (like my current focus, a project, or my background) to show you know who i am. then ask what i'd like to update. keep it to 2-3 sentences.`;
929
1185
  }
1186
+ if (projectCtx && repoNeedsBootstrap(projectCtx.root)) {
1187
+ greetingInstruction += " i am inside a repo that is missing some agent/project wiring. briefly mention that you noticed it and that you can set it up if i want, but do not derail the opening message into a long checklist.";
1188
+ }
930
1189
  const messages = [
931
1190
  { role: "system", content: CHAT_SYSTEM_PROMPT },
932
1191
  {
@@ -935,32 +1194,43 @@ async function chatCommand() {
935
1194
  },
936
1195
  ];
937
1196
  // Initial greeting from agent
938
- let response;
939
- try {
940
- response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1197
+ if (process.env.YOUMD_LAUNCH_SURFACE === "you") {
1198
+ const proactiveIntro = buildYouLaunchIntro(projectCtx, bundleDir, launchInvestigation);
1199
+ messages.push({ role: "assistant", content: proactiveIntro });
1200
+ printAgentMessage(proactiveIntro);
941
1201
  }
942
- catch (err) {
943
- console.log(chalk_1.default.red(` failed to connect: ${err instanceof Error ? err.message : String(err)}`));
944
- console.log(chalk_1.default.dim(" chat requires the AI service. try again later."));
945
- console.log("");
946
- rl.close();
947
- return;
948
- }
949
- messages.push({ role: "assistant", content: response });
950
- const initial = (0, onboarding_1.parseUpdatesFromResponse)(response);
951
- // Write any updates (unlikely on greeting, but handle it)
952
- if (initial.updates.length > 0) {
953
- for (const update of initial.updates) {
954
- (0, onboarding_1.writeSectionFile)(bundleDir, update.section, update.content);
1202
+ else {
1203
+ let response;
1204
+ let streamed = false;
1205
+ try {
1206
+ const result = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1207
+ response = result.text;
1208
+ streamed = result.streamed;
1209
+ }
1210
+ catch (err) {
1211
+ console.log(chalk_1.default.red(` failed to connect: ${err instanceof Error ? err.message : String(err)}`));
1212
+ console.log(chalk_1.default.dim(" chat requires the AI service. try again later."));
1213
+ console.log("");
1214
+ rl.close();
1215
+ return;
1216
+ }
1217
+ messages.push({ role: "assistant", content: response });
1218
+ const initial = (0, onboarding_1.parseUpdatesFromResponse)(response);
1219
+ // Write any updates (unlikely on greeting, but handle it)
1220
+ if (initial.updates.length > 0) {
1221
+ for (const update of initial.updates) {
1222
+ (0, onboarding_1.writeSectionFile)(bundleDir, update.section, update.content);
1223
+ }
1224
+ console.log(chalk_1.default.cyan(` [updated: ${initial.updates.map((u) => (0, onboarding_1.sectionLabel)(u.section)).join(", ")}]`));
1225
+ console.log("");
1226
+ }
1227
+ if (!streamed) {
1228
+ printAgentMessage(initial.display);
955
1229
  }
956
- console.log(chalk_1.default.cyan(` [updated: ${initial.updates.map((u) => (0, onboarding_1.sectionLabel)(u.section)).join(", ")}]`));
957
- console.log("");
958
1230
  }
959
- // Only print via rich renderer if we didn't stream (streaming already wrote output)
960
- // But we still need to display parsed output for non-streamed fallback
961
- // Since streaming writes raw text, print formatted version for updates parsing
962
- printAgentMessage(initial.display);
963
1231
  // ── Conversation loop ──────────────────────────────────────────────
1232
+ let response = "";
1233
+ let streamed = false;
964
1234
  while (true) {
965
1235
  const userInput = await ask(rl, chalk_1.default.green(" > ") + "");
966
1236
  if (!userInput)
@@ -1093,7 +1363,9 @@ async function chatCommand() {
1093
1363
  continue;
1094
1364
  // After research, get an LLM response with the injected context
1095
1365
  try {
1096
- response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1366
+ const result = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1367
+ response = result.text;
1368
+ streamed = result.streamed;
1097
1369
  }
1098
1370
  catch (err) {
1099
1371
  console.log(chalk_1.default.red(` AI error: ${err instanceof Error ? err.message : String(err)}`));
@@ -1111,7 +1383,9 @@ async function chatCommand() {
1111
1383
  console.log(chalk_1.default.cyan(` [updated: ${researchParsed.updates.map((u) => (0, onboarding_1.sectionLabel)(u.section)).join(", ")}]`));
1112
1384
  console.log("");
1113
1385
  }
1114
- printAgentMessage(researchParsed.display);
1386
+ if (!streamed) {
1387
+ printAgentMessage(researchParsed.display);
1388
+ }
1115
1389
  continue;
1116
1390
  }
1117
1391
  if (lower === "/rebuild") {
@@ -1147,7 +1421,9 @@ async function chatCommand() {
1147
1421
  }
1148
1422
  }
1149
1423
  try {
1150
- response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1424
+ const result = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1425
+ response = result.text;
1426
+ streamed = result.streamed;
1151
1427
  }
1152
1428
  catch (err) {
1153
1429
  console.log(chalk_1.default.red(` ${err instanceof Error ? err.message : "failed"}`));
@@ -1155,7 +1431,9 @@ async function chatCommand() {
1155
1431
  continue;
1156
1432
  }
1157
1433
  messages.push({ role: "assistant", content: response });
1158
- printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(response).display);
1434
+ if (!streamed) {
1435
+ printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(response).display);
1436
+ }
1159
1437
  continue;
1160
1438
  }
1161
1439
  // ── Detect dragged/pasted file paths ──
@@ -1170,7 +1448,9 @@ async function chatCommand() {
1170
1448
  content: `[USER DROPPED IMAGE: ${path.basename(detectedFile)}]\nthe user dropped an image file into the chat.\n![${path.basename(detectedFile)}](${dataUrl})`,
1171
1449
  });
1172
1450
  try {
1173
- response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1451
+ const result = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1452
+ response = result.text;
1453
+ streamed = result.streamed;
1174
1454
  }
1175
1455
  catch (err) {
1176
1456
  console.log(chalk_1.default.red(` ${err instanceof Error ? err.message : "failed"}`));
@@ -1178,7 +1458,9 @@ async function chatCommand() {
1178
1458
  continue;
1179
1459
  }
1180
1460
  messages.push({ role: "assistant", content: response });
1181
- printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(response).display);
1461
+ if (!streamed) {
1462
+ printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(response).display);
1463
+ }
1182
1464
  continue;
1183
1465
  }
1184
1466
  }
@@ -1192,7 +1474,9 @@ async function chatCommand() {
1192
1474
  content: `[USER DROPPED FILE: ${path.basename(detectedFile)}]\n\`\`\`\n${text.slice(0, 10000)}\n\`\`\`\n\nreview this file and suggest how it relates to my identity or profile.`,
1193
1475
  });
1194
1476
  try {
1195
- response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1477
+ const result = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1478
+ response = result.text;
1479
+ streamed = result.streamed;
1196
1480
  }
1197
1481
  catch (err) {
1198
1482
  console.log(chalk_1.default.red(` ${err instanceof Error ? err.message : "failed"}`));
@@ -1200,7 +1484,9 @@ async function chatCommand() {
1200
1484
  continue;
1201
1485
  }
1202
1486
  messages.push({ role: "assistant", content: response });
1203
- printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(response).display);
1487
+ if (!streamed) {
1488
+ printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(response).display);
1489
+ }
1204
1490
  continue;
1205
1491
  }
1206
1492
  }
@@ -1236,7 +1522,9 @@ async function chatCommand() {
1236
1522
  }
1237
1523
  }
1238
1524
  try {
1239
- response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1525
+ const result = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
1526
+ response = result.text;
1527
+ streamed = result.streamed;
1240
1528
  }
1241
1529
  catch (err) {
1242
1530
  console.log(chalk_1.default.red(` ${err instanceof Error ? err.message : "failed"}`));
@@ -1348,7 +1636,9 @@ async function chatCommand() {
1348
1636
  // non-fatal
1349
1637
  }
1350
1638
  }
1351
- printAgentMessage(parsed.display);
1639
+ if (!streamed) {
1640
+ printAgentMessage(parsed.display);
1641
+ }
1352
1642
  }
1353
1643
  rl.close();
1354
1644
  }