@openlife/cli 1.7.11 → 1.7.13

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.
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ // src/cli/ChatBanner.ts
3
+ // OPENLIFE banner — pixel-block letters + Cristo Redentor silhouette.
4
+ // Reference: Downloads/ChatGPT Image 15 de mai. de 2026, 13_58_20.png
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.openlifeBanner = openlifeBanner;
7
+ exports.inventoryPanel = inventoryPanel;
8
+ exports.bootOutput = bootOutput;
9
+ const MatrixTheme_1 = require("./MatrixTheme");
10
+ // Pixel-block OPENLIFE — 5 rows tall. Each letter is 7 cols wide + 1 gap.
11
+ // Built from full block (█) so it stays Matrix-green and crisp.
12
+ const OPENLIFE_BLOCK = [
13
+ ' ██████ ██████ ███████ ███ ██ ██ ██ ███████ ███████',
14
+ '██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ',
15
+ '██ ██ ██████ █████ ██ ██ ██ ██ ██ █████ █████ ',
16
+ '██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ',
17
+ ' ██████ ██ ███████ ██ ████ ███████ ██ ██ ███████',
18
+ ];
19
+ // Cristo Redentor silhouette — stylized to fit ~16 cols × 8 rows.
20
+ // Built from block characters with the famous T-shaped pose centered.
21
+ const CRISTO = [
22
+ ' ▄ ',
23
+ ' ▟███▙ ',
24
+ ' ▄▄▟▓▓███▓▓▙▄▄ ', // arms outstretched
25
+ ' ███ ',
26
+ ' ███ ',
27
+ ' ▟█████▙ ',
28
+ ' ▟▓▓█████▓▓▙ ', // robe widens
29
+ ' ▟▓░▒▓█████▓▒░▓▙ ',
30
+ ' ▟▓░░▒▒▓██▓██▓▒▒░░▓▙ ', // base of statue
31
+ '▟▓░░░░▒▒▒▓▓▓▓▓▒▒▒░░░░▓▙',
32
+ ];
33
+ // Mountain/horizon line beneath the statue (Pão de Açúcar feel).
34
+ const HORIZON = '▁▂▃▄▆▇█▇▆▅▄▃▂▁▁▂▃▅▇█▇▆▄▃▂▁▁▂▄▅▇█▇▅▄▂▁▁▂▃▅▇█▇▅▃▂▁';
35
+ /**
36
+ * Render the full OpenLife boot banner.
37
+ * Pure function — no side effects. Caller `console.log`s the result.
38
+ *
39
+ * In non-TTY environments (CI, piped output, NO_COLOR), returns plain text
40
+ * without ANSI codes so log scrapers stay readable.
41
+ */
42
+ function openlifeBanner() {
43
+ const useColor = (0, MatrixTheme_1.supportsColor)();
44
+ const head = useColor ? MatrixTheme_1.MATRIX.head : '';
45
+ const body = useColor ? MatrixTheme_1.MATRIX.body : '';
46
+ const tail = useColor ? MatrixTheme_1.MATRIX.tail : '';
47
+ const reset = useColor ? MatrixTheme_1.ANSI.reset : '';
48
+ const lines = [];
49
+ lines.push('');
50
+ // OPENLIFE in bright matrix green
51
+ for (const row of OPENLIFE_BLOCK) {
52
+ lines.push(` ${head}${row}${reset}`);
53
+ }
54
+ lines.push('');
55
+ // Cristo silhouette in mid green (centered relative to OPENLIFE width)
56
+ const bannerWidth = OPENLIFE_BLOCK[0].length;
57
+ for (const row of CRISTO) {
58
+ lines.push(` ${body}${(0, MatrixTheme_1.center)(row, bannerWidth)}${reset}`);
59
+ }
60
+ // Horizon line in faded green
61
+ lines.push(` ${tail}${(0, MatrixTheme_1.center)(HORIZON, bannerWidth)}${reset}`);
62
+ lines.push('');
63
+ return lines.join('\n');
64
+ }
65
+ function inventoryPanel(stats) {
66
+ const useColor = (0, MatrixTheme_1.supportsColor)();
67
+ const head = useColor ? MatrixTheme_1.MATRIX.head : '';
68
+ const body = useColor ? MatrixTheme_1.MATRIX.body : '';
69
+ const tail = useColor ? MatrixTheme_1.MATRIX.tail : '';
70
+ const reset = useColor ? MatrixTheme_1.ANSI.reset : '';
71
+ // Truncate cwd to fit; keep last 50 chars (the meaningful tail).
72
+ const cwd = stats.cwd.length > 50 ? '…' + stats.cwd.slice(-49) : stats.cwd;
73
+ const w = 64;
74
+ const top = '┌─ openlife ' + '─'.repeat(w - 12) + '┐';
75
+ // Bottom must match top width exactly (both 65 chars: ┌ + 63 chars + ┐).
76
+ const bot = '└' + '─'.repeat(top.length - 2) + '┘';
77
+ const row = (label, value) => {
78
+ const left = `${label} ${value}`.padEnd(w - 2, ' ');
79
+ return `│ ${body}${left}${reset}│`;
80
+ };
81
+ const lines = [];
82
+ lines.push(`${tail}${top}${reset}`);
83
+ lines.push(row('VERSION:'.padEnd(10), stats.version));
84
+ lines.push(row('MODEL: '.padEnd(10), stats.model));
85
+ lines.push(row('SESSION:'.padEnd(10), stats.sessionId));
86
+ lines.push(row('CWD: '.padEnd(10), cwd));
87
+ lines.push(row('CATALOG:'.padEnd(10), `agents ${stats.agents} · squads ${stats.squads} · skills ${stats.skills} · mcps ${stats.mcps}`));
88
+ lines.push(`│ ${tail}${'type /help for commands · @skill to invoke a skill'.padEnd(w - 2)}${reset}│`);
89
+ lines.push(`${tail}${bot}${reset}`);
90
+ // Wrap top/bot in head color for emphasis
91
+ lines[0] = `${head}${top}${reset}`;
92
+ lines[lines.length - 1] = `${head}${bot}${reset}`;
93
+ return lines.join('\n');
94
+ }
95
+ /**
96
+ * Combined boot output (banner + inventory panel + welcome line).
97
+ * Useful test handle and one-shot console.log target from ChatTui.
98
+ */
99
+ function bootOutput(stats) {
100
+ const useColor = (0, MatrixTheme_1.supportsColor)();
101
+ const body = useColor ? MatrixTheme_1.MATRIX.body : '';
102
+ const tail = useColor ? MatrixTheme_1.MATRIX.tail : '';
103
+ const reset = useColor ? MatrixTheme_1.ANSI.reset : '';
104
+ return [
105
+ openlifeBanner(),
106
+ inventoryPanel(stats),
107
+ '',
108
+ `${body}Welcome to OpenLife. Type your message or /help for commands.${reset}`,
109
+ `${tail}${'━'.repeat(60)}${reset}`,
110
+ '',
111
+ ].join('\n');
112
+ }
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ // src/cli/ChatTui.ts
3
+ // OpenLife interactive chat REPL — Matrix 2026 theme.
4
+ //
5
+ // Entry point: `runChat()`. Bound to bare `openlife` invocation in src/index.ts
6
+ // when stdout is a TTY. Renders the OPENLIFE banner + Cristo silhouette, a
7
+ // catalog inventory panel, and a Matrix code-rain "thinking" region that
8
+ // streams Brain reasoning lines while the agent works.
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.buildInventoryStats = buildInventoryStats;
44
+ exports.runChat = runChat;
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const readline = __importStar(require("readline"));
48
+ const ChatBanner_1 = require("./ChatBanner");
49
+ const MatrixRain_1 = require("./MatrixRain");
50
+ const MatrixTheme_1 = require("./MatrixTheme");
51
+ function counts(dir) {
52
+ try {
53
+ if (!fs.existsSync(dir))
54
+ return 0;
55
+ return fs.readdirSync(dir).filter((entry) => {
56
+ const p = path.join(dir, entry);
57
+ try {
58
+ return fs.statSync(p).isDirectory() && !entry.startsWith('test-');
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }).length;
64
+ }
65
+ catch {
66
+ return 0;
67
+ }
68
+ }
69
+ function readActiveModel(root) {
70
+ try {
71
+ const p = path.join(root, 'models.json');
72
+ if (!fs.existsSync(p))
73
+ return 'unknown';
74
+ const j = JSON.parse(fs.readFileSync(p, 'utf-8'));
75
+ return j.primary?.raw || 'unknown';
76
+ }
77
+ catch {
78
+ return 'unknown';
79
+ }
80
+ }
81
+ function genSessionId() {
82
+ const d = new Date();
83
+ const pad = (n, w = 2) => String(n).padStart(w, '0');
84
+ const stamp = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
85
+ const rand = Math.random().toString(16).slice(2, 8);
86
+ return `${stamp}_${rand}`;
87
+ }
88
+ function buildInventoryStats(root, sessionId) {
89
+ const pkg = (() => {
90
+ try {
91
+ return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf-8'));
92
+ }
93
+ catch {
94
+ return { version: 'dev' };
95
+ }
96
+ })();
97
+ return {
98
+ version: `v${pkg.version}`,
99
+ model: readActiveModel(root),
100
+ sessionId,
101
+ cwd: root,
102
+ agents: counts(path.join(root, '.catalog', 'agents')),
103
+ squads: counts(path.join(root, '.catalog', 'squads')),
104
+ skills: counts(path.join(root, '.catalog', 'skills')),
105
+ mcps: counts(path.join(root, '.catalog', 'mcps')),
106
+ };
107
+ }
108
+ /** Built-in slash commands shown by `/help`. */
109
+ const SLASH_COMMANDS = [
110
+ { name: '/help', desc: 'Show this command list' },
111
+ { name: '/clear', desc: 'Clear screen and re-render banner' },
112
+ { name: '/new', desc: 'Start a fresh session id (keeps the chat scroll)' },
113
+ { name: '/history', desc: 'Show last 10 turns of the current session' },
114
+ { name: '/save', desc: 'Persist the session log to disk now' },
115
+ { name: '/model', desc: 'Print the currently active model' },
116
+ { name: '/provider', desc: 'Open the provider selector (same as /config → Provider)' },
117
+ { name: '/config', desc: 'Open the full configuration TUI' },
118
+ { name: '/status', desc: 'Print daemon + executor status' },
119
+ { name: '/doctor', desc: 'Run the doctor health checks' },
120
+ { name: '/exit', desc: 'Leave the chat' },
121
+ ];
122
+ function printHelp() {
123
+ const head = (0, MatrixTheme_1.supportsColor)() ? MatrixTheme_1.MATRIX.head : '';
124
+ const body = (0, MatrixTheme_1.supportsColor)() ? MatrixTheme_1.MATRIX.body : '';
125
+ const tail = (0, MatrixTheme_1.supportsColor)() ? MatrixTheme_1.MATRIX.tail : '';
126
+ const reset = (0, MatrixTheme_1.supportsColor)() ? MatrixTheme_1.ANSI.reset : '';
127
+ process.stdout.write(`\n${head}Commands${reset}\n`);
128
+ for (const c of SLASH_COMMANDS) {
129
+ process.stdout.write(` ${body}${c.name.padEnd(12)}${reset} ${tail}${c.desc}${reset}\n`);
130
+ }
131
+ process.stdout.write('\n');
132
+ }
133
+ function appendSessionLog(root, sessionId, entry) {
134
+ try {
135
+ const dir = path.join(root, '.openlife', 'chat-sessions');
136
+ if (!fs.existsSync(dir))
137
+ fs.mkdirSync(dir, { recursive: true });
138
+ fs.appendFileSync(path.join(dir, `${sessionId}.jsonl`), JSON.stringify(entry) + '\n', 'utf-8');
139
+ }
140
+ catch { /* logging is best-effort; never break the chat */ }
141
+ }
142
+ async function runChat(opts = {}) {
143
+ const root = opts.cwd ?? process.cwd();
144
+ let sessionId = genSessionId();
145
+ let stats = buildInventoryStats(root, sessionId);
146
+ // Boot output — banner + inventory panel + welcome
147
+ process.stdout.write((0, ChatBanner_1.bootOutput)(stats));
148
+ appendSessionLog(root, sessionId, { ts: new Date().toISOString(), role: 'system', text: 'session_start' });
149
+ // Lazy require Gateway to keep --help fast (banner test runs without it).
150
+ const lazyGateway = () => {
151
+ try {
152
+ const { Gateway } = require('../orchestrator/Gateway');
153
+ const gw = new Gateway();
154
+ return gw;
155
+ }
156
+ catch (err) {
157
+ const msg = err instanceof Error ? err.message : String(err);
158
+ process.stdout.write((0, MatrixTheme_1.paint)(`\n[chat] Gateway unavailable: ${msg}\n`, MatrixTheme_1.MATRIX.warn) + '\n');
159
+ return null;
160
+ }
161
+ };
162
+ // Build a readline interface for the input prompt.
163
+ const promptSymbol = (0, MatrixTheme_1.supportsColor)() ? `${MatrixTheme_1.MATRIX.head}›${MatrixTheme_1.ANSI.reset} ` : '› ';
164
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
165
+ const askLine = () => new Promise((resolve) => {
166
+ rl.question(promptSymbol, (answer) => resolve(answer));
167
+ });
168
+ const cleanShutdown = () => {
169
+ rl.close();
170
+ process.stdout.write(MatrixTheme_1.ANSI.showCursor + MatrixTheme_1.ANSI.reset);
171
+ };
172
+ process.on('SIGINT', () => { cleanShutdown(); process.exit(130); });
173
+ // Main REPL loop
174
+ // eslint-disable-next-line no-constant-condition
175
+ while (true) {
176
+ const raw = (await askLine()).trim();
177
+ if (!raw)
178
+ continue;
179
+ if (raw === '/exit' || raw === '/quit') {
180
+ process.stdout.write((0, MatrixTheme_1.paint)(`\nResume with: openlife --session ${sessionId}\n`, MatrixTheme_1.MATRIX.tail) + '\n');
181
+ cleanShutdown();
182
+ return;
183
+ }
184
+ if (raw === '/help') {
185
+ printHelp();
186
+ continue;
187
+ }
188
+ if (raw === '/clear') {
189
+ process.stdout.write(MatrixTheme_1.ANSI.clearScreen);
190
+ process.stdout.write((0, ChatBanner_1.bootOutput)(stats));
191
+ continue;
192
+ }
193
+ if (raw === '/new') {
194
+ sessionId = genSessionId();
195
+ stats = buildInventoryStats(root, sessionId);
196
+ process.stdout.write((0, MatrixTheme_1.paint)(`\nNew session: ${sessionId}\n`, MatrixTheme_1.MATRIX.head) + '\n');
197
+ appendSessionLog(root, sessionId, { ts: new Date().toISOString(), role: 'system', text: 'session_start' });
198
+ continue;
199
+ }
200
+ if (raw === '/model') {
201
+ process.stdout.write((0, MatrixTheme_1.paint)(`\nactive model: ${readActiveModel(root)}\n`, MatrixTheme_1.MATRIX.body) + '\n');
202
+ continue;
203
+ }
204
+ if (raw === '/status') {
205
+ try {
206
+ const { execFileSync } = require('child_process');
207
+ const out = execFileSync(process.execPath, [path.join(root, 'bin', 'openlife.js'), 'status'], { encoding: 'utf-8' });
208
+ process.stdout.write('\n' + out + '\n');
209
+ }
210
+ catch (e) {
211
+ process.stdout.write((0, MatrixTheme_1.paint)(`\nstatus failed: ${e instanceof Error ? e.message : String(e)}\n`, MatrixTheme_1.MATRIX.err) + '\n');
212
+ }
213
+ continue;
214
+ }
215
+ if (raw === '/doctor') {
216
+ try {
217
+ const { execFileSync } = require('child_process');
218
+ const out = execFileSync(process.execPath, [path.join(root, 'bin', 'openlife.js'), 'doctor'], { encoding: 'utf-8' });
219
+ process.stdout.write('\n' + out + '\n');
220
+ }
221
+ catch (e) {
222
+ process.stdout.write((0, MatrixTheme_1.paint)(`\ndoctor failed: ${e instanceof Error ? e.message : String(e)}\n`, MatrixTheme_1.MATRIX.err) + '\n');
223
+ }
224
+ continue;
225
+ }
226
+ if (raw === '/config' || raw === '/provider') {
227
+ try {
228
+ const { runConfig } = require('./ConfigTui');
229
+ const onlyProvider = raw === '/provider';
230
+ await runConfig({ cwd: root, focus: onlyProvider ? 'provider' : undefined });
231
+ // Re-render banner after returning so the chat scroll context is preserved.
232
+ stats = buildInventoryStats(root, sessionId);
233
+ process.stdout.write((0, ChatBanner_1.bootOutput)(stats));
234
+ }
235
+ catch (e) {
236
+ process.stdout.write((0, MatrixTheme_1.paint)(`\nconfig failed: ${e instanceof Error ? e.message : String(e)}\n`, MatrixTheme_1.MATRIX.err) + '\n');
237
+ }
238
+ continue;
239
+ }
240
+ if (raw === '/history' || raw === '/save') {
241
+ const file = path.join(root, '.openlife', 'chat-sessions', `${sessionId}.jsonl`);
242
+ if (raw === '/save') {
243
+ process.stdout.write((0, MatrixTheme_1.paint)(`\nsession persisted: ${file}\n`, MatrixTheme_1.MATRIX.head) + '\n');
244
+ }
245
+ else {
246
+ try {
247
+ const data = fs.readFileSync(file, 'utf-8').trim().split('\n').slice(-20).map(l => JSON.parse(l));
248
+ process.stdout.write('\n');
249
+ for (const e of data) {
250
+ const tag = e.role === 'user' ? '●' : e.role === 'assistant' ? '◢' : '·';
251
+ process.stdout.write(` ${(0, MatrixTheme_1.paint)(tag, MatrixTheme_1.MATRIX.head)} ${e.role}: ${e.text.split('\n')[0].slice(0, 100)}\n`);
252
+ }
253
+ process.stdout.write('\n');
254
+ }
255
+ catch (err) {
256
+ process.stdout.write((0, MatrixTheme_1.paint)(`\nno history yet\n`, MatrixTheme_1.MATRIX.tail) + '\n');
257
+ }
258
+ }
259
+ continue;
260
+ }
261
+ // Anything else → user message. Log, render header, fire backend, render response.
262
+ const userTag = (0, MatrixTheme_1.paint)('●', MatrixTheme_1.MATRIX.head);
263
+ process.stdout.write(`${userTag} ${raw}\n`);
264
+ appendSessionLog(root, sessionId, { ts: new Date().toISOString(), role: 'user', text: raw });
265
+ const gw = lazyGateway();
266
+ if (!gw) {
267
+ process.stdout.write((0, MatrixTheme_1.paint)(' Gateway unavailable — start the daemon first.\n', MatrixTheme_1.MATRIX.warn) + '\n');
268
+ continue;
269
+ }
270
+ // Reserve a region for Matrix rain. Width = min(60, columns - 4).
271
+ const cols = process.stdout.columns ?? 80;
272
+ const rows = process.stdout.rows ?? 24;
273
+ const rainWidth = Math.min(30, Math.max(20, Math.floor(cols / 3)));
274
+ const panelWidth = Math.max(20, cols - rainWidth - 4);
275
+ const rainRow = Math.max(2, rows - 10);
276
+ const rainHeight = 8;
277
+ const reasoningLines = [];
278
+ const sidePanelLines = () => {
279
+ const tail = reasoningLines.slice(-rainHeight);
280
+ while (tail.length < rainHeight)
281
+ tail.unshift('');
282
+ return tail.map(line => (line.length > panelWidth ? line.slice(0, panelWidth - 1) + '…' : line));
283
+ };
284
+ // Allocate vertical space so rain has somewhere to render.
285
+ for (let i = 0; i < rainHeight; i++)
286
+ process.stdout.write('\n');
287
+ const rain = (0, MatrixRain_1.startRain)({
288
+ row: rainRow,
289
+ col: 3,
290
+ width: rainWidth + 1 + panelWidth,
291
+ height: rainHeight,
292
+ rainWidth,
293
+ sidePanelLines,
294
+ });
295
+ const startedAt = Date.now();
296
+ let answer = '';
297
+ try {
298
+ answer = await gw.processChat('cli-user', raw, {
299
+ onReasoning: (chunk) => {
300
+ reasoningLines.push(chunk);
301
+ if (reasoningLines.length > 200)
302
+ reasoningLines.splice(0, reasoningLines.length - 200);
303
+ },
304
+ });
305
+ }
306
+ catch (err) {
307
+ answer = `[chat error] ${err instanceof Error ? err.message : String(err)}`;
308
+ }
309
+ rain.stop();
310
+ const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
311
+ const aiTag = (0, MatrixTheme_1.paint)('◢', MatrixTheme_1.MATRIX.head);
312
+ process.stdout.write(`\n${aiTag} ${answer}\n`);
313
+ process.stdout.write((0, MatrixTheme_1.paint)(` (${elapsed}s · ${readActiveModel(root)})\n`, MatrixTheme_1.MATRIX.tail) + '\n');
314
+ appendSessionLog(root, sessionId, { ts: new Date().toISOString(), role: 'assistant', text: answer });
315
+ }
316
+ }