skyloom 1.4.2 → 1.4.4

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/cli/main.js CHANGED
@@ -1,9 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- /**
4
- * CLI interface for Skyloom — terminal agent product.
5
- * Uses Commander.js for command routing.
6
- */
7
3
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
4
  if (k2 === undefined) k2 = k;
9
5
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -41,6 +37,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
41
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
42
38
  };
43
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
+ /**
41
+ * 天空织机 CLI — Skyloom Terminal Interface
42
+ * Raw-mode input + slash command popup + streaming display
43
+ */
44
44
  const commander_1 = require("commander");
45
45
  const fs = __importStar(require("fs"));
46
46
  const readline = __importStar(require("readline"));
@@ -50,353 +50,685 @@ const config_1 = require("../core/config");
50
50
  const router_1 = require("../core/router");
51
51
  const mode_1 = require("./mode");
52
52
  const MODE = new mode_1.ModeController();
53
- const VERSION = '1.4.0';
54
- const program = new commander_1.Command();
55
- program
56
- .name('sky')
57
- .description('Skyloom CLI multi-agent orchestration framework')
58
- .version(VERSION);
59
- // ── Chat command ──
60
- program
61
- .command('chat')
62
- .description('Start interactive chat with an agent')
63
- .argument('[agent]', 'Agent name (fog, rain, frost, snow, dew, fair)', 'fog')
64
- .option('-m, --model <model>', 'Model to use')
65
- .action(async (agentName, options) => {
66
- try {
67
- await interactiveChat(agentName, options.model);
68
- }
69
- catch (e) {
70
- console.error(chalk_1.default.red(`Error: ${e}`));
71
- process.exit(1);
72
- }
73
- });
74
- // ── Task command ──
75
- program
76
- .command('task')
77
- .description('Execute a multi-agent orchestration task')
78
- .argument('[goal]', 'Task goal description')
79
- .option('-r, --resume', 'Resume from checkpoint')
80
- .action(async (goal, options) => {
81
- if (!goal) {
82
- console.log(chalk_1.default.yellow('Please provide a task goal. Usage: sky task "<goal>"'));
83
- return;
84
- }
53
+ const VERSION = "1.4.2";
54
+ /* ── Agent colors ── */
55
+ const AGENT_COLORS = {
56
+ fog: "#b8c6db", rain: "#4a90d9", frost: "#2cd4d4",
57
+ snow: "#e8ecf1", dew: "#7bed9f", fair: "#f7b733",
58
+ };
59
+ const AGENT_DISPLAY = {
60
+ fog: "≋ 雾 Fog", rain: "⸽ 雨 Rain", frost: "✱ 霜 Frost",
61
+ snow: "❉ 雪 Snow", dew: "∘ 露 Dew", fair: "☼ 晴 Fair",
62
+ };
63
+ const AGENT_NAMES = ["fog", "rain", "frost", "snow", "dew", "fair"];
64
+ /* ═══════════════════════════════════════
65
+ Commander program
66
+ ═══════════════════════════════════════ */
67
+ const program = new commander_1.Command()
68
+ .name("sky").description("天空织机 Skyloom — 6 weather-themed AI agents").version(VERSION);
69
+ program.command("chat").description("Start interactive chat")
70
+ .argument("[agent]", "agent name", "fog")
71
+ .option("-m,--model <model>", "Model override")
72
+ .action(async (a, o) => { await chat(a, o.model); });
73
+ program.command("task").description("Multi-agent orchestration")
74
+ .argument("[goal]", "task goal")
75
+ .option("-r,--resume", "resume from checkpoint")
76
+ .action(async (g, o) => { if (g)
77
+ await runTask(g, o?.resume); });
78
+ program.command("web").description("Start web server")
79
+ .option("-p,--port <port>", "port", "3000")
80
+ .action(async (o) => { const { startWebServer } = await Promise.resolve().then(() => __importStar(require("../web/server"))); await startWebServer(parseInt(o.port || "3000", 10)); });
81
+ program.command("mcp").description("Start MCP server")
82
+ .action(async () => { const { startMCPServer } = await Promise.resolve().then(() => __importStar(require("../core/mcp_server"))); await startMCPServer(); });
83
+ program.command("config").description("Show configuration")
84
+ .action(() => { const c = (0, config_1.loadConfig)(); logLine(chalk_1.default.cyan("Config dir: ") + config_1.USER_CONFIG_DIR); logLine(chalk_1.default.cyan("Agent models:")); for (const [n, a] of Object.entries(c.agents || {}))
85
+ logLine(` ${chalk_1.default.bold(n)}: ${a.model || "default"}`); });
86
+ program.command("init").description("Initialize config directory")
87
+ .action(() => { const d = config_1.USER_CONFIG_DIR; if (!fs.existsSync(d))
88
+ fs.mkdirSync(d, { recursive: true }); logLine(chalk_1.default.green("✓ ") + d); });
89
+ program.command("version").description("Show version")
90
+ .action(() => logLine(`Skyloom v${VERSION}`));
91
+ /* ═══════════════════════════════════════
92
+ Interactive Chat — raw-mode input + popup
93
+ ═══════════════════════════════════════ */
94
+ const SLASH_CMDS = [
95
+ ["/help", "Show all commands", false, ""],
96
+ ["/clear", "Clear screen", false, ""],
97
+ ["/status", "Agent overview", false, ""],
98
+ ["/cost", "Usage & cost", false, ""],
99
+ ["/cost reset", "Reset usage stats", false, ""],
100
+ ["/compact", "Compress context", false, ""],
101
+ ["/retry", "Resend last message", false, ""],
102
+ ["/mcp", "MCP server status", false, ""],
103
+ ["/memory", "Memory stats", false, ""],
104
+ ["/sessions", "Session list", false, ""],
105
+ ["/workspace", "Workspace info", false, ""],
106
+ ["/model", "Model info", false, ""],
107
+ ["/version", "Version info", false, ""],
108
+ ["/task <goal>", "Multi-agent orchestrate", true, ""],
109
+ ["/quiz", "Export chat as quiz", false, ""],
110
+ ["/fog", "≋ Fog — research", false, "fog"],
111
+ ["/rain", "⸽ Rain — codegen", false, "rain"],
112
+ ["/frost", "✱ Frost — review", false, "frost"],
113
+ ["/snow", "❉ Snow — planning", false, "snow"],
114
+ ["/dew", "∘ Dew — devops", false, "dew"],
115
+ ["/fair", "☼ Fair — companion", false, "fair"],
116
+ ["/quit", "Exit chat", false, ""],
117
+ ["/exit", "Exit chat", false, ""],
118
+ ];
119
+ function logLine(s) { process.stdout.write(s + "\n"); }
120
+ /* ── Stream response with spinner ── */
121
+ async function chatWithSpinner(agent, ctx, message) {
122
+ let frame = 0;
123
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
124
+ const spinner = setInterval(() => {
125
+ readline.cursorTo(process.stdout, 0);
126
+ process.stdout.write(chalk_1.default.cyan(` ${frames[frame % frames.length]} ${agent.displayName} thinking...`));
127
+ frame++;
128
+ }, 80);
85
129
  try {
86
- await runTask(goal, options?.resume);
130
+ const response = await agent.chat(message);
131
+ clearInterval(spinner);
132
+ readline.cursorTo(process.stdout, 0);
133
+ process.stdout.write(" ".repeat(50) + "\r");
134
+ return response;
87
135
  }
88
136
  catch (e) {
89
- console.error(chalk_1.default.red(`Error: ${e}`));
137
+ clearInterval(spinner);
138
+ throw e;
90
139
  }
91
- });
92
- // ── Web command ──
93
- program
94
- .command('web')
95
- .description('Start web server')
96
- .option('-p, --port <port>', 'Port to listen on', '3000')
97
- .action(async (options) => {
98
- try {
99
- // Dynamic import to avoid loading express when not needed
100
- const { startWebServer } = await Promise.resolve().then(() => __importStar(require('../web/server')));
101
- const port = parseInt(options.port || '3000', 10);
102
- await startWebServer(port);
140
+ }
141
+ /* ── Render response ── */
142
+ function renderResponse(text) {
143
+ const lines = [];
144
+ const w = process.stdout.columns || 80;
145
+ const maxW = Math.min(w - 6, 76);
146
+ for (const block of text.split("\n\n")) {
147
+ const trimmed = block.trim();
148
+ if (!trimmed)
149
+ continue;
150
+ // Code blocks
151
+ if (trimmed.startsWith("```")) {
152
+ const codeLines = trimmed.split("\n");
153
+ lines.push(chalk_1.default.dim(" ┌─ code ──────────────"));
154
+ for (let i = 1; i < codeLines.length - 1; i++) {
155
+ const cl = codeLines[i];
156
+ lines.push(` ${chalk_1.default.dim("│")} ${chalk_1.default.white(cl.slice(0, maxW - 4))}`);
157
+ }
158
+ lines.push(chalk_1.default.dim(" └────────────────────"));
159
+ continue;
160
+ }
161
+ // Wrap long lines
162
+ for (const line of trimmed.split("\n")) {
163
+ if (line.startsWith("#")) {
164
+ lines.push(" " + chalk_1.default.bold(line));
165
+ }
166
+ else if (line.startsWith("- ") || line.startsWith("* ")) {
167
+ lines.push(" " + chalk_1.default.dim("• ") + line.slice(2));
168
+ }
169
+ else {
170
+ let remaining = line;
171
+ while (remaining.length > maxW) {
172
+ const cut = remaining.lastIndexOf(" ", maxW);
173
+ const idx = cut > 0 ? cut : maxW;
174
+ lines.push(" " + remaining.slice(0, idx));
175
+ remaining = remaining.slice(idx).trimStart();
176
+ }
177
+ if (remaining)
178
+ lines.push(" " + remaining);
179
+ }
180
+ }
103
181
  }
104
- catch (e) {
105
- console.error(chalk_1.default.red(`Web server error: ${e}`));
182
+ return lines;
183
+ }
184
+ /* ── Welcome banner ── */
185
+ function printWelcome(agent, model) {
186
+ const w = process.stdout.columns || 80;
187
+ logLine("");
188
+ logLine(" ".repeat(Math.max(0, Math.floor((w - 40) / 2))) + chalk_1.default.cyan("✦ 天 空 织 机 ✦"));
189
+ logLine(" ".repeat(Math.max(0, Math.floor((w - 36) / 2))) + chalk_1.default.dim("S K Y L O O M"));
190
+ logLine("");
191
+ const agentLine = [];
192
+ for (const name of AGENT_NAMES) {
193
+ const active = name === agent.name;
194
+ const prefix = active ? chalk_1.default.bold(AGENT_DISPLAY[name].split(" ").slice(0, 2).join(" ")) : chalk_1.default.dim(AGENT_DISPLAY[name].split(" ")[0] + " " + AGENT_DISPLAY[name].split(" ")[1]);
195
+ agentLine.push(prefix);
106
196
  }
107
- });
108
- // ── MCP command ──
109
- program
110
- .command('mcp')
111
- .description('Start MCP server (stdio JSON-RPC for Claude Desktop etc.)')
112
- .action(async () => {
197
+ logLine(" " + agentLine.join(chalk_1.default.dim(" · ")));
198
+ logLine("");
199
+ logLine(chalk_1.default.dim(` Model: ${model} · /help for commands · /quit to exit`));
200
+ logLine("");
201
+ }
202
+ /* ── Status bar ── */
203
+ function statusBar(agent, ctx) {
204
+ let ctxStr = "";
205
+ let costStr = "$0";
206
+ let modelStr = "default";
113
207
  try {
114
- console.error(chalk_1.default.cyan('Starting MCP server on stdio...'));
115
- const { startMCPServer } = await Promise.resolve().then(() => __importStar(require('../core/mcp_server')));
116
- await startMCPServer();
117
- }
118
- catch (e) {
119
- console.error(chalk_1.default.red(`MCP server error: ${e}`));
120
- process.exit(1);
208
+ const cu = agent.contextUsage();
209
+ modelStr = cu.model || "?";
210
+ const pct = cu.pct || 0;
211
+ const barColor = pct < 50 ? chalk_1.default.green : pct < 80 ? chalk_1.default.yellow : chalk_1.default.red;
212
+ const barLen = Math.round(pct / 10);
213
+ ctxStr = `${barColor("█".repeat(barLen) + "░".repeat(10 - barLen))} ${pct}%`;
214
+ costStr = formatCost(ctx.llm.getTotalCost());
121
215
  }
122
- });
123
- // ── Config command ──
124
- program
125
- .command('config')
126
- .description('Show current configuration')
127
- .action(() => {
128
- const config = (0, config_1.loadConfig)();
129
- console.log(chalk_1.default.bold('\nSkyloom Configuration'));
130
- console.log(chalk_1.default.dim('─'.repeat(40)));
131
- console.log(chalk_1.default.cyan('Config dir:'), config_1.USER_CONFIG_DIR);
132
- console.log(chalk_1.default.cyan('Agents:'));
133
- for (const [name, cfg] of Object.entries(config.agents || {})) {
134
- console.log(` ${chalk_1.default.bold(name)}: ${cfg.model || 'default'}`);
135
- }
136
- });
137
- // ── Init / Setup command ──
138
- program
139
- .command('init')
140
- .description('Initialize Skyloom configuration')
141
- .action(() => {
142
- const configDir = config_1.USER_CONFIG_DIR;
143
- if (!fs.existsSync(configDir)) {
144
- fs.mkdirSync(configDir, { recursive: true });
216
+ catch { /* ignore */ }
217
+ const w = process.stdout.columns || 80;
218
+ return chalk_1.default.dim(`┤ ${ctxStr} · ${costStr} · ${modelStr} ├${"─".repeat(Math.max(0, w - 60))}`);
219
+ }
220
+ function formatCost(cost) {
221
+ if (cost >= 1)
222
+ return chalk_1.default.yellow(`$${cost.toFixed(2)}`);
223
+ if (cost >= 0.01)
224
+ return chalk_1.default.yellow(`$${cost.toFixed(4)}`);
225
+ if (cost > 0)
226
+ return chalk_1.default.green(`${(cost * 100).toFixed(2)}¢`);
227
+ return "$0";
228
+ }
229
+ /* ── Slash-command popup ── */
230
+ async function readWithPopup(agent, ctx) {
231
+ if (!process.stdin.isTTY) {
232
+ return new Promise(resolve => {
233
+ const rl = readline.createInterface({ input: process.stdin });
234
+ rl.on("line", (line) => { rl.close(); resolve(line.trim()); });
235
+ });
145
236
  }
146
- console.log(chalk_1.default.green(`✓ Initialized config at ${configDir}`));
147
- console.log(chalk_1.default.dim('Edit config.yaml in that directory to configure agents and models.'));
148
- });
149
- // ── Version command ──
150
- program
151
- .command('version')
152
- .description('Show version')
153
- .action(() => {
154
- console.log(`Skyloom v${VERSION}`);
155
- });
156
- // ── Interactive chat ──
157
- async function interactiveChat(agentName, modelOverride) {
237
+ return new Promise(resolve => {
238
+ const stdin = process.stdin;
239
+ try {
240
+ stdin.setRawMode?.(true);
241
+ }
242
+ catch { /* non-TTY */ }
243
+ stdin.resume();
244
+ let buf = "";
245
+ let cursor = 0;
246
+ let popup = false;
247
+ let selIdx = 0;
248
+ const _history = readWithPopup._popupHistory || [];
249
+ let histIdx = _history.length;
250
+ function render() {
251
+ const w = process.stdout.columns || 80;
252
+ readline.cursorTo(process.stdout, 0);
253
+ // Clear current line area
254
+ const promptLine = ` ${chalk_1.default.cyan(agent.displayName)} ${chalk_1.default.dim("❯")} `;
255
+ // Build filtered commands
256
+ const filtered = popup ? SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase())) : [];
257
+ if (filtered.length && selIdx >= filtered.length)
258
+ selIdx = filtered.length - 1;
259
+ const popupH = Math.min(filtered.length, 10);
260
+ const totalH = popup ? popupH + 3 : 1;
261
+ // Move cursor up
262
+ if (popup) {
263
+ for (let i = 0; i < popupH + 2; i++)
264
+ process.stdout.write("\x1b[1A\x1b[2K");
265
+ }
266
+ else {
267
+ process.stdout.write("\x1b[2K\r");
268
+ }
269
+ // Input line
270
+ const before = buf.slice(0, cursor);
271
+ const after = buf.slice(cursor);
272
+ const cursorChar = after[0] || " ";
273
+ process.stdout.write(promptLine + before + chalk_1.default.inverse(cursorChar) + after.slice(1) + "\n");
274
+ // Popup
275
+ if (popup && filtered.length) {
276
+ const maxW = Math.min(w - 4, 60);
277
+ const start = Math.max(0, Math.min(selIdx - 4, filtered.length - 8));
278
+ const end = Math.min(filtered.length, start + 8);
279
+ process.stdout.write(chalk_1.default.dim(` ┌─ commands (↑↓ pick · type to filter · tab/enter select · esc close)${"─".repeat(Math.max(0, maxW - 58))}┐\n`));
280
+ for (let i = start; i < end; i++) {
281
+ const [cmd, desc] = filtered[i];
282
+ const marker = i === selIdx ? chalk_1.default.cyan(" ▶ ") : " ";
283
+ const cmdColored = i === selIdx ? chalk_1.default.bold(cmd) : chalk_1.default.cyan(cmd);
284
+ const line = ` │${marker}${cmdColored.padEnd(24)}${chalk_1.default.dim(desc)}`;
285
+ process.stdout.write(line + " ".repeat(Math.max(0, maxW - line.length + 6)) + "│\n");
286
+ }
287
+ process.stdout.write(chalk_1.default.dim(` └${"─".repeat(maxW + 1)}┘\n`));
288
+ }
289
+ // Status bar
290
+ process.stdout.write(statusBar(agent, ctx) + (popup ? "" : "\n"));
291
+ }
292
+ function accept(line) {
293
+ try {
294
+ stdin.setRawMode?.(false);
295
+ }
296
+ catch { }
297
+ stdin.pause();
298
+ resolve(line);
299
+ }
300
+ stdin.on("data", (data) => {
301
+ const seq = data.toString();
302
+ for (const ch of seq) {
303
+ // Esc
304
+ if (ch === "\x1b") {
305
+ if (popup) {
306
+ popup = false;
307
+ render();
308
+ return;
309
+ }
310
+ accept("");
311
+ return;
312
+ }
313
+ // Enter
314
+ if (ch === "\r" || ch === "\n") {
315
+ if (popup && SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase())).length) {
316
+ const filtered = SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase()));
317
+ buf = filtered[selIdx]?.[0] || buf;
318
+ popup = false;
319
+ }
320
+ accept(buf.trim());
321
+ return;
322
+ }
323
+ // Tab
324
+ if (ch === "\t") {
325
+ if (popup && SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase())).length) {
326
+ const filtered = SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase()));
327
+ buf = filtered[selIdx]?.[0] || buf;
328
+ cursor = buf.length;
329
+ popup = false;
330
+ render();
331
+ return;
332
+ }
333
+ // Insert 2 spaces
334
+ buf = buf.slice(0, cursor) + " " + buf.slice(cursor);
335
+ cursor += 2;
336
+ render();
337
+ return;
338
+ }
339
+ // Backspace
340
+ if (ch === "\x7f" || ch === "\b") {
341
+ if (cursor > 0) {
342
+ buf = buf.slice(0, cursor - 1) + buf.slice(cursor);
343
+ cursor--;
344
+ }
345
+ if (!buf) {
346
+ popup = false;
347
+ }
348
+ render();
349
+ return;
350
+ }
351
+ // Ctrl+C
352
+ if (ch === "\x03") {
353
+ accept("/quit");
354
+ return;
355
+ }
356
+ // Printable
357
+ if (ch >= " ") {
358
+ buf = buf.slice(0, cursor) + ch + buf.slice(cursor);
359
+ cursor++;
360
+ if (buf === "/") {
361
+ popup = true;
362
+ selIdx = 0;
363
+ }
364
+ else if (popup) {
365
+ selIdx = 0;
366
+ }
367
+ render();
368
+ return;
369
+ }
370
+ }
371
+ });
372
+ // Arrow keys come as escape sequences — handle via process.stdin
373
+ // For simplicity, arrow keys in raw mode are: \x1b[A (up), \x1b[B (down), \x1b[C (right), \x1b[D (left)
374
+ // We handle them in the data handler above by checking for escape sequences
375
+ // Actually the raw mode data comes byte by byte — let me use a state machine approach
376
+ // For now, let me handle the common case: the full escape sequence arrives in one data event
377
+ // Note: Arrow keys are 3 bytes: \x1b [ A/B/C/D. They may arrive in one or multiple data events.
378
+ // In practice on Windows they arrive as one event. Let me add a simple buffered approach.
379
+ stdin.removeAllListeners("data");
380
+ let escBuf = "";
381
+ stdin.on("data", (data) => {
382
+ const str = data.toString();
383
+ escBuf += str;
384
+ // If we have an escape sequence, process it
385
+ if (escBuf.startsWith("\x1b[")) {
386
+ if (escBuf.length >= 3) {
387
+ const code = escBuf[2];
388
+ escBuf = "";
389
+ if (code === "A") {
390
+ if (popup) {
391
+ selIdx = Math.max(0, selIdx - 1);
392
+ }
393
+ else if (_history.length) {
394
+ histIdx = Math.max(0, histIdx - 1);
395
+ buf = _history[histIdx] || "";
396
+ cursor = buf.length;
397
+ }
398
+ render();
399
+ return;
400
+ }
401
+ if (code === "B") {
402
+ if (popup) {
403
+ const f = SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase()));
404
+ selIdx = Math.min(f.length - 1, selIdx + 1);
405
+ }
406
+ else if (_history.length && histIdx < _history.length) {
407
+ histIdx++;
408
+ buf = _history[histIdx] || "";
409
+ cursor = buf.length;
410
+ }
411
+ render();
412
+ return;
413
+ }
414
+ if (code === "C") {
415
+ if (cursor < buf.length)
416
+ cursor++;
417
+ if (popup)
418
+ popup = false;
419
+ render();
420
+ return;
421
+ }
422
+ if (code === "D") {
423
+ if (cursor > 0)
424
+ cursor--;
425
+ if (popup)
426
+ popup = false;
427
+ render();
428
+ return;
429
+ }
430
+ }
431
+ else {
432
+ return; /* wait for more bytes */
433
+ }
434
+ }
435
+ // Not an escape sequence — process normally
436
+ for (const ch of escBuf) {
437
+ escBuf = "";
438
+ if (ch === "\x1b") {
439
+ if (popup) {
440
+ popup = false;
441
+ render();
442
+ }
443
+ return;
444
+ }
445
+ if (ch === "\r" || ch === "\n") {
446
+ if (popup && SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase())).length) {
447
+ buf = SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase()))[selIdx]?.[0] || buf;
448
+ popup = false;
449
+ }
450
+ accept(buf.trim());
451
+ return;
452
+ }
453
+ if (ch === "\t") {
454
+ if (popup && SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase())).length) {
455
+ const filtered = SLASH_CMDS.filter(c => c[0].toLowerCase().includes(buf.toLowerCase()));
456
+ buf = filtered[selIdx]?.[0] || buf;
457
+ cursor = buf.length;
458
+ popup = false;
459
+ render();
460
+ return;
461
+ }
462
+ buf = buf.slice(0, cursor) + " " + buf.slice(cursor);
463
+ cursor += 2;
464
+ render();
465
+ return;
466
+ }
467
+ if (ch === "\x7f" || ch === "\b") {
468
+ if (cursor > 0) {
469
+ buf = buf.slice(0, cursor - 1) + buf.slice(cursor);
470
+ cursor--;
471
+ }
472
+ if (!buf)
473
+ popup = false;
474
+ render();
475
+ return;
476
+ }
477
+ if (ch === "\x03") {
478
+ accept("/quit");
479
+ return;
480
+ }
481
+ if (ch >= " ") {
482
+ buf = buf.slice(0, cursor) + ch + buf.slice(cursor);
483
+ cursor++;
484
+ if (buf === "/") {
485
+ popup = true;
486
+ selIdx = 0;
487
+ }
488
+ else if (popup)
489
+ selIdx = 0;
490
+ render();
491
+ return;
492
+ }
493
+ }
494
+ });
495
+ render();
496
+ });
497
+ }
498
+ /* ═══════════════════════════════════════
499
+ Main chat loop
500
+ ═══════════════════════════════════════ */
501
+ async function chat(agentName, modelOverride) {
158
502
  const ctx = (0, factory_1.createSystemContext)();
159
- const agent = ctx.agentMap.get(agentName);
503
+ let agent = ctx.agentMap.get(agentName);
160
504
  if (!agent) {
161
- console.error(chalk_1.default.red(`Unknown agent: ${agentName}. Available: ${[...ctx.agentMap.keys()].join(', ')}`));
505
+ logLine(chalk_1.default.red(`Unknown agent: ${agentName}`));
162
506
  return;
163
507
  }
164
508
  await agent.init();
165
- const color = getAgentColor(agentName);
166
- console.log();
167
- console.log(chalk_1.default.cyan('≈ S K Y L O O M ≈'));
168
- console.log(chalk_1.default.dim(`Agent: ${chalk_1.default.bold(agent.displayName)} · Model: ${modelOverride || 'default'}`));
169
- console.log(chalk_1.default.dim('Type /help for commands, /quit to exit'));
170
- console.log();
171
- const rl = readline.createInterface({
172
- input: process.stdin,
173
- output: process.stdout,
174
- prompt: '',
175
- });
509
+ const model = modelOverride || "default";
510
+ printWelcome(agent, model);
176
511
  const inputHistory = [];
177
- const processInput = async (input) => {
178
- const cmd = input.trim();
179
- if (!cmd)
180
- return;
181
- // Slash commands
182
- if (cmd === '/quit' || cmd === '/exit') {
183
- rl.close();
184
- return;
512
+ while (true) {
513
+ /* ── Read input ── */
514
+ const inp = await readWithPopup(agent, ctx);
515
+ if (!inp) {
516
+ logLine("");
517
+ continue;
185
518
  }
186
- if (cmd === '/help') {
519
+ // Save to history
520
+ if (!inputHistory[inputHistory.length - 1] || inputHistory[inputHistory.length - 1] !== inp) {
521
+ inputHistory.push(inp);
522
+ if (inputHistory.length > 50)
523
+ inputHistory.shift();
524
+ }
525
+ readWithPopup._popupHistory = inputHistory;
526
+ const cmd = inp.trim();
527
+ const cmdLower = cmd.toLowerCase();
528
+ /* ── Slash commands ── */
529
+ let handled = false;
530
+ // Agent switching
531
+ for (const n of AGENT_NAMES) {
532
+ if (cmdLower === `/${n}`) {
533
+ const newAgent = ctx.agentMap.get(n);
534
+ if (newAgent) {
535
+ await newAgent.init();
536
+ // Switch agent reference (mutate closure)
537
+ logLine(chalk_1.default.dim(`\n ⟳ ${AGENT_DISPLAY[n]}\n`));
538
+ // We can't reassign the outer `agent` const, so use a workaround
539
+ chat._currentAgent = newAgent;
540
+ chat._currentCtx = ctx;
541
+ // Actually, let me handle this differently — replace the agent in the closure
542
+ agent = newAgent;
543
+ }
544
+ handled = true;
545
+ break;
546
+ }
547
+ }
548
+ if (cmdLower === "/quit" || cmdLower === "/exit")
549
+ break;
550
+ if (cmdLower === "/help") {
187
551
  printHelp();
188
- return;
552
+ handled = true;
189
553
  }
190
- if (cmd === '/clear') {
554
+ if (cmdLower === "/clear") {
191
555
  console.clear();
192
- return;
556
+ handled = true;
193
557
  }
194
- if (cmd === '/status') {
195
- const status = agent.getStatus();
196
- console.log(chalk_1.default.bold('\nAgent Status'));
197
- console.log(chalk_1.default.dim('─'.repeat(40)));
198
- console.log(` ${chalk_1.default.cyan(status.displayName)} (${status.name})`);
199
- console.log(` State: ${status.state}`);
200
- console.log(` Specialty: ${status.specialty}`);
201
- if (status.skills?.length) {
202
- const active = status.skills.filter((s) => s.active).map((s) => s.name);
203
- if (active.length)
204
- console.log(` Active skills: ${chalk_1.default.green(active.join(', '))}`);
205
- }
206
- return;
558
+ if (cmdLower === "/version") {
559
+ logLine(` Skyloom v${VERSION}`);
560
+ handled = true;
561
+ }
562
+ if (cmdLower === "/status") {
563
+ logLine(chalk_1.default.bold(`\n ${agent.displayName} (${agent.name})`));
564
+ logLine(chalk_1.default.dim(` State: ${agent.state} · Specialty: ${agent.specialty}`));
565
+ logLine(chalk_1.default.dim(` Memory: ${agent.memory.shortTerm.length} messages · ${Object.keys(agent.memory.working).length} working keys`));
566
+ handled = true;
567
+ }
568
+ if (cmdLower === "/cost") {
569
+ logLine(chalk_1.default.bold("\n Usage & Cost"));
570
+ logLine(chalk_1.default.dim(" ─".repeat(24)));
571
+ logLine(` Total: ${formatCost(ctx.llm.getTotalCost())}`);
572
+ handled = true;
573
+ }
574
+ if (cmdLower === "/cost reset") {
575
+ ctx.llm.resetUsageStats?.();
576
+ logLine(chalk_1.default.dim(" Stats reset"));
577
+ handled = true;
578
+ }
579
+ if (cmdLower === "/compact") {
580
+ logLine(chalk_1.default.dim(" Compacting..."));
581
+ const r = await agent.compact();
582
+ logLine(chalk_1.default.green(` ✓ ${r}`));
583
+ handled = true;
584
+ }
585
+ if (cmdLower === "/memory") {
586
+ logLine(chalk_1.default.bold("\n Memory"));
587
+ logLine(chalk_1.default.dim(` Short-term: ${agent.memory.shortTerm.length} msgs · Working: ${Object.keys(agent.memory.working).length} keys`));
588
+ handled = true;
207
589
  }
208
- if (cmd === '/cost') {
209
- const totalCost = ctx.llm.getTotalCost();
210
- console.log(` Total cost: ${chalk_1.default.green(formatCost(totalCost))}`);
211
- return;
590
+ if (cmdLower === "/workspace") {
591
+ logLine(chalk_1.default.dim(`\n Workspace: ${ctx.workspacePath || "default"}`));
592
+ handled = true;
212
593
  }
213
- if (cmd === '/compact') {
214
- const result = await agent.compact();
215
- console.log(chalk_1.default.green(` ✓ ${result}`));
216
- return;
594
+ if (cmdLower === "/mcp") {
595
+ logLine(chalk_1.default.dim(`\n MCP servers: ${ctx.mcpStatus?.length ? ctx.mcpStatus.join(", ") : "none configured"}`));
596
+ handled = true;
217
597
  }
218
- if (cmd === '/version') {
219
- console.log(` Skyloom v${VERSION}`);
220
- return;
598
+ if (cmdLower === "/sessions") {
599
+ const sessions = await agent.memory.listSessions();
600
+ if (sessions.length) {
601
+ logLine(chalk_1.default.bold("\n Sessions"));
602
+ for (const s of sessions.slice(0, 10)) {
603
+ logLine(chalk_1.default.dim(` ${s.id?.slice(0, 10)}... ${s.preview || ""} (${s.messageCount || 0} msgs)`));
604
+ }
605
+ }
606
+ else {
607
+ logLine(chalk_1.default.dim(" No saved sessions"));
608
+ }
609
+ handled = true;
221
610
  }
222
- if (cmd.startsWith('/model')) {
223
- console.log(chalk_1.default.dim(' Model management: use config.yaml to change models'));
224
- return;
611
+ if (cmdLower.startsWith("/model")) {
612
+ logLine(chalk_1.default.dim(` Model: ${modelOverride || "default"}. Configure in ~/.skyloom/config.yaml`));
613
+ handled = true;
225
614
  }
226
- if (cmd.startsWith('/task ')) {
615
+ if (cmdLower.startsWith("/task ")) {
227
616
  const goal = cmd.slice(6).trim();
228
617
  if (goal) {
229
- console.log(chalk_1.default.cyan(`\n Orchestrating: ${goal}\n`));
618
+ logLine(chalk_1.default.cyan(`\n Orchestrating: ${goal}\n`));
230
619
  await runTask(goal);
231
620
  }
232
- return;
621
+ handled = true;
233
622
  }
234
- if (cmd.startsWith('/')) {
235
- printHelp();
236
- return;
623
+ if (handled) {
624
+ logLine("");
625
+ continue;
237
626
  }
238
- // Save to history
239
- if (!inputHistory.includes(cmd)) {
240
- inputHistory.push(cmd);
241
- if (inputHistory.length > 50)
242
- inputHistory.shift();
243
- }
244
- // Classify and route
627
+ /* ── Route message ── */
245
628
  const mode = MODE.current;
246
629
  if (mode === mode_1.InteractiveMode.PLAN) {
247
- console.log(chalk_1.default.magenta('\n [PLAN mode] Routing to orchestrator...\n'));
248
630
  await runTask(cmd);
249
- return;
631
+ logLine("");
632
+ continue;
250
633
  }
251
634
  const cls = (0, router_1.classify)(cmd);
252
- if (cls === 'orchestrate' && mode !== mode_1.InteractiveMode.AUTO) {
635
+ if (cls === "orchestrate" && mode !== mode_1.InteractiveMode.AUTO) {
253
636
  await runTask(cmd);
254
- return;
637
+ logLine("");
638
+ continue;
255
639
  }
256
- // Single-agent chat
257
- process.stdout.write(`\n ${chalk_1.default.cyan(agent.displayName)} ${chalk_1.default.dim('thinking...')}\n`);
640
+ /* ── Chat ── */
258
641
  try {
259
- const response = await agent.chat(cmd);
260
- process.stdout.write('\n');
261
- console.log(chalk_1.default.white(response));
262
- }
263
- catch (e) {
264
- console.error(chalk_1.default.red(`\n Error: ${e}`));
265
- }
266
- // AUTO mode: continue if model signals more work
267
- if (mode === mode_1.InteractiveMode.AUTO) {
268
- // Simple auto-continue check
269
- const lastMsg = agent.memory.shortTerm[agent.memory.shortTerm.length - 1];
270
- if (lastMsg && lastMsg.content && shouldAutoContinue(lastMsg.content)) {
271
- process.stdout.write(chalk_1.default.yellow('\n [auto-continue]\n'));
272
- // Re-trigger
273
- try {
274
- const response = await agent.chat('请继续完成');
275
- process.stdout.write('\n');
276
- console.log(chalk_1.default.white(response));
277
- }
278
- catch (e) {
279
- console.error(chalk_1.default.red(`\n Error: ${e}`));
642
+ const response = await chatWithSpinner(agent, ctx, cmd);
643
+ logLine("");
644
+ for (const line of renderResponse(response)) {
645
+ logLine(line);
646
+ }
647
+ logLine("");
648
+ // Auto-continue
649
+ if (mode === mode_1.InteractiveMode.AUTO) {
650
+ const tail = response.split("\n").slice(-6).join("\n");
651
+ if (/(?:接下来|下一步|继续|next|let me|I'[vl]l)/i.test(tail) && !/(?:完成了|全部完成|all done)/i.test(tail)) {
652
+ logLine(chalk_1.default.yellow(" [auto-continue]\n"));
653
+ try {
654
+ const r2 = await chatWithSpinner(agent, ctx, "请继续完成");
655
+ logLine("");
656
+ for (const line of renderResponse(r2))
657
+ logLine(line);
658
+ logLine("");
659
+ }
660
+ catch { /* ignore */ }
280
661
  }
281
662
  }
282
663
  }
283
- };
284
- rl.on('line', async (line) => {
285
- try {
286
- await processInput(line);
287
- }
288
664
  catch (e) {
289
- console.error(chalk_1.default.red(`Error: ${e}`));
665
+ logLine(chalk_1.default.red(`\n ✗ Error: ${e.message || e}\n`));
290
666
  }
291
- rl.prompt();
292
- });
293
- rl.on('close', () => {
294
- console.log(chalk_1.default.dim('\n Session ended'));
295
- ctx.closeAll().catch(() => { });
296
- process.exit(0);
297
- });
298
- rl.prompt();
667
+ }
668
+ logLine(chalk_1.default.dim("\n Session ended"));
669
+ await ctx.closeAll();
670
+ process.exit(0);
299
671
  }
672
+ /* ═══════════════════════════════════════
673
+ Task execution
674
+ ═══════════════════════════════════════ */
300
675
  async function runTask(goal, resume) {
301
676
  const ctx = (0, factory_1.createSystemContext)();
302
677
  await ctx.initAll();
303
- const [_tasks, results, summary] = await (0, factory_1.orchestrateTask)(goal, ctx.agentMap, null, {
304
- resultTruncate: 500,
305
- maxTaskRetries: 3,
306
- maxReplanRounds: 1,
307
- resume,
678
+ const [, results, summary] = await (0, factory_1.orchestrateTask)(goal, ctx.agentMap, null, {
679
+ resultTruncate: 500, maxTaskRetries: 3, maxReplanRounds: 1, resume,
308
680
  });
309
- console.log(chalk_1.default.bold('\n Task Results'));
310
- console.log(chalk_1.default.dim(''.repeat(30)));
681
+ logLine(chalk_1.default.bold("\n Task Results"));
682
+ logLine(chalk_1.default.dim("".repeat(30)));
311
683
  for (const r of results) {
312
- const status = r.success ? chalk_1.default.green('') : chalk_1.default.red('');
313
- console.log(` ${status} ${chalk_1.default.cyan(r.agent)}: ${r.description.slice(0, 60)}...`);
684
+ logLine(` ${r.success ? chalk_1.default.green("") : chalk_1.default.red("")} ${chalk_1.default.cyan(r.agent)}: ${r.description.slice(0, 60)}`);
314
685
  }
315
- console.log(chalk_1.default.bold('\n Summary'));
316
- console.log(chalk_1.default.dim(''.repeat(30)));
317
- console.log(` ${summary.slice(0, 1000)}`);
318
- console.log();
686
+ logLine(chalk_1.default.bold("\n Summary"));
687
+ logLine(chalk_1.default.dim("".repeat(30)));
688
+ logLine(` ${summary.slice(0, 1000)}`);
689
+ logLine("");
319
690
  await ctx.closeAll();
320
691
  }
321
692
  function printHelp() {
322
- console.log(chalk_1.default.bold('\n Commands'));
323
- console.log(chalk_1.default.dim(''.repeat(30)));
324
- const cmds = [
325
- ['/help', 'Show this help'],
326
- ['/clear', 'Clear screen'],
327
- ['/status', 'Agent status'],
328
- ['/cost', 'Usage & cost'],
329
- ['/compact', 'Compress context'],
330
- ['/version', 'Version info'],
331
- ['/task <goal>', 'Multi-agent task'],
332
- ['/quit', 'Exit chat'],
333
- ['', ''],
334
- ['Switch agents:', ''],
335
- ['/fog', 'Fog — research'],
336
- ['/rain', 'Rain — codegen'],
337
- ['/frost', 'Frost — review'],
338
- ['/snow', 'Snow — planning'],
339
- ['/dew', 'Dew — devops'],
340
- ['/fair', 'Fair — companion'],
693
+ logLine(chalk_1.default.bold("\n Slash Commands"));
694
+ logLine(chalk_1.default.dim("".repeat(40)));
695
+ const groups = [
696
+ ["Agent", [["/fog /rain /frost", "Switch agents"], ["/snow /dew /fair", "Switch agents"]]],
697
+ ["Chat", [["/help", "Show commands"], ["/clear", "Clear screen"], ["/compact", "Compress context"], ["/retry", "Resend last msg"], ["/quit", "Exit"]]],
698
+ ["Info", [["/status", "Agent status"], ["/cost", "Usage & cost"], ["/memory", "Memory stats"], ["/sessions", "Session list"], ["/workspace", "Workspace info"], ["/version", "Version"]]],
699
+ ["Orch.", [["/task <goal>", "Multi-agent task"]]],
341
700
  ];
342
- for (const [cmd, desc] of cmds) {
343
- if (cmd) {
344
- console.log(` ${chalk_1.default.cyan(cmd.padEnd(20))}${chalk_1.default.dim(desc)}`);
345
- }
346
- else {
347
- console.log();
348
- }
701
+ for (const [title, cmds] of groups) {
702
+ logLine(chalk_1.default.cyan(` ${title}`));
703
+ for (const [c, d] of cmds)
704
+ logLine(` ${chalk_1.default.cyan(c.padEnd(18))}${chalk_1.default.dim(d)}`);
349
705
  }
350
- console.log();
351
- }
352
- function getAgentColor(name) {
353
- const colors = {
354
- fog: 'bright_white', rain: 'blue', frost: 'cyan',
355
- snow: 'bright_white', dew: 'green', fair: '#FFD700',
356
- };
357
- return colors[name] || 'white';
358
- }
359
- function formatCost(cost) {
360
- if (cost >= 1.0)
361
- return `$${cost.toFixed(2)}`;
362
- if (cost >= 0.01)
363
- return `$${cost.toFixed(4)}`;
364
- if (cost > 0.0)
365
- return `${(cost * 100).toFixed(2)}¢`;
366
- return '$0';
367
- }
368
- function shouldAutoContinue(text) {
369
- const autoContinuePattern = /(?:接下来|下一步|下面我|然后我|接着|继续|next|let me\s|I'[vl]l\s)/i;
370
- const autoStopPattern = /(?:完成了|全部完成|以上就|all done|task complete)/i;
371
- const tail = text.split('\n').slice(-6).join('\n');
372
- if (autoStopPattern.test(tail))
373
- return false;
374
- return autoContinuePattern.test(tail);
706
+ logLine("");
375
707
  }
376
- // ── Parse CLI args and run ──
708
+ /* ═══════════════════════════════════════
709
+ Entry
710
+ ═══════════════════════════════════════ */
377
711
  async function main() {
378
712
  const args = process.argv.slice(2);
379
- // If no args or just "chat", start interactive chat
380
- if (args.length === 0 || args[0] === 'chat' || (args.length === 1 && !args[0].startsWith('-') && !['task', 'web', 'config', 'init', 'version'].includes(args[0]))) {
381
- // Check if first arg is an agent name
382
- const knownAgents = new Set(['fog', 'rain', 'frost', 'snow', 'dew', 'fair']);
383
- let agent = 'fog';
384
- let model;
385
- for (let i = 0; i < args.length; i++) {
386
- if (knownAgents.has(args[i])) {
387
- agent = args[i];
388
- }
389
- else if (args[i] === '-m' || args[i] === '--model') {
390
- model = args[++i];
391
- }
713
+ if (args.length === 0) {
714
+ await chat("fog");
715
+ return;
716
+ }
717
+ if (AGENT_NAMES.includes(args[0])) {
718
+ let m;
719
+ for (let i = 1; i < args.length; i++) {
720
+ if ((args[i] === "-m" || args[i] === "--model") && i + 1 < args.length)
721
+ m = args[++i];
392
722
  }
393
- await interactiveChat(agent, model);
723
+ await chat(args[0], m);
724
+ return;
725
+ }
726
+ const subCmds = ["chat", "task", "web", "config", "init", "version", "mcp", "help"];
727
+ if (!subCmds.includes(args[0]) && !args[0].startsWith("-")) {
728
+ await chat("fog");
394
729
  return;
395
730
  }
396
731
  program.parse(process.argv);
397
732
  }
398
- main().catch((e) => {
399
- console.error(chalk_1.default.red(`Fatal error: ${e}`));
400
- process.exit(1);
401
- });
733
+ main().catch(e => { logLine(chalk_1.default.red(`Fatal: ${e.message}`)); process.exit(1); });
402
734
  //# sourceMappingURL=main.js.map