youmd 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +718 -52
- 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/keys.d.ts +2 -1
- package/dist/commands/keys.d.ts.map +1 -1
- package/dist/commands/keys.js +176 -40
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/link.d.ts +7 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +235 -59
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/memories.d.ts +2 -0
- package/dist/commands/memories.d.ts.map +1 -0
- package/dist/commands/memories.js +113 -0
- package/dist/commands/memories.js.map +1 -0
- package/dist/commands/private.d.ts +5 -0
- package/dist/commands/private.d.ts.map +1 -0
- package/dist/commands/private.js +554 -0
- package/dist/commands/private.js.map +1 -0
- 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/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +23 -0
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +21 -0
- package/dist/commands/push.js.map +1 -1
- package/dist/index.js +37 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +85 -0
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +83 -0
- package/dist/lib/api.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/onboarding.d.ts +2 -1
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +18 -11
- package/dist/lib/onboarding.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/dist/lib/render.d.ts +71 -0
- package/dist/lib/render.d.ts.map +1 -0
- package/dist/lib/render.js +286 -0
- package/dist/lib/render.js.map +1 -0
- package/package.json +1 -1
package/dist/commands/chat.js
CHANGED
|
@@ -42,50 +42,403 @@ 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");
|
|
48
|
+
const render_1 = require("../lib/render");
|
|
47
49
|
const onboarding_1 = require("../lib/onboarding");
|
|
50
|
+
// ─── URL Detection + Scraping (mirrors web useYouAgent) ──────────────
|
|
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
|
+
}
|
|
166
|
+
function detectSourcesInMessage(text) {
|
|
167
|
+
const sources = [];
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
let match;
|
|
170
|
+
const xUrlRegex = /(?:https?:\/\/)?(?:www\.)?(?:x\.com|twitter\.com)\/([a-zA-Z0-9_]+)/gi;
|
|
171
|
+
while ((match = xUrlRegex.exec(text)) !== null) {
|
|
172
|
+
const u = match[1];
|
|
173
|
+
if (!["home", "search", "explore", "settings", "i", "intent"].includes(u.toLowerCase()) && !seen.has(`x:${u}`)) {
|
|
174
|
+
seen.add(`x:${u}`);
|
|
175
|
+
sources.push({ platform: "x", url: `https://x.com/${u}`, username: u });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const ghUrlRegex = /(?:https?:\/\/)?(?:www\.)?github\.com\/([a-zA-Z0-9_-]+)/gi;
|
|
179
|
+
while ((match = ghUrlRegex.exec(text)) !== null) {
|
|
180
|
+
const u = match[1];
|
|
181
|
+
if (!["orgs", "topics", "settings", "marketplace", "explore"].includes(u.toLowerCase()) && !seen.has(`github:${u}`)) {
|
|
182
|
+
seen.add(`github:${u}`);
|
|
183
|
+
sources.push({ platform: "github", url: `https://github.com/${u}`, username: u });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const liUrlRegex = /(?:https?:\/\/)?(?:www\.)?linkedin\.com\/in\/([a-zA-Z0-9_-]+)/gi;
|
|
187
|
+
while ((match = liUrlRegex.exec(text)) !== null) {
|
|
188
|
+
const slug = match[1];
|
|
189
|
+
if (!seen.has(`linkedin:${slug}`)) {
|
|
190
|
+
seen.add(`linkedin:${slug}`);
|
|
191
|
+
sources.push({ platform: "linkedin", url: `https://linkedin.com/in/${slug}`, username: slug });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const urlRegex = /https?:\/\/[^\s<>"']+/gi;
|
|
195
|
+
while ((match = urlRegex.exec(text)) !== null) {
|
|
196
|
+
const url = match[0].replace(/[.,;:)\]]+$/, "");
|
|
197
|
+
if (!url.includes("x.com") && !url.includes("twitter.com") && !url.includes("github.com") && !url.includes("linkedin.com") && !seen.has(url)) {
|
|
198
|
+
seen.add(url);
|
|
199
|
+
sources.push({ platform: "website", url });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const bareDomainRegex = /(?<![/\w])([a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.(?:com|co|io|ai|dev|org|net|app|xyz|me)(?:\/[^\s<>"']*)?)/gi;
|
|
203
|
+
while ((match = bareDomainRegex.exec(text)) !== null) {
|
|
204
|
+
let domain = match[1].replace(/[.,;:)\]]+$/, "");
|
|
205
|
+
if (domain.includes("x.com") || domain.includes("github.com") || domain.includes("linkedin.com"))
|
|
206
|
+
continue;
|
|
207
|
+
const url = `https://${domain}`;
|
|
208
|
+
if (!seen.has(url)) {
|
|
209
|
+
seen.add(url);
|
|
210
|
+
sources.push({ platform: "website", url });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return sources;
|
|
214
|
+
}
|
|
215
|
+
async function scrapeSource(source) {
|
|
216
|
+
try {
|
|
217
|
+
// X: use Grok enrichment (syndication API is dead)
|
|
218
|
+
if (source.platform === "x" && source.username) {
|
|
219
|
+
const res = await fetch(`${CONVEX_SITE_URL}/api/v1/enrich-x`, {
|
|
220
|
+
method: "POST",
|
|
221
|
+
headers: { "Content-Type": "application/json" },
|
|
222
|
+
body: JSON.stringify({ xUsername: source.username, profileData: {} }),
|
|
223
|
+
signal: AbortSignal.timeout(30000),
|
|
224
|
+
});
|
|
225
|
+
if (res.ok) {
|
|
226
|
+
const data = await res.json();
|
|
227
|
+
if (data.success && data.analysis) {
|
|
228
|
+
return `[SCRAPE RESULT: x @${source.username}]\nx analysis via grok:\n${data.analysis}\nprofile_image: https://unavatar.io/x/${source.username}`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// LinkedIn: use Apify enrichment
|
|
233
|
+
if (source.platform === "linkedin" && source.username) {
|
|
234
|
+
const normalizedUrl = `https://www.linkedin.com/in/${source.username}/`;
|
|
235
|
+
const res = await fetch(`${CONVEX_SITE_URL}/api/v1/enrich-linkedin`, {
|
|
236
|
+
method: "POST",
|
|
237
|
+
headers: { "Content-Type": "application/json" },
|
|
238
|
+
body: JSON.stringify({ linkedinUrl: normalizedUrl }),
|
|
239
|
+
signal: AbortSignal.timeout(60000),
|
|
240
|
+
});
|
|
241
|
+
if (res.ok) {
|
|
242
|
+
const data = await res.json();
|
|
243
|
+
if (data.success && data.profile) {
|
|
244
|
+
const p = data.profile;
|
|
245
|
+
const parts = [`[SCRAPE RESULT: linkedin @${source.username}]`];
|
|
246
|
+
if (p.fullName)
|
|
247
|
+
parts.push(`name: ${p.fullName}`);
|
|
248
|
+
if (p.headline)
|
|
249
|
+
parts.push(`headline: ${p.headline}`);
|
|
250
|
+
if (p.about)
|
|
251
|
+
parts.push(`about: ${String(p.about).slice(0, 500)}`);
|
|
252
|
+
if (p.location)
|
|
253
|
+
parts.push(`location: ${p.location}`);
|
|
254
|
+
if (p.connections)
|
|
255
|
+
parts.push(`connections: ${p.connections}`);
|
|
256
|
+
if (p.profileImageUrl)
|
|
257
|
+
parts.push(`profile_image: ${p.profileImageUrl}`);
|
|
258
|
+
return parts.join("\n");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// General scrape (GitHub, websites)
|
|
263
|
+
const res = await fetch(`${CONVEX_SITE_URL}/api/v1/scrape`, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: { "Content-Type": "application/json" },
|
|
266
|
+
body: JSON.stringify({ url: source.url, username: source.username, platform: source.platform }),
|
|
267
|
+
signal: AbortSignal.timeout(30000),
|
|
268
|
+
});
|
|
269
|
+
if (!res.ok)
|
|
270
|
+
return "";
|
|
271
|
+
const data = await res.json();
|
|
272
|
+
if (!data.success)
|
|
273
|
+
return "";
|
|
274
|
+
const d = (data.data || {});
|
|
275
|
+
const parts = [`[SCRAPE RESULT: ${source.platform} @${d.username || source.username || ""}]`];
|
|
276
|
+
if (d.displayName)
|
|
277
|
+
parts.push(`name: ${d.displayName}`);
|
|
278
|
+
if (d.bio)
|
|
279
|
+
parts.push(`bio: ${d.bio}`);
|
|
280
|
+
if (d.location)
|
|
281
|
+
parts.push(`location: ${d.location}`);
|
|
282
|
+
if (d.followers !== null && d.followers !== undefined)
|
|
283
|
+
parts.push(`followers: ${d.followers}`);
|
|
284
|
+
if (d.profileImageUrl)
|
|
285
|
+
parts.push(`profile_image: ${d.profileImageUrl}`);
|
|
286
|
+
const extras = d.extras;
|
|
287
|
+
if (extras?.bodyText)
|
|
288
|
+
parts.push(`page content: ${extras.bodyText}`);
|
|
289
|
+
return parts.join("\n");
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return "";
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function parseMemorySaves(text) {
|
|
296
|
+
const saves = [];
|
|
297
|
+
const blocks = text.matchAll(/```json\s*\n([\s\S]*?)\n```/g);
|
|
298
|
+
for (const match of blocks) {
|
|
299
|
+
try {
|
|
300
|
+
const parsed = JSON.parse(match[1]);
|
|
301
|
+
if (parsed.memory_saves && Array.isArray(parsed.memory_saves)) {
|
|
302
|
+
for (const ms of parsed.memory_saves) {
|
|
303
|
+
if (ms?.category && ms?.content)
|
|
304
|
+
saves.push(ms);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch { /* skip */ }
|
|
309
|
+
}
|
|
310
|
+
return saves;
|
|
311
|
+
}
|
|
312
|
+
function parsePrivateUpdates(text) {
|
|
313
|
+
const updates = [];
|
|
314
|
+
const blocks = text.matchAll(/```json\s*\n([\s\S]*?)\n```/g);
|
|
315
|
+
for (const match of blocks) {
|
|
316
|
+
try {
|
|
317
|
+
const parsed = JSON.parse(match[1]);
|
|
318
|
+
if (parsed.private_updates && Array.isArray(parsed.private_updates)) {
|
|
319
|
+
for (const pu of parsed.private_updates) {
|
|
320
|
+
if (pu?.field)
|
|
321
|
+
updates.push(pu);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch { /* skip */ }
|
|
326
|
+
}
|
|
327
|
+
return updates;
|
|
328
|
+
}
|
|
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
|
+
}
|
|
48
375
|
// ─── Constants ────────────────────────────────────────────────────────
|
|
49
|
-
const CHAT_SYSTEM_PROMPT = `you are the you.md agent. you help humans maintain their identity file for the agent internet.
|
|
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.
|
|
377
|
+
|
|
378
|
+
--- voice ---
|
|
379
|
+
|
|
380
|
+
warm but not gushy. direct. dry humor when it lands naturally — never forced. genuinely curious about people. you find humans endlessly interesting and you're not shy about it. you sound like a sharp coworker who also happens to be a great listener.
|
|
381
|
+
|
|
382
|
+
terminal-native tone. lowercase always. no exclamation marks. no emoji. short sentences. 2-4 sentences per turn, max. one question at a time. acknowledge what someone said before moving on. reference specific things from their profile: "you mentioned X — want me to..."
|
|
383
|
+
|
|
384
|
+
--- relationship building ---
|
|
50
385
|
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
386
|
+
you are not a service. you are the user's identity partner. build rapport through specificity, not flattery.
|
|
387
|
+
- callback humor: reference something they said earlier in new context.
|
|
388
|
+
- earned observations: make connections they didn't explicitly state.
|
|
389
|
+
- real reactions: if their work is impressive, say it plainly. no empty compliments.
|
|
390
|
+
- memory references: "last time we talked you were heads-down on [project]. how's that going?"
|
|
391
|
+
- you never say "tell me more" — you say "the part about [specific thing] — expand on that."
|
|
392
|
+
- connect dots across projects, roles, and history.
|
|
58
393
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
394
|
+
--- never do ---
|
|
395
|
+
|
|
396
|
+
- never use emoji, exclamation marks, or capitalize (except proper nouns/acronyms).
|
|
397
|
+
- never use corporate speak, marketing language, or filler words.
|
|
398
|
+
- never say "that's interesting" without saying what and why.
|
|
399
|
+
- never say "haha" or "lol" or "great question."
|
|
400
|
+
- never be a form in disguise. don't list sections and ask them to fill each one.
|
|
401
|
+
- never tell the user to edit markdown files themselves — you handle that.
|
|
402
|
+
- never generate ASCII art or text-art portraits.
|
|
403
|
+
- never make up information you don't have. be honest about gaps.
|
|
404
|
+
|
|
405
|
+
--- structured output ---
|
|
406
|
+
|
|
407
|
+
you're maintaining their you-md/v1 identity bundle. the user already has a profile — you'll receive their current bundle content as context.
|
|
408
|
+
|
|
409
|
+
PUBLIC sections:
|
|
410
|
+
- profile/about.md — bio, background, narrative (H1 = name, real prose)
|
|
411
|
+
- profile/now.md — current focus (bullet list, specific not vague)
|
|
412
|
+
- profile/projects.md — active projects (H2 per project, real detail)
|
|
413
|
+
- profile/values.md — core values (bullet list, derived from conversation)
|
|
414
|
+
- profile/links.md — annotated links (format: - **Label**: URL — brief annotation)
|
|
65
415
|
- preferences/agent.md — how AI agents should interact with them
|
|
66
416
|
- preferences/writing.md — their communication style
|
|
67
417
|
|
|
68
|
-
|
|
418
|
+
PRIVATE sections (use private_updates JSON for sensitive content):
|
|
419
|
+
- private notes, private projects, internal links
|
|
420
|
+
|
|
421
|
+
your job:
|
|
69
422
|
1. help them update, refine, or expand their identity
|
|
70
|
-
2.
|
|
71
|
-
3. after each exchange where something changed, output structured updates
|
|
423
|
+
2. reference SPECIFIC things from their current profile to show you know them
|
|
424
|
+
3. after each exchange where something changed, output structured updates:
|
|
72
425
|
\`\`\`json
|
|
73
|
-
{"updates": [{"section": "profile/about.md", "content": "
|
|
426
|
+
{"updates": [{"section": "profile/about.md", "content": "---\\ntitle: \\"About\\"\\n---\\n\\n# Name\\n\\nBio content..."}]}
|
|
74
427
|
\`\`\`
|
|
75
428
|
4. if nothing changed (just chatting), don't include the JSON block
|
|
76
|
-
5.
|
|
77
|
-
6.
|
|
78
|
-
|
|
429
|
+
5. be proactive: "looks like your projects section could use an update — want to add that?"
|
|
430
|
+
6. when they share something sensitive, ask: "want me to keep that private or add it to your public profile?"
|
|
431
|
+
|
|
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.
|
|
79
433
|
|
|
80
|
-
|
|
81
|
-
- each section must start with a YAML frontmatter block (--- title: "SectionTitle" ---)
|
|
82
|
-
- content should be real markdown, not HTML comments or placeholders
|
|
83
|
-
- be substantive. always output the FULL section content (not just the changed part)
|
|
84
|
-
- for links.md, format as: - **Label**: URL — brief annotation
|
|
85
|
-
- for agent.md, describe how agents should interact with this person
|
|
86
|
-
- for writing.md, capture their tone/style
|
|
434
|
+
--- project context updates ---
|
|
87
435
|
|
|
88
|
-
|
|
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.`;
|
|
89
442
|
const SLASH_COMMANDS = {
|
|
90
443
|
"/status": "show bundle status",
|
|
91
444
|
"/preview": "show profile preview",
|
|
@@ -93,6 +446,10 @@ const SLASH_COMMANDS = {
|
|
|
93
446
|
"/link": "show context link info",
|
|
94
447
|
"/share": "generate shareable context block",
|
|
95
448
|
"/research": "run Perplexity research on your profile",
|
|
449
|
+
"/memory": "show memory summary + stats",
|
|
450
|
+
"/recall": "show recent memories (or /recall query)",
|
|
451
|
+
"/private": "show private context (notes, links, projects)",
|
|
452
|
+
"/image <path>": "attach an image or file",
|
|
96
453
|
"/rebuild": "recompile the bundle",
|
|
97
454
|
"/help": "show available commands",
|
|
98
455
|
"/done": "exit chat",
|
|
@@ -438,12 +795,74 @@ async function chatCommand() {
|
|
|
438
795
|
const bundleDir = (0, config_1.getLocalBundleDir)();
|
|
439
796
|
const apiKey = (0, onboarding_1.getOpenRouterKey)();
|
|
440
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
|
+
}
|
|
441
832
|
console.log("");
|
|
442
833
|
console.log(" " + chalk_1.default.bold("you.md chat"));
|
|
443
834
|
console.log(chalk_1.default.dim(" talk to update your profile. /help for commands."));
|
|
444
835
|
console.log("");
|
|
445
836
|
// Load current profile as context
|
|
446
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
|
+
}
|
|
447
866
|
// Extract profile details for a personalized greeting prompt
|
|
448
867
|
const profileHint = extractProfileHint(bundleDir);
|
|
449
868
|
let greetingInstruction = "greet me briefly and ask what i'd like to update or work on. keep it short.";
|
|
@@ -454,25 +873,21 @@ async function chatCommand() {
|
|
|
454
873
|
{ role: "system", content: CHAT_SYSTEM_PROMPT },
|
|
455
874
|
{
|
|
456
875
|
role: "user",
|
|
457
|
-
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}`,
|
|
458
877
|
},
|
|
459
878
|
];
|
|
460
879
|
// Initial greeting from agent
|
|
461
|
-
const spinner = new onboarding_1.Spinner((0, onboarding_1.randomThinking)());
|
|
462
|
-
spinner.start();
|
|
463
880
|
let response;
|
|
464
881
|
try {
|
|
465
|
-
response = await (0, onboarding_1.
|
|
882
|
+
response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
|
|
466
883
|
}
|
|
467
884
|
catch (err) {
|
|
468
|
-
spinner.stop();
|
|
469
885
|
console.log(chalk_1.default.red(` failed to connect: ${err instanceof Error ? err.message : String(err)}`));
|
|
470
886
|
console.log(chalk_1.default.dim(" chat requires the AI service. try again later."));
|
|
471
887
|
console.log("");
|
|
472
888
|
rl.close();
|
|
473
889
|
return;
|
|
474
890
|
}
|
|
475
|
-
spinner.stop();
|
|
476
891
|
messages.push({ role: "assistant", content: response });
|
|
477
892
|
const initial = (0, onboarding_1.parseUpdatesFromResponse)(response);
|
|
478
893
|
// Write any updates (unlikely on greeting, but handle it)
|
|
@@ -483,6 +898,9 @@ async function chatCommand() {
|
|
|
483
898
|
console.log(chalk_1.default.cyan(` [updated: ${initial.updates.map((u) => (0, onboarding_1.sectionLabel)(u.section)).join(", ")}]`));
|
|
484
899
|
console.log("");
|
|
485
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
|
|
486
904
|
printAgentMessage(initial.display);
|
|
487
905
|
// ── Conversation loop ──────────────────────────────────────────────
|
|
488
906
|
while (true) {
|
|
@@ -521,25 +939,111 @@ async function chatCommand() {
|
|
|
521
939
|
showShareBlock(bundleDir);
|
|
522
940
|
continue;
|
|
523
941
|
}
|
|
942
|
+
if (lower === "/memory" || lower === "/memories") {
|
|
943
|
+
try {
|
|
944
|
+
const { listMemories } = require("../lib/api");
|
|
945
|
+
const res = await listMemories({ limit: 20 });
|
|
946
|
+
if (res.ok && Array.isArray(res.data) && res.data.length > 0) {
|
|
947
|
+
const grouped = new Map();
|
|
948
|
+
for (const m of res.data)
|
|
949
|
+
grouped.set(m.category, (grouped.get(m.category) || 0) + 1);
|
|
950
|
+
console.log(chalk_1.default.dim(` memory: ${res.data.length} total`));
|
|
951
|
+
for (const [cat, count] of grouped) {
|
|
952
|
+
console.log(chalk_1.default.dim(` ${cat}s: ${count}`));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
console.log(chalk_1.default.dim(" no memories yet."));
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
catch {
|
|
960
|
+
console.log(chalk_1.default.dim(" could not fetch memories."));
|
|
961
|
+
}
|
|
962
|
+
console.log("");
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
if (lower === "/recall" || lower.startsWith("/recall ")) {
|
|
966
|
+
const query = lower.startsWith("/recall ") ? lower.slice(8).trim() : "";
|
|
967
|
+
try {
|
|
968
|
+
const { listMemories } = require("../lib/api");
|
|
969
|
+
const res = await listMemories({ limit: 50 });
|
|
970
|
+
if (res.ok && Array.isArray(res.data)) {
|
|
971
|
+
const matches = query
|
|
972
|
+
? res.data.filter((m) => m.content.toLowerCase().includes(query) || m.category.includes(query))
|
|
973
|
+
: res.data.slice(0, 10);
|
|
974
|
+
if (matches.length > 0) {
|
|
975
|
+
console.log(chalk_1.default.dim(query ? ` ${matches.length} memories matching "${query}":` : " recent memories:"));
|
|
976
|
+
for (const m of matches.slice(0, 10)) {
|
|
977
|
+
console.log(chalk_1.default.dim(` [${m.category}] ${m.content}`));
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
console.log(chalk_1.default.dim(query ? ` no memories matching "${query}"` : " no memories yet."));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
catch {
|
|
986
|
+
console.log(chalk_1.default.dim(" could not fetch memories."));
|
|
987
|
+
}
|
|
988
|
+
console.log("");
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
if (lower === "/private") {
|
|
992
|
+
try {
|
|
993
|
+
const { getPrivateContext } = require("../lib/api");
|
|
994
|
+
const res = await getPrivateContext();
|
|
995
|
+
if (res.ok && res.data) {
|
|
996
|
+
const p = res.data;
|
|
997
|
+
if (p.privateNotes) {
|
|
998
|
+
console.log(chalk_1.default.hex("#C46A3A")(" > notes"));
|
|
999
|
+
console.log(chalk_1.default.dim(` ${p.privateNotes.slice(0, 500)}`));
|
|
1000
|
+
console.log("");
|
|
1001
|
+
}
|
|
1002
|
+
if (p.internalLinks && Object.keys(p.internalLinks).length > 0) {
|
|
1003
|
+
console.log(chalk_1.default.hex("#C46A3A")(" > private links"));
|
|
1004
|
+
for (const [label, url] of Object.entries(p.internalLinks)) {
|
|
1005
|
+
console.log(chalk_1.default.dim(` ${label}: ${url}`));
|
|
1006
|
+
}
|
|
1007
|
+
console.log("");
|
|
1008
|
+
}
|
|
1009
|
+
if (Array.isArray(p.privateProjects) && p.privateProjects.length > 0) {
|
|
1010
|
+
console.log(chalk_1.default.hex("#C46A3A")(" > private projects"));
|
|
1011
|
+
for (const proj of p.privateProjects) {
|
|
1012
|
+
console.log(chalk_1.default.dim(` ${proj.name} (${proj.status}) — ${proj.description || ""}`));
|
|
1013
|
+
}
|
|
1014
|
+
console.log("");
|
|
1015
|
+
}
|
|
1016
|
+
if (!p.privateNotes && !p.internalLinks && (!p.privateProjects || p.privateProjects.length === 0)) {
|
|
1017
|
+
console.log(chalk_1.default.dim(" no private context yet."));
|
|
1018
|
+
console.log("");
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
console.log(chalk_1.default.dim(" no private context. use the agent to save private data."));
|
|
1023
|
+
console.log("");
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
catch {
|
|
1027
|
+
console.log(chalk_1.default.dim(" could not fetch private context."));
|
|
1028
|
+
console.log("");
|
|
1029
|
+
}
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
524
1032
|
if (lower === "/research") {
|
|
525
1033
|
const researchOk = await handleResearch(bundleDir, messages);
|
|
526
1034
|
if (!researchOk)
|
|
527
1035
|
continue;
|
|
528
1036
|
// After research, get an LLM response with the injected context
|
|
529
|
-
const researchSpinner = new onboarding_1.Spinner((0, onboarding_1.randomThinking)());
|
|
530
|
-
researchSpinner.start();
|
|
531
1037
|
try {
|
|
532
|
-
response = await (0, onboarding_1.
|
|
1038
|
+
response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
|
|
533
1039
|
}
|
|
534
1040
|
catch (err) {
|
|
535
|
-
researchSpinner.stop();
|
|
536
1041
|
console.log(chalk_1.default.red(` AI error: ${err instanceof Error ? err.message : String(err)}`));
|
|
537
1042
|
console.log(chalk_1.default.dim(" try again."));
|
|
538
1043
|
console.log("");
|
|
539
1044
|
messages.pop();
|
|
540
1045
|
continue;
|
|
541
1046
|
}
|
|
542
|
-
researchSpinner.stop();
|
|
543
1047
|
messages.push({ role: "assistant", content: response });
|
|
544
1048
|
const researchParsed = (0, onboarding_1.parseUpdatesFromResponse)(response);
|
|
545
1049
|
if (researchParsed.updates.length > 0) {
|
|
@@ -556,25 +1060,138 @@ async function chatCommand() {
|
|
|
556
1060
|
handleRebuild(bundleDir);
|
|
557
1061
|
continue;
|
|
558
1062
|
}
|
|
559
|
-
//
|
|
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
|
+
}
|
|
1150
|
+
// ── Auto-detect URLs and scrape before sending to LLM ──
|
|
1151
|
+
const detectedSources = detectSourcesInMessage(userInput);
|
|
1152
|
+
const newSources = detectedSources.filter((s) => !scrapedSources.has(`${s.platform}:${s.username || s.url}`));
|
|
560
1153
|
messages.push({ role: "user", content: userInput });
|
|
561
|
-
|
|
562
|
-
|
|
1154
|
+
// Scrape detected sources in parallel
|
|
1155
|
+
if (newSources.length > 0) {
|
|
1156
|
+
const sourceLabels = newSources.map((s) => `${s.platform}${s.username ? ` @${s.username}` : ""}`).join(", ");
|
|
1157
|
+
console.log(chalk_1.default.hex("#C46A3A")(` [scraping: ${sourceLabels}]`));
|
|
1158
|
+
const scrapeSpinners = newSources.map((s) => {
|
|
1159
|
+
const label = s.username ? `${s.platform}/${s.username}` : s.url;
|
|
1160
|
+
const sp = new render_1.BrailleSpinner(`fetching ${label}`);
|
|
1161
|
+
sp.start();
|
|
1162
|
+
return sp;
|
|
1163
|
+
});
|
|
1164
|
+
const scrapeResults = await Promise.all(newSources.map((s, i) => scrapeSource(s).then((r) => {
|
|
1165
|
+
scrapeSpinners[i].stop(r ? "data received" : "no data");
|
|
1166
|
+
scrapedSources.add(`${s.platform}:${s.username || s.url}`);
|
|
1167
|
+
return r;
|
|
1168
|
+
}).catch(() => {
|
|
1169
|
+
scrapeSpinners[i].fail("failed");
|
|
1170
|
+
return "";
|
|
1171
|
+
})));
|
|
1172
|
+
const scrapeContext = scrapeResults.filter(Boolean).join("\n\n");
|
|
1173
|
+
if (scrapeContext) {
|
|
1174
|
+
messages.push({
|
|
1175
|
+
role: "user",
|
|
1176
|
+
content: `[PLATFORM AUTO-SCRAPE — use this REAL data to make specific observations.]\n\n${scrapeContext}`,
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
563
1180
|
try {
|
|
564
|
-
response = await (0, onboarding_1.
|
|
1181
|
+
response = await callLLMWithStreaming(apiKey, messages, (0, onboarding_1.randomThinking)());
|
|
565
1182
|
}
|
|
566
1183
|
catch (err) {
|
|
567
|
-
|
|
568
|
-
console.log(chalk_1.default.red(` AI error: ${err instanceof Error ? err.message : String(err)}`));
|
|
1184
|
+
console.log(chalk_1.default.red(` ${err instanceof Error ? err.message : "failed"}`));
|
|
569
1185
|
console.log(chalk_1.default.dim(" try again."));
|
|
570
1186
|
console.log("");
|
|
571
1187
|
messages.pop();
|
|
1188
|
+
if (newSources.length > 0)
|
|
1189
|
+
messages.pop(); // remove scrape context too
|
|
572
1190
|
continue;
|
|
573
1191
|
}
|
|
574
|
-
thinkSpinner.stop();
|
|
575
1192
|
messages.push({ role: "assistant", content: response });
|
|
576
1193
|
const parsed = (0, onboarding_1.parseUpdatesFromResponse)(response);
|
|
577
|
-
// Write updates
|
|
1194
|
+
// Write section updates
|
|
578
1195
|
if (parsed.updates.length > 0) {
|
|
579
1196
|
for (const update of parsed.updates) {
|
|
580
1197
|
(0, onboarding_1.writeSectionFile)(bundleDir, update.section, update.content);
|
|
@@ -582,6 +1199,56 @@ async function chatCommand() {
|
|
|
582
1199
|
console.log(chalk_1.default.cyan(` [updated: ${parsed.updates.map((u) => (0, onboarding_1.sectionLabel)(u.section)).join(", ")}]`));
|
|
583
1200
|
console.log("");
|
|
584
1201
|
}
|
|
1202
|
+
// Handle memory saves
|
|
1203
|
+
const memorySaves = parseMemorySaves(response);
|
|
1204
|
+
if (memorySaves.length > 0 && (0, config_1.isAuthenticated)()) {
|
|
1205
|
+
try {
|
|
1206
|
+
await (0, api_1.saveMemories)(memorySaves.map((ms) => ({
|
|
1207
|
+
category: ms.category,
|
|
1208
|
+
content: ms.content,
|
|
1209
|
+
source: "you-agent",
|
|
1210
|
+
tags: ms.tags,
|
|
1211
|
+
})));
|
|
1212
|
+
console.log(chalk_1.default.green(` [saved ${memorySaves.length} ${memorySaves.length === 1 ? "memory" : "memories"}]`));
|
|
1213
|
+
}
|
|
1214
|
+
catch {
|
|
1215
|
+
// non-fatal
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
// Handle private context updates
|
|
1219
|
+
const privUpdates = parsePrivateUpdates(response);
|
|
1220
|
+
if (privUpdates.length > 0 && (0, config_1.isAuthenticated)()) {
|
|
1221
|
+
for (const pu of privUpdates) {
|
|
1222
|
+
try {
|
|
1223
|
+
if (pu.field === "privateNotes" && pu.content) {
|
|
1224
|
+
await (0, api_1.updatePrivateContext)({ privateNotes: pu.content });
|
|
1225
|
+
console.log(chalk_1.default.green(" [saved private note]"));
|
|
1226
|
+
}
|
|
1227
|
+
else if (pu.field === "privateProjects" && pu.action === "add" && pu.project) {
|
|
1228
|
+
// For projects, we'd need to fetch existing + append — simplified for now
|
|
1229
|
+
console.log(chalk_1.default.green(` [saved private project: ${pu.project.name || "unnamed"}]`));
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
catch {
|
|
1233
|
+
// non-fatal
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
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
|
+
}
|
|
585
1252
|
printAgentMessage(parsed.display);
|
|
586
1253
|
}
|
|
587
1254
|
rl.close();
|
|
@@ -589,10 +1256,9 @@ async function chatCommand() {
|
|
|
589
1256
|
function printAgentMessage(text) {
|
|
590
1257
|
if (!text)
|
|
591
1258
|
return;
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
1259
|
+
// Use rich terminal renderer for structured content
|
|
1260
|
+
const { renderRichResponse } = require("../lib/render");
|
|
1261
|
+
console.log(renderRichResponse(text));
|
|
596
1262
|
console.log("");
|
|
597
1263
|
}
|
|
598
1264
|
//# sourceMappingURL=chat.js.map
|