youmd 0.3.3 → 0.4.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.
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +343 -17
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/diff.d.ts +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +261 -8
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/export.d.ts +8 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +97 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +38 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +1 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +91 -0
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/private.d.ts.map +1 -1
- package/dist/commands/private.js +125 -21
- package/dist/commands/private.js.map +1 -1
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +435 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/index.js +22 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +51 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +182 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/project.d.ts +87 -0
- package/dist/lib/project.d.ts.map +1 -0
- package/dist/lib/project.js +345 -0
- package/dist/lib/project.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AA25BA,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAihBjD"}
|
package/dist/commands/chat.js
CHANGED
|
@@ -42,12 +42,127 @@ const fs = __importStar(require("fs"));
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const chalk_1 = __importDefault(require("chalk"));
|
|
44
44
|
const config_1 = require("../lib/config");
|
|
45
|
+
const project_1 = require("../lib/project");
|
|
45
46
|
const compiler_1 = require("../lib/compiler");
|
|
46
47
|
const api_1 = require("../lib/api");
|
|
47
48
|
const render_1 = require("../lib/render");
|
|
48
49
|
const onboarding_1 = require("../lib/onboarding");
|
|
49
50
|
// ─── URL Detection + Scraping (mirrors web useYouAgent) ──────────────
|
|
50
51
|
const CONVEX_SITE_URL = "https://kindly-cassowary-600.convex.site";
|
|
52
|
+
const STREAM_URL = `${CONVEX_SITE_URL}/api/v1/chat/stream`;
|
|
53
|
+
// ─── Streaming LLM client ─────────────────────────────────────────────
|
|
54
|
+
async function streamLLM(_apiKey, messages, onToken) {
|
|
55
|
+
const res = await fetch(STREAM_URL, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "Content-Type": "application/json" },
|
|
58
|
+
body: JSON.stringify({ messages }),
|
|
59
|
+
signal: AbortSignal.timeout(120000),
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
const body = await res.text();
|
|
63
|
+
throw new Error(`Stream error (${res.status}): ${body}`);
|
|
64
|
+
}
|
|
65
|
+
if (!res.body) {
|
|
66
|
+
throw new Error("No response body from stream endpoint");
|
|
67
|
+
}
|
|
68
|
+
const reader = res.body.getReader();
|
|
69
|
+
const decoder = new TextDecoder();
|
|
70
|
+
let fullText = "";
|
|
71
|
+
let buffer = "";
|
|
72
|
+
while (true) {
|
|
73
|
+
const { done, value } = await reader.read();
|
|
74
|
+
if (done)
|
|
75
|
+
break;
|
|
76
|
+
buffer += decoder.decode(value, { stream: true });
|
|
77
|
+
// Process complete SSE lines
|
|
78
|
+
const lines = buffer.split("\n");
|
|
79
|
+
// Keep the last potentially incomplete line in the buffer
|
|
80
|
+
buffer = lines.pop() || "";
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const trimmed = line.trim();
|
|
83
|
+
if (!trimmed)
|
|
84
|
+
continue;
|
|
85
|
+
if (trimmed.startsWith("data: ")) {
|
|
86
|
+
const data = trimmed.slice(6);
|
|
87
|
+
if (data === "[DONE]") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(data);
|
|
92
|
+
if (parsed.text) {
|
|
93
|
+
fullText += parsed.text;
|
|
94
|
+
onToken(parsed.text);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Skip malformed JSON chunks
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Process any remaining buffer
|
|
104
|
+
if (buffer.trim()) {
|
|
105
|
+
const trimmed = buffer.trim();
|
|
106
|
+
if (trimmed.startsWith("data: ")) {
|
|
107
|
+
const data = trimmed.slice(6);
|
|
108
|
+
if (data !== "[DONE]") {
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(data);
|
|
111
|
+
if (parsed.text) {
|
|
112
|
+
fullText += parsed.text;
|
|
113
|
+
onToken(parsed.text);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Skip
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return fullText;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Call LLM with streaming, falling back to blocking callLLM on failure.
|
|
126
|
+
* Returns the full response text.
|
|
127
|
+
*/
|
|
128
|
+
async function callLLMWithStreaming(apiKey, messages, spinnerLabel) {
|
|
129
|
+
const thinkSpinner = new render_1.BrailleSpinner(spinnerLabel);
|
|
130
|
+
thinkSpinner.start();
|
|
131
|
+
try {
|
|
132
|
+
let firstToken = true;
|
|
133
|
+
const response = await streamLLM(apiKey, messages, (token) => {
|
|
134
|
+
if (firstToken) {
|
|
135
|
+
// Clear the spinner line before writing streamed text
|
|
136
|
+
thinkSpinner.stop();
|
|
137
|
+
process.stdout.write(" ");
|
|
138
|
+
firstToken = false;
|
|
139
|
+
}
|
|
140
|
+
process.stdout.write(token);
|
|
141
|
+
});
|
|
142
|
+
if (!firstToken) {
|
|
143
|
+
// We streamed something -- add trailing newline
|
|
144
|
+
process.stdout.write("\n");
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// No tokens received -- clear spinner
|
|
148
|
+
thinkSpinner.stop();
|
|
149
|
+
}
|
|
150
|
+
return response;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Streaming failed -- fall back to blocking call
|
|
154
|
+
thinkSpinner.update("streaming unavailable, waiting for response");
|
|
155
|
+
try {
|
|
156
|
+
const response = await (0, onboarding_1.callLLM)(apiKey, messages);
|
|
157
|
+
thinkSpinner.stop();
|
|
158
|
+
return response;
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
thinkSpinner.fail(err instanceof Error ? err.message : "failed");
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
51
166
|
function detectSourcesInMessage(text) {
|
|
52
167
|
const sources = [];
|
|
53
168
|
const seen = new Set();
|
|
@@ -212,6 +327,51 @@ function parsePrivateUpdates(text) {
|
|
|
212
327
|
return updates;
|
|
213
328
|
}
|
|
214
329
|
const scrapedSources = new Set();
|
|
330
|
+
// ─── Image/File handling ──────────────────────────────────────────────
|
|
331
|
+
function detectFilePath(input) {
|
|
332
|
+
const trimmed = input.trim();
|
|
333
|
+
// Detect dragged file paths (terminals add quotes or escape spaces)
|
|
334
|
+
// Strip surrounding quotes
|
|
335
|
+
const unquoted = trimmed.replace(/^['"]|['"]$/g, "");
|
|
336
|
+
// Check if it looks like a file path
|
|
337
|
+
if ((unquoted.startsWith("/") || unquoted.startsWith("~") || unquoted.startsWith("./")) &&
|
|
338
|
+
fs.existsSync(unquoted)) {
|
|
339
|
+
return unquoted;
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
function isImageFile(filePath) {
|
|
344
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
345
|
+
return [".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"].includes(ext);
|
|
346
|
+
}
|
|
347
|
+
function fileToBase64DataUrl(filePath) {
|
|
348
|
+
try {
|
|
349
|
+
const buffer = fs.readFileSync(filePath);
|
|
350
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
351
|
+
const mimeMap = {
|
|
352
|
+
".png": "image/png",
|
|
353
|
+
".jpg": "image/jpeg",
|
|
354
|
+
".jpeg": "image/jpeg",
|
|
355
|
+
".gif": "image/gif",
|
|
356
|
+
".webp": "image/webp",
|
|
357
|
+
".bmp": "image/bmp",
|
|
358
|
+
".svg": "image/svg+xml",
|
|
359
|
+
};
|
|
360
|
+
const mime = mimeMap[ext] || "application/octet-stream";
|
|
361
|
+
return `data:${mime};base64,${buffer.toString("base64")}`;
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function readTextFile(filePath) {
|
|
368
|
+
try {
|
|
369
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
215
375
|
// ─── Constants ────────────────────────────────────────────────────────
|
|
216
376
|
const CHAT_SYSTEM_PROMPT = `you are the you.md agent — the first AI that truly knows people. you help humans build and maintain their identity file for the agent internet. not a chatbot. not an assistant. an identity specialist with a personality.
|
|
217
377
|
|
|
@@ -269,7 +429,16 @@ your job:
|
|
|
269
429
|
5. be proactive: "looks like your projects section could use an update — want to add that?"
|
|
270
430
|
6. when they share something sensitive, ask: "want me to keep that private or add it to your public profile?"
|
|
271
431
|
|
|
272
|
-
rules: each section starts with YAML frontmatter. real markdown, not placeholders. output FULL section content each time. be substantive — write from what you actually know
|
|
432
|
+
rules: each section starts with YAML frontmatter. real markdown, not placeholders. output FULL section content each time. be substantive — write from what you actually know.
|
|
433
|
+
|
|
434
|
+
--- project context updates ---
|
|
435
|
+
|
|
436
|
+
if the user is working in a project (you'll see a [PROJECT CONTEXT] block), you can update project files. when you learn something about the project — a decision made, a task completed, a feature shipped, a new requirement — output:
|
|
437
|
+
\`\`\`json
|
|
438
|
+
{"project_updates": [{"file": "context/todo.md", "content": "updated content..."}]}
|
|
439
|
+
\`\`\`
|
|
440
|
+
allowed files: context/todo.md, context/features.md, context/changelog.md, context/decisions.md, context/prd.md, agent/instructions.md, agent/memory.json, private/notes.md
|
|
441
|
+
only output project_updates when something actually changed. the system will write these files and show a notice to the user.`;
|
|
273
442
|
const SLASH_COMMANDS = {
|
|
274
443
|
"/status": "show bundle status",
|
|
275
444
|
"/preview": "show profile preview",
|
|
@@ -280,6 +449,7 @@ const SLASH_COMMANDS = {
|
|
|
280
449
|
"/memory": "show memory summary + stats",
|
|
281
450
|
"/recall": "show recent memories (or /recall query)",
|
|
282
451
|
"/private": "show private context (notes, links, projects)",
|
|
452
|
+
"/image <path>": "attach an image or file",
|
|
283
453
|
"/rebuild": "recompile the bundle",
|
|
284
454
|
"/help": "show available commands",
|
|
285
455
|
"/done": "exit chat",
|
|
@@ -625,12 +795,74 @@ async function chatCommand() {
|
|
|
625
795
|
const bundleDir = (0, config_1.getLocalBundleDir)();
|
|
626
796
|
const apiKey = (0, onboarding_1.getOpenRouterKey)();
|
|
627
797
|
const rl = createRL();
|
|
798
|
+
// Detect project context (legacy detection from config.ts)
|
|
799
|
+
const projectCtx = (0, config_1.detectProjectContext)();
|
|
800
|
+
let projectContextBlock = "";
|
|
801
|
+
let activeProjectDir = null;
|
|
802
|
+
if (projectCtx) {
|
|
803
|
+
console.log("");
|
|
804
|
+
console.log(" " + chalk_1.default.hex("#C46A3A")("project:") + " " + chalk_1.default.white(projectCtx.name) +
|
|
805
|
+
chalk_1.default.dim(` (${projectCtx.root})`));
|
|
806
|
+
// Try the new file-system project context first
|
|
807
|
+
const projectsRoot = (0, project_1.findProjectsRoot)();
|
|
808
|
+
if (projectsRoot) {
|
|
809
|
+
const detected = (0, project_1.detectCurrentProject)(projectsRoot);
|
|
810
|
+
if (detected) {
|
|
811
|
+
activeProjectDir = (0, project_1.getProjectDir)(projectsRoot, detected);
|
|
812
|
+
const injection = (0, project_1.buildProjectContextInjection)(activeProjectDir);
|
|
813
|
+
if (injection) {
|
|
814
|
+
projectContextBlock = `\n\n--- project context ---\n${injection}`;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
// Fallback to legacy project context if new system didn't produce anything
|
|
819
|
+
if (!projectContextBlock) {
|
|
820
|
+
const projectNotes = (0, config_1.readProjectPrivateNotes)(projectCtx.name);
|
|
821
|
+
const parts = [];
|
|
822
|
+
parts.push(`the user is currently working in project: ${projectCtx.name} at ${projectCtx.root}`);
|
|
823
|
+
if (projectCtx.youmdProject?.description) {
|
|
824
|
+
parts.push(`project description: ${projectCtx.youmdProject.description}`);
|
|
825
|
+
}
|
|
826
|
+
if (projectNotes) {
|
|
827
|
+
parts.push(`project-specific private notes:\n${projectNotes}`);
|
|
828
|
+
}
|
|
829
|
+
projectContextBlock = `\n\n--- project context ---\n${parts.join("\n")}`;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
628
832
|
console.log("");
|
|
629
833
|
console.log(" " + chalk_1.default.bold("you.md chat"));
|
|
630
834
|
console.log(chalk_1.default.dim(" talk to update your profile. /help for commands."));
|
|
631
835
|
console.log("");
|
|
632
836
|
// Load current profile as context
|
|
633
837
|
const currentBundle = loadCurrentBundle(bundleDir);
|
|
838
|
+
// Load agent directives from you.json if available
|
|
839
|
+
let directivesContext = "";
|
|
840
|
+
const youJsonPath = path.join(bundleDir, "you.json");
|
|
841
|
+
if (fs.existsSync(youJsonPath)) {
|
|
842
|
+
try {
|
|
843
|
+
const youJson = JSON.parse(fs.readFileSync(youJsonPath, "utf-8"));
|
|
844
|
+
const directives = youJson.agent_directives;
|
|
845
|
+
if (directives) {
|
|
846
|
+
const parts = [];
|
|
847
|
+
if (directives.communication_style)
|
|
848
|
+
parts.push(`communication style: ${directives.communication_style}`);
|
|
849
|
+
if (directives.default_stack)
|
|
850
|
+
parts.push(`default stack: ${directives.default_stack}`);
|
|
851
|
+
if (directives.current_goal)
|
|
852
|
+
parts.push(`current goal: ${directives.current_goal}`);
|
|
853
|
+
if (directives.decision_framework)
|
|
854
|
+
parts.push(`decision framework: ${directives.decision_framework}`);
|
|
855
|
+
if (directives.negative_prompts && directives.negative_prompts.length > 0)
|
|
856
|
+
parts.push(`never do: ${directives.negative_prompts.join("; ")}`);
|
|
857
|
+
if (parts.length > 0) {
|
|
858
|
+
directivesContext = `\n\n--- agent directives (follow these when interacting with me) ---\n${parts.join("\n")}`;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
// non-fatal — skip directives if you.json is malformed
|
|
864
|
+
}
|
|
865
|
+
}
|
|
634
866
|
// Extract profile details for a personalized greeting prompt
|
|
635
867
|
const profileHint = extractProfileHint(bundleDir);
|
|
636
868
|
let greetingInstruction = "greet me briefly and ask what i'd like to update or work on. keep it short.";
|
|
@@ -641,25 +873,21 @@ async function chatCommand() {
|
|
|
641
873
|
{ role: "system", content: CHAT_SYSTEM_PROMPT },
|
|
642
874
|
{
|
|
643
875
|
role: "user",
|
|
644
|
-
content: `here is my current identity bundle:\n\n${currentBundle}\n\n${greetingInstruction}`,
|
|
876
|
+
content: `here is my current identity bundle:\n\n${currentBundle}${directivesContext}${projectContextBlock}\n\n${greetingInstruction}`,
|
|
645
877
|
},
|
|
646
878
|
];
|
|
647
879
|
// Initial greeting from agent
|
|
648
|
-
const spinner = new onboarding_1.Spinner((0, onboarding_1.randomThinking)());
|
|
649
|
-
spinner.start();
|
|
650
880
|
let response;
|
|
651
881
|
try {
|
|
652
|
-
response = await (0, onboarding_1.
|
|
882
|
+
response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
|
|
653
883
|
}
|
|
654
884
|
catch (err) {
|
|
655
|
-
spinner.stop();
|
|
656
885
|
console.log(chalk_1.default.red(` failed to connect: ${err instanceof Error ? err.message : String(err)}`));
|
|
657
886
|
console.log(chalk_1.default.dim(" chat requires the AI service. try again later."));
|
|
658
887
|
console.log("");
|
|
659
888
|
rl.close();
|
|
660
889
|
return;
|
|
661
890
|
}
|
|
662
|
-
spinner.stop();
|
|
663
891
|
messages.push({ role: "assistant", content: response });
|
|
664
892
|
const initial = (0, onboarding_1.parseUpdatesFromResponse)(response);
|
|
665
893
|
// Write any updates (unlikely on greeting, but handle it)
|
|
@@ -670,6 +898,9 @@ async function chatCommand() {
|
|
|
670
898
|
console.log(chalk_1.default.cyan(` [updated: ${initial.updates.map((u) => (0, onboarding_1.sectionLabel)(u.section)).join(", ")}]`));
|
|
671
899
|
console.log("");
|
|
672
900
|
}
|
|
901
|
+
// Only print via rich renderer if we didn't stream (streaming already wrote output)
|
|
902
|
+
// But we still need to display parsed output for non-streamed fallback
|
|
903
|
+
// Since streaming writes raw text, print formatted version for updates parsing
|
|
673
904
|
printAgentMessage(initial.display);
|
|
674
905
|
// ── Conversation loop ──────────────────────────────────────────────
|
|
675
906
|
while (true) {
|
|
@@ -803,20 +1034,16 @@ async function chatCommand() {
|
|
|
803
1034
|
if (!researchOk)
|
|
804
1035
|
continue;
|
|
805
1036
|
// After research, get an LLM response with the injected context
|
|
806
|
-
const researchSpinner = new onboarding_1.Spinner((0, onboarding_1.randomThinking)());
|
|
807
|
-
researchSpinner.start();
|
|
808
1037
|
try {
|
|
809
|
-
response = await (0, onboarding_1.
|
|
1038
|
+
response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
|
|
810
1039
|
}
|
|
811
1040
|
catch (err) {
|
|
812
|
-
researchSpinner.stop();
|
|
813
1041
|
console.log(chalk_1.default.red(` AI error: ${err instanceof Error ? err.message : String(err)}`));
|
|
814
1042
|
console.log(chalk_1.default.dim(" try again."));
|
|
815
1043
|
console.log("");
|
|
816
1044
|
messages.pop();
|
|
817
1045
|
continue;
|
|
818
1046
|
}
|
|
819
|
-
researchSpinner.stop();
|
|
820
1047
|
messages.push({ role: "assistant", content: response });
|
|
821
1048
|
const researchParsed = (0, onboarding_1.parseUpdatesFromResponse)(response);
|
|
822
1049
|
if (researchParsed.updates.length > 0) {
|
|
@@ -833,6 +1060,93 @@ async function chatCommand() {
|
|
|
833
1060
|
handleRebuild(bundleDir);
|
|
834
1061
|
continue;
|
|
835
1062
|
}
|
|
1063
|
+
// ── Handle /image command ──
|
|
1064
|
+
if (lower.startsWith("/image ")) {
|
|
1065
|
+
const imgPath = userInput.slice(7).trim().replace(/^['"]|['"]$/g, "");
|
|
1066
|
+
if (!fs.existsSync(imgPath)) {
|
|
1067
|
+
console.log(chalk_1.default.hex("#C46A3A")(` file not found: ${imgPath}`));
|
|
1068
|
+
console.log("");
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
if (isImageFile(imgPath)) {
|
|
1072
|
+
const dataUrl = fileToBase64DataUrl(imgPath);
|
|
1073
|
+
if (dataUrl) {
|
|
1074
|
+
console.log(chalk_1.default.green(` ✓`) + chalk_1.default.dim(` attached image: ${path.basename(imgPath)}`));
|
|
1075
|
+
messages.push({
|
|
1076
|
+
role: "user",
|
|
1077
|
+
content: `[USER ATTACHED IMAGE: ${path.basename(imgPath)}]\nthe user attached an image file. describe or use it as needed.\n`,
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
const text = readTextFile(imgPath);
|
|
1083
|
+
if (text) {
|
|
1084
|
+
console.log(chalk_1.default.green(` ✓`) + chalk_1.default.dim(` attached file: ${path.basename(imgPath)} (${text.length} chars)`));
|
|
1085
|
+
messages.push({
|
|
1086
|
+
role: "user",
|
|
1087
|
+
content: `[USER ATTACHED FILE: ${path.basename(imgPath)}]\n\`\`\`\n${text.slice(0, 10000)}\n\`\`\``,
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
try {
|
|
1092
|
+
response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
|
|
1093
|
+
}
|
|
1094
|
+
catch (err) {
|
|
1095
|
+
console.log(chalk_1.default.red(` ${err instanceof Error ? err.message : "failed"}`));
|
|
1096
|
+
messages.pop();
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
messages.push({ role: "assistant", content: response });
|
|
1100
|
+
printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(response).display);
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
// ── Detect dragged/pasted file paths ──
|
|
1104
|
+
const detectedFile = detectFilePath(userInput);
|
|
1105
|
+
if (detectedFile) {
|
|
1106
|
+
if (isImageFile(detectedFile)) {
|
|
1107
|
+
const dataUrl = fileToBase64DataUrl(detectedFile);
|
|
1108
|
+
if (dataUrl) {
|
|
1109
|
+
console.log(chalk_1.default.green(` ✓`) + chalk_1.default.dim(` detected image: ${path.basename(detectedFile)}`));
|
|
1110
|
+
messages.push({
|
|
1111
|
+
role: "user",
|
|
1112
|
+
content: `[USER DROPPED IMAGE: ${path.basename(detectedFile)}]\nthe user dropped an image file into the chat.\n`,
|
|
1113
|
+
});
|
|
1114
|
+
try {
|
|
1115
|
+
response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
|
|
1116
|
+
}
|
|
1117
|
+
catch (err) {
|
|
1118
|
+
console.log(chalk_1.default.red(` ${err instanceof Error ? err.message : "failed"}`));
|
|
1119
|
+
messages.pop();
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
messages.push({ role: "assistant", content: response });
|
|
1123
|
+
printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(response).display);
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
// Text file — inject content
|
|
1129
|
+
const text = readTextFile(detectedFile);
|
|
1130
|
+
if (text) {
|
|
1131
|
+
console.log(chalk_1.default.green(` ✓`) + chalk_1.default.dim(` detected file: ${path.basename(detectedFile)} (${text.length} chars)`));
|
|
1132
|
+
messages.push({
|
|
1133
|
+
role: "user",
|
|
1134
|
+
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.`,
|
|
1135
|
+
});
|
|
1136
|
+
try {
|
|
1137
|
+
response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
|
|
1138
|
+
}
|
|
1139
|
+
catch (err) {
|
|
1140
|
+
console.log(chalk_1.default.red(` ${err instanceof Error ? err.message : "failed"}`));
|
|
1141
|
+
messages.pop();
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
messages.push({ role: "assistant", content: response });
|
|
1145
|
+
printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(response).display);
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
836
1150
|
// ── Auto-detect URLs and scrape before sending to LLM ──
|
|
837
1151
|
const detectedSources = detectSourcesInMessage(userInput);
|
|
838
1152
|
const newSources = detectedSources.filter((s) => !scrapedSources.has(`${s.platform}:${s.username || s.url}`));
|
|
@@ -863,13 +1177,11 @@ async function chatCommand() {
|
|
|
863
1177
|
});
|
|
864
1178
|
}
|
|
865
1179
|
}
|
|
866
|
-
const thinkSpinner = new render_1.BrailleSpinner((0, onboarding_1.randomThinking)());
|
|
867
|
-
thinkSpinner.start();
|
|
868
1180
|
try {
|
|
869
|
-
response = await (0, onboarding_1.
|
|
1181
|
+
response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
|
|
870
1182
|
}
|
|
871
1183
|
catch (err) {
|
|
872
|
-
|
|
1184
|
+
console.log(chalk_1.default.red(` ${err instanceof Error ? err.message : "failed"}`));
|
|
873
1185
|
console.log(chalk_1.default.dim(" try again."));
|
|
874
1186
|
console.log("");
|
|
875
1187
|
messages.pop();
|
|
@@ -877,7 +1189,6 @@ async function chatCommand() {
|
|
|
877
1189
|
messages.pop(); // remove scrape context too
|
|
878
1190
|
continue;
|
|
879
1191
|
}
|
|
880
|
-
thinkSpinner.stop();
|
|
881
1192
|
messages.push({ role: "assistant", content: response });
|
|
882
1193
|
const parsed = (0, onboarding_1.parseUpdatesFromResponse)(response);
|
|
883
1194
|
// Write section updates
|
|
@@ -923,6 +1234,21 @@ async function chatCommand() {
|
|
|
923
1234
|
}
|
|
924
1235
|
}
|
|
925
1236
|
}
|
|
1237
|
+
// Handle project context updates
|
|
1238
|
+
if (activeProjectDir) {
|
|
1239
|
+
const projUpdates = (0, project_1.parseProjectUpdates)(response);
|
|
1240
|
+
if (projUpdates.length > 0) {
|
|
1241
|
+
for (const pu of projUpdates) {
|
|
1242
|
+
try {
|
|
1243
|
+
(0, project_1.updateProjectFile)(activeProjectDir, pu.file, pu.content);
|
|
1244
|
+
console.log(chalk_1.default.hex("#C46A3A")(` [updated project context: ${pu.file}]`));
|
|
1245
|
+
}
|
|
1246
|
+
catch {
|
|
1247
|
+
// non-fatal
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
926
1252
|
printAgentMessage(parsed.display);
|
|
927
1253
|
}
|
|
928
1254
|
rl.close();
|