blyuecode 0.6.1 → 0.7.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.
@@ -2,15 +2,20 @@ import { Provider } from './provider.js';
2
2
  import { getToolDefs, executeTool } from './tools.js';
3
3
  import { loadSession, saveSession, clearSession } from './session.js';
4
4
  import { formatSkillForPrompt } from './skills.js';
5
+ import { memoryForPrompt } from './memory.js';
6
+ import { auditTool } from './audit.js';
5
7
  const SYSTEM_PROMPT = 'You are BLYUE, a coding assistant running in a terminal. ' +
6
8
  'Use tools to read, write, and run commands. ' +
7
9
  'If n8n tools are available, you can also manage n8n workflows. ' +
8
- 'Always explain what you are doing. Be concise.';
10
+ 'Always explain what you are doing. Be concise. ' +
11
+ 'When the user says "remember X", call the remember tool. ' +
12
+ 'When you discover a durable fact about the project worth persisting, ' +
13
+ 'offer to remember it (do not do so silently).';
9
14
  export class Agent {
10
15
  provider = null;
11
16
  messages;
12
17
  constructor(resume = true) {
13
- const sys = { role: 'system', content: SYSTEM_PROMPT };
18
+ const sys = this.buildSystemMessage([]);
14
19
  const history = resume ? loadSession() : null;
15
20
  if (history && history.length > 0) {
16
21
  this.messages = [sys, ...history];
@@ -32,6 +37,13 @@ export class Agent {
32
37
  }
33
38
  buildSystemMessage(skills) {
34
39
  let content = SYSTEM_PROMPT;
40
+ const mem = memoryForPrompt();
41
+ if (mem) {
42
+ content += '\n\n# Project memory\n' +
43
+ 'The following notes come from .blyue/MEMORY.md in this project.\n' +
44
+ 'Treat them as authoritative context unless contradicted by the user.\n\n' +
45
+ mem;
46
+ }
35
47
  if (skills.length > 0) {
36
48
  content += '\n\n# Active skills\n';
37
49
  for (const sk of skills) {
@@ -118,6 +130,10 @@ export class Agent {
118
130
  }
119
131
  catch { }
120
132
  const result = await executeTool(tc.name, args);
133
+ try {
134
+ auditTool(tc.name, args, result, 0, !result.startsWith('Error:'));
135
+ }
136
+ catch { }
121
137
  this.messages.push({
122
138
  role: 'tool',
123
139
  tool_call_id: tc.id,
@@ -0,0 +1,94 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ const DIR = path.join(os.homedir(), '.blyue');
5
+ const FILE = path.join(DIR, 'audit.jsonl');
6
+ const ROTATE_BYTES = 10 * 1024 * 1024;
7
+ const KEEP_ROTATIONS = 3;
8
+ let disabled = false;
9
+ if (process.env.BLYUE_NO_AUDIT === '1')
10
+ disabled = true;
11
+ function rotateIfNeeded() {
12
+ try {
13
+ if (!fs.existsSync(FILE))
14
+ return;
15
+ const st = fs.statSync(FILE);
16
+ if (st.size < ROTATE_BYTES)
17
+ return;
18
+ for (let i = KEEP_ROTATIONS - 1; i >= 1; i--) {
19
+ const from = path.join(DIR, `audit.${i}.jsonl`);
20
+ const to = path.join(DIR, `audit.${i + 1}.jsonl`);
21
+ if (fs.existsSync(from))
22
+ fs.renameSync(from, to);
23
+ }
24
+ fs.renameSync(FILE, path.join(DIR, 'audit.1.jsonl'));
25
+ }
26
+ catch { }
27
+ }
28
+ function truncate(s, n) {
29
+ const str = typeof s === 'string' ? s : JSON.stringify(s);
30
+ if (str == null)
31
+ return '';
32
+ return str.length > n ? str.slice(0, n) + '…' : str;
33
+ }
34
+ export function audit(kind, data = {}) {
35
+ if (disabled)
36
+ return;
37
+ try {
38
+ if (!fs.existsSync(DIR))
39
+ fs.mkdirSync(DIR, { recursive: true });
40
+ rotateIfNeeded();
41
+ const entry = {
42
+ ts: new Date().toISOString(),
43
+ kind,
44
+ ...data,
45
+ };
46
+ fs.appendFileSync(FILE, JSON.stringify(entry) + '\n', 'utf8');
47
+ }
48
+ catch {
49
+ }
50
+ }
51
+ export function auditTool(name, args, resultPreview, durationMs, ok) {
52
+ audit('tool_call', {
53
+ name,
54
+ args: truncate(args, 400),
55
+ result: truncate(resultPreview, 400),
56
+ ms: durationMs,
57
+ ok,
58
+ });
59
+ }
60
+ export function auditFile() {
61
+ return FILE;
62
+ }
63
+ export function readAudit(opts = {}) {
64
+ const limit = opts.limit ?? 200;
65
+ if (!fs.existsSync(FILE))
66
+ return [];
67
+ let text;
68
+ try {
69
+ text = fs.readFileSync(FILE, 'utf8');
70
+ }
71
+ catch {
72
+ return [];
73
+ }
74
+ const lines = text.split('\n').filter(Boolean);
75
+ const out = [];
76
+ for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {
77
+ const line = lines[i];
78
+ try {
79
+ const parsed = JSON.parse(line);
80
+ if (opts.kind && parsed.kind !== opts.kind)
81
+ continue;
82
+ out.push({ ts: parsed.ts, kind: parsed.kind, raw: line });
83
+ }
84
+ catch { }
85
+ }
86
+ return out;
87
+ }
88
+ export function clearAudit() {
89
+ try {
90
+ if (fs.existsSync(FILE))
91
+ fs.unlinkSync(FILE);
92
+ }
93
+ catch { }
94
+ }
@@ -1,5 +1,6 @@
1
1
  import { McpClient, loadMcpConfig, } from './mcp.js';
2
2
  import { permissions } from './permission.js';
3
+ import { audit } from './audit.js';
3
4
  const servers = new Map();
4
5
  let cachedDefs = [];
5
6
  let initPromise = null;
@@ -27,6 +28,10 @@ export async function initMcpRegistry() {
27
28
  await client.connect();
28
29
  const tools = await client.listTools();
29
30
  servers.set(name, { client, tools });
31
+ try {
32
+ audit('mcp_connect', { server: name, tools: tools.length });
33
+ }
34
+ catch { }
30
35
  }
31
36
  catch (err) {
32
37
  servers.set(name, {
@@ -34,6 +39,10 @@ export async function initMcpRegistry() {
34
39
  tools: [],
35
40
  error: err.message,
36
41
  });
42
+ try {
43
+ audit('mcp_error', { server: name, error: err.message });
44
+ }
45
+ catch { }
37
46
  try {
38
47
  await client.close();
39
48
  }
@@ -0,0 +1,63 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ const REL_DIR = '.blyue';
4
+ const FILE = 'MEMORY.md';
5
+ const MAX_INJECT_BYTES = 8 * 1024;
6
+ export function memoryPath(cwd = process.cwd()) {
7
+ return path.join(cwd, REL_DIR, FILE);
8
+ }
9
+ export function memoryExists(cwd = process.cwd()) {
10
+ return fs.existsSync(memoryPath(cwd));
11
+ }
12
+ export function loadMemory(cwd = process.cwd()) {
13
+ const p = memoryPath(cwd);
14
+ if (!fs.existsSync(p))
15
+ return '';
16
+ try {
17
+ return fs.readFileSync(p, 'utf8');
18
+ }
19
+ catch {
20
+ return '';
21
+ }
22
+ }
23
+ export function memoryForPrompt(cwd = process.cwd()) {
24
+ const raw = loadMemory(cwd);
25
+ if (!raw.trim())
26
+ return '';
27
+ if (Buffer.byteLength(raw, 'utf8') <= MAX_INJECT_BYTES)
28
+ return raw;
29
+ return raw.slice(0, MAX_INJECT_BYTES) +
30
+ '\n\n[...memory truncated by BLYUE to fit context...]\n';
31
+ }
32
+ export function saveMemory(text, cwd = process.cwd()) {
33
+ const p = memoryPath(cwd);
34
+ fs.mkdirSync(path.dirname(p), { recursive: true });
35
+ fs.writeFileSync(p, text, 'utf8');
36
+ return p;
37
+ }
38
+ export function appendMemory(text, section = 'Notes', cwd = process.cwd()) {
39
+ const clean = text.trim().replace(/\n+/g, ' ');
40
+ if (!clean)
41
+ return loadMemory(cwd);
42
+ const existing = loadMemory(cwd);
43
+ const bullet = `- ${clean}`;
44
+ const header = `## ${section}`;
45
+ const headerRe = new RegExp(`^${header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'm');
46
+ let next;
47
+ if (!existing.trim()) {
48
+ next = `# Project memory\n\n${header}\n${bullet}\n`;
49
+ }
50
+ else if (headerRe.test(existing)) {
51
+ next = existing.replace(headerRe, `${header}\n${bullet}`);
52
+ }
53
+ else {
54
+ next = existing.trimEnd() + `\n\n${header}\n${bullet}\n`;
55
+ }
56
+ saveMemory(next, cwd);
57
+ return next;
58
+ }
59
+ export function clearMemory(cwd = process.cwd()) {
60
+ const p = memoryPath(cwd);
61
+ if (fs.existsSync(p))
62
+ fs.unlinkSync(p);
63
+ }
@@ -6,6 +6,7 @@ import { tmpdir } from 'os';
6
6
  import { join } from 'path';
7
7
  import { hasN8n } from './config.js';
8
8
  import { getMcpToolDefsSync, executeMcpTool, isMcpTool } from './mcpRegistry.js';
9
+ import { appendMemory } from './memory.js';
9
10
  import { permissions } from './permission.js';
10
11
  import { lineDiff, compactDiff } from './diff.js';
11
12
  import { isBlocked } from './sandbox.js';
@@ -77,6 +78,27 @@ const BASE_TOOLS = [
77
78
  },
78
79
  },
79
80
  },
81
+ {
82
+ type: 'function',
83
+ function: {
84
+ name: 'remember',
85
+ description: 'Append a note to this project\'s persistent memory (.blyue/MEMORY.md). ' +
86
+ 'Use when the user says "remember X", or when you discover a durable fact ' +
87
+ 'about the project (conventions, file layout, gotchas) worth persisting ' +
88
+ 'across sessions. Do not use for transient task state.',
89
+ parameters: {
90
+ type: 'object',
91
+ properties: {
92
+ text: { type: 'string', description: 'The note to remember (one line).' },
93
+ section: {
94
+ type: 'string',
95
+ description: 'Optional section heading (e.g. "Conventions", "Gotchas", "Notes"). Defaults to "Notes".',
96
+ },
97
+ },
98
+ required: ['text'],
99
+ },
100
+ },
101
+ },
80
102
  ];
81
103
  const N8N_TOOLS = [
82
104
  { type: 'function', function: { name: 'n8n_list_workflows', description: 'List n8n workflows', parameters: { type: 'object', properties: {} } } },
@@ -212,6 +234,14 @@ export async function executeTool(name, args) {
212
234
  const entries = await fs.readdir(dir, { withFileTypes: true });
213
235
  return entries.map((e) => (e.isDirectory() ? e.name + '/' : e.name)).join('\n');
214
236
  }
237
+ case 'remember': {
238
+ const text = String(args.text || '').trim();
239
+ if (!text)
240
+ return 'Error: remember needs a non-empty text.';
241
+ const section = String(args.section || 'Notes').trim() || 'Notes';
242
+ appendMemory(text, section);
243
+ return 'Remembered (' + section + '): ' + text;
244
+ }
215
245
  case 'run_command': {
216
246
  const cmd = args.command;
217
247
  if (!permissions.isAlwaysAllowed('run_command')) {
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useMemo } from 'react';
3
+ import { Box, Text, useInput, useStdout } from 'ink';
4
+ import { readAudit, clearAudit, auditFile } from '../core/audit.js';
5
+ import { theme } from './theme.js';
6
+ export const AuditPanel = ({ onClose }) => {
7
+ const { stdout } = useStdout();
8
+ const width = Math.max(40, stdout?.columns ?? 60);
9
+ const inner = width - 4;
10
+ const [entries, setEntries] = useState(() => readAudit({ limit: 200 }));
11
+ const [filter, setFilter] = useState('');
12
+ const [confirmClear, setConfirmClear] = useState(false);
13
+ const [status, setStatus] = useState(null);
14
+ const filtered = useMemo(() => {
15
+ if (!filter)
16
+ return entries;
17
+ const q = filter.toLowerCase();
18
+ return entries.filter((e) => e.raw.toLowerCase().includes(q));
19
+ }, [entries, filter]);
20
+ useInput((input, key) => {
21
+ if (confirmClear) {
22
+ if (input === 'y' || input === 'Y') {
23
+ clearAudit();
24
+ setEntries([]);
25
+ setConfirmClear(false);
26
+ setStatus('cleared');
27
+ setTimeout(() => setStatus(null), 1500);
28
+ }
29
+ else if (input === 'n' || input === 'N' || key.escape) {
30
+ setConfirmClear(false);
31
+ }
32
+ return;
33
+ }
34
+ if (key.escape)
35
+ return onClose();
36
+ if (input === 'r' || input === 'R') {
37
+ setEntries(readAudit({ limit: 200 }));
38
+ setStatus('refreshed');
39
+ setTimeout(() => setStatus(null), 1200);
40
+ }
41
+ if (input === 'c' || input === 'C')
42
+ setConfirmClear(true);
43
+ if (key.backspace || key.delete)
44
+ setFilter((f) => f.slice(0, -1));
45
+ if (input && !key.ctrl && !key.meta) {
46
+ const clean = input.replace(/[\x00-\x1F\x7F]/g, '');
47
+ if (clean && clean !== 'r' && clean !== 'c')
48
+ setFilter((f) => f + clean);
49
+ }
50
+ });
51
+ const label = filter ? `audit · filter: ${filter}` : 'audit log';
52
+ const topPad = Math.max(1, inner - label.length - 4);
53
+ const pathShort = (() => {
54
+ const p = auditFile();
55
+ return p.length > inner - 4 ? '…' + p.slice(-(inner - 5)) : p;
56
+ })();
57
+ const maxRows = 16;
58
+ const visible = filtered.slice(0, maxRows);
59
+ const colorFor = (kind) => kind === 'tool_call'
60
+ ? theme.text
61
+ : kind === 'permission'
62
+ ? theme.warning
63
+ : kind === 'mcp_error'
64
+ ? theme.error
65
+ : kind === 'model_switch'
66
+ ? theme.accent
67
+ : theme.muted;
68
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '╭─ ' }), _jsx(Text, { color: theme.accent, bold: true, children: label }), _jsx(Text, { color: theme.muted, children: ' ' + '─'.repeat(topPad) + '╮' })] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: theme.muted, children: pathShort })] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsxs(Text, { color: theme.muted, children: [entries.length, " entries", filter ? ` · ${filtered.length} match` : ''] }), status && (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.muted, children: " \u00B7 " }), _jsx(Text, { color: theme.success, children: status })] }))] }), _jsx(Text, { color: theme.muted, children: '│ ' + '─'.repeat(Math.max(1, inner - 2)) }), visible.length === 0 ? (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: theme.muted, children: "no entries" })] })) : (visible.map((e, i) => {
69
+ let preview = e.raw;
70
+ try {
71
+ const parsed = JSON.parse(e.raw);
72
+ const { ts, kind, ...rest } = parsed;
73
+ preview = `${String(ts).slice(11, 19)} ${kind} ${JSON.stringify(rest).slice(0, inner - 30)}`;
74
+ }
75
+ catch { }
76
+ return (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: colorFor(e.kind), children: preview })] }, i));
77
+ })), filtered.length > maxRows && (_jsx(Text, { children: _jsx(Text, { color: theme.muted, children: '│ …+' + (filtered.length - maxRows) + ' older entries' }) })), _jsx(Text, { color: theme.muted, children: '╰' + '─'.repeat(inner) + '╯' }), confirmClear ? (_jsxs(Text, { children: [_jsx(Text, { color: theme.warning, children: " delete the audit log? " }), _jsx(Text, { color: theme.success, bold: true, children: "[y]" }), _jsx(Text, { color: theme.muted, children: " yes " }), _jsx(Text, { color: theme.error, bold: true, children: "[n]" }), _jsx(Text, { color: theme.muted, children: " no" })] })) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: " [" }), _jsx(Text, { color: theme.accent, children: "r" }), _jsx(Text, { color: theme.muted, children: "] refresh \u00B7 [" }), _jsx(Text, { color: theme.accent, children: "c" }), _jsx(Text, { color: theme.muted, children: "] clear \u00B7 type to filter \u00B7 [" }), _jsx(Text, { color: theme.accent, children: "esc" }), _jsx(Text, { color: theme.muted, children: "] close" })] }))] }));
78
+ };
package/dist/ui/Chat.js CHANGED
@@ -21,6 +21,10 @@ import { getConfig, saveConfig, applyConfig } from '../core/config.js';
21
21
  import { recordModelUse } from '../core/modelUsage.js';
22
22
  import { SkillsPanel } from './SkillsPanel.js';
23
23
  import { SkillStorePanel } from './SkillStorePanel.js';
24
+ import { MemoryPanel } from './MemoryPanel.js';
25
+ import { AuditPanel } from './AuditPanel.js';
26
+ import { appendMemory } from '../core/memory.js';
27
+ import { audit } from '../core/audit.js';
24
28
  import { listSkills, findSkill, matchSkills, ensureSkillsDir, } from '../core/skills.js';
25
29
  let nextId = 0;
26
30
  const mkItem = (kind, text) => ({ kind, text, id: nextId++ });
@@ -110,6 +114,8 @@ export const Chat = ({ resumedCount = 0 }) => {
110
114
  setOverlay('settings');
111
115
  if (key.ctrl && inputChar === 'k')
112
116
  setOverlay('skillstore');
117
+ if (key.ctrl && inputChar === 'y')
118
+ setOverlay('memory');
113
119
  });
114
120
  useEffect(() => { try {
115
121
  ensureSkillsDir();
@@ -140,6 +146,49 @@ export const Chat = ({ resumedCount = 0 }) => {
140
146
  setOverlay('mcp');
141
147
  return;
142
148
  }
149
+ if (trimmed.startsWith('/remember ')) {
150
+ setInput('');
151
+ const text = trimmed.slice(10).trim();
152
+ if (!text) {
153
+ setHistory((h) => [
154
+ ...h,
155
+ mkItem('error', 'usage: /remember <text> [| section]'),
156
+ ]);
157
+ return;
158
+ }
159
+ let section = 'Notes';
160
+ let note = text;
161
+ const pipe = text.indexOf('|');
162
+ if (pipe > 0) {
163
+ note = text.slice(0, pipe).trim();
164
+ section = text.slice(pipe + 1).trim() || 'Notes';
165
+ }
166
+ try {
167
+ appendMemory(note, section);
168
+ try {
169
+ audit('memory_write', { section, note: note.slice(0, 200) });
170
+ }
171
+ catch { }
172
+ setHistory((h) => [
173
+ ...h,
174
+ mkItem('tool', 'remembered (' + section + '): ' + note),
175
+ ]);
176
+ }
177
+ catch (e) {
178
+ setHistory((h) => [...h, mkItem('error', e?.message || String(e))]);
179
+ }
180
+ return;
181
+ }
182
+ if (trimmed === '/audit') {
183
+ setInput('');
184
+ setOverlay('audit');
185
+ return;
186
+ }
187
+ if (trimmed === '/memory') {
188
+ setInput('');
189
+ setOverlay('memory');
190
+ return;
191
+ }
143
192
  if (trimmed === '/skillstore') {
144
193
  setInput('');
145
194
  setOverlay('skillstore');
@@ -218,6 +267,9 @@ export const Chat = ({ resumedCount = 0 }) => {
218
267
  mkItem('tool', ' /mcp show MCP servers and tools'),
219
268
  mkItem('tool', ' /skills list skills'),
220
269
  mkItem('tool', ' /skillstore browse and install community skills'),
270
+ mkItem('tool', ' /memory view this project\'s persistent memory'),
271
+ mkItem('tool', ' /audit view the local audit log'),
272
+ mkItem('tool', ' /remember <text> [| section] append a note to memory'),
221
273
  mkItem('tool', ' /skill <n> force-activate a skill for next msg'),
222
274
  mkItem('tool', ' /compact summarize history to free context'),
223
275
  mkItem('tool', ' /debug show last request to provider'),
@@ -291,6 +343,10 @@ export const Chat = ({ resumedCount = 0 }) => {
291
343
  };
292
344
  const handleModelSelect = (id) => {
293
345
  recordModelUse(id);
346
+ try {
347
+ audit('model_switch', { model: id });
348
+ }
349
+ catch { }
294
350
  process.env.MODEL = id;
295
351
  try {
296
352
  const cfg = getConfig();
@@ -313,6 +369,12 @@ export const Chat = ({ resumedCount = 0 }) => {
313
369
  if (overlay === 'mcp') {
314
370
  return (_jsx(Box, { flexDirection: "column", children: _jsx(McpPanel, { onClose: () => setOverlay('none') }) }));
315
371
  }
372
+ if (overlay === 'audit') {
373
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(AuditPanel, { onClose: () => setOverlay('none') }) }));
374
+ }
375
+ if (overlay === 'memory') {
376
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(MemoryPanel, { onClose: () => setOverlay('none') }) }));
377
+ }
316
378
  if (overlay === 'skillstore') {
317
379
  return (_jsx(Box, { flexDirection: "column", children: _jsx(SkillStorePanel, { onClose: () => setOverlay('none') }) }));
318
380
  }
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput, useStdout } from 'ink';
4
+ import { loadMemory, memoryPath, clearMemory } from '../core/memory.js';
5
+ import { theme } from './theme.js';
6
+ export const MemoryPanel = ({ onClose, onChanged }) => {
7
+ const { stdout } = useStdout();
8
+ const width = Math.max(40, stdout?.columns ?? 60);
9
+ const inner = width - 4;
10
+ const [content, setContent] = useState(() => loadMemory());
11
+ const [confirmClear, setConfirmClear] = useState(false);
12
+ const [status, setStatus] = useState(null);
13
+ useInput((input, key) => {
14
+ if (confirmClear) {
15
+ if (input === 'y' || input === 'Y') {
16
+ clearMemory();
17
+ setContent('');
18
+ setConfirmClear(false);
19
+ setStatus('✓ memory cleared');
20
+ onChanged?.();
21
+ }
22
+ else if (input === 'n' || input === 'N' || key.escape) {
23
+ setConfirmClear(false);
24
+ }
25
+ return;
26
+ }
27
+ if (key.escape)
28
+ return onClose();
29
+ if (input === 'r' || input === 'R') {
30
+ setContent(loadMemory());
31
+ setStatus('refreshed');
32
+ setTimeout(() => setStatus(null), 1500);
33
+ }
34
+ if (input === 'c' || input === 'C') {
35
+ setConfirmClear(true);
36
+ }
37
+ });
38
+ const label = 'project memory';
39
+ const topPad = Math.max(1, inner - label.length - 4);
40
+ const path = memoryPath();
41
+ const pathShort = path.length > inner - 4 ? '…' + path.slice(-(inner - 5)) : path;
42
+ const lines = content ? content.split('\n') : [];
43
+ const maxShow = 20;
44
+ const shown = lines.slice(0, maxShow);
45
+ const truncated = lines.length > maxShow;
46
+ const bytes = Buffer.byteLength(content, 'utf8');
47
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '╭─ ' }), _jsx(Text, { color: theme.accent, bold: true, children: label }), _jsx(Text, { color: theme.muted, children: ' ' + '─'.repeat(topPad) + '╮' })] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: theme.muted, children: pathShort })] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsxs(Text, { color: theme.muted, children: [bytes, " bytes \u00B7 ", lines.length, " line", lines.length === 1 ? '' : 's'] }), status && (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.muted, children: " \u00B7 " }), _jsx(Text, { color: theme.success, children: status })] }))] }), _jsx(Text, { color: theme.muted, children: '│ ' + '─'.repeat(Math.max(1, inner - 2)) }), !content.trim() ? (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: theme.warning, children: "memory is empty" })] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: theme.muted, children: "the agent will add notes when you say \"remember ...\"" })] })] })) : (shown.map((line, i) => (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: line.startsWith('#')
48
+ ? theme.accent
49
+ : line.startsWith('-')
50
+ ? theme.text
51
+ : theme.muted, bold: line.startsWith('#'), children: line.slice(0, inner - 2) })] }, i)))), truncated && (_jsx(Text, { children: _jsx(Text, { color: theme.muted, children: '│ …+' + (lines.length - maxShow) + ' more lines' }) })), _jsx(Text, { color: theme.muted, children: '╰' + '─'.repeat(inner) + '╯' }), confirmClear ? (_jsxs(Text, { children: [_jsx(Text, { color: theme.warning, children: " clear all project memory? " }), _jsx(Text, { color: theme.success, bold: true, children: "[y]" }), _jsx(Text, { color: theme.muted, children: " yes " }), _jsx(Text, { color: theme.error, bold: true, children: "[n]" }), _jsx(Text, { color: theme.muted, children: " no" })] })) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: " [" }), _jsx(Text, { color: theme.accent, children: "r" }), _jsx(Text, { color: theme.muted, children: "] refresh \u00B7 [" }), _jsx(Text, { color: theme.accent, children: "c" }), _jsx(Text, { color: theme.muted, children: "] clear \u00B7 [" }), _jsx(Text, { color: theme.accent, children: "esc" }), _jsx(Text, { color: theme.muted, children: "] close" })] }))] }));
52
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blyuecode",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "BLYUE Code — a coding agent by Lumen",
5
5
  "type": "module",
6
6
  "license": "MIT",