skyloom 1.5.0 → 1.5.2

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
@@ -39,7 +39,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  /**
41
41
  * 天空织机 CLI — Skyloom Terminal Interface
42
- * Raw-mode input + slash command popup + streaming display
43
42
  */
44
43
  const commander_1 = require("commander");
45
44
  const fs = __importStar(require("fs"));
@@ -47,663 +46,292 @@ const readline = __importStar(require("readline"));
47
46
  const chalk_1 = __importDefault(require("chalk"));
48
47
  const factory_1 = require("../core/factory");
49
48
  const config_1 = require("../core/config");
50
- const router_1 = require("../core/router");
51
49
  const mode_1 = require("./mode");
52
50
  const MODE = new mode_1.ModeController();
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
- };
51
+ const VERSION = "1.4.4";
59
52
  const AGENT_DISPLAY = {
60
53
  fog: "≋ 雾 Fog", rain: "⸽ 雨 Rain", frost: "✱ 霜 Frost",
61
54
  snow: "❉ 雪 Snow", dew: "∘ 露 Dew", fair: "☼ 晴 Fair",
62
55
  };
63
56
  const AGENT_NAMES = ["fog", "rain", "frost", "snow", "dew", "fair"];
64
57
  /* ═══════════════════════════════════════
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
58
+ Slash commands registry
93
59
  ═══════════════════════════════════════ */
94
60
  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, ""],
61
+ ["/help", "Show all commands"],
62
+ ["/clear", "Clear screen"],
63
+ ["/status", "Agent overview"],
64
+ ["/cost", "Usage & cost"],
65
+ ["/cost reset", "Reset usage stats"],
66
+ ["/compact", "Compress context"],
67
+ ["/retry", "Resend last msg"],
68
+ ["/memory", "Memory stats"],
69
+ ["/memory clear", "Clear short-term memory"],
70
+ ["/sessions", "Session list"],
71
+ ["/workspace", "Workspace info"],
72
+ ["/model", "Model info"],
73
+ ["/mcp", "MCP server status"],
74
+ ["/version", "Version info"],
75
+ ["/task <goal>", "Multi-agent orchestrate"],
76
+ ["/fog", "≋ Fog — research insight"],
77
+ ["/rain", "⸽ Rain — creation codegen"],
78
+ ["/frost", "✱ Frost — review quality"],
79
+ ["/snow", "❉ Snow — planning architect"],
80
+ ["/dew", "∘ Dew — devops reliability"],
81
+ ["/fair", "☼ Fair — companion warmth"],
82
+ ["/quit", "Exit chat"],
83
+ ["/exit", "Exit chat"],
118
84
  ];
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);
129
- try {
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;
135
- }
136
- catch (e) {
137
- clearInterval(spinner);
138
- throw e;
139
- }
140
- }
141
- /* ── Render response ── */
142
- function renderResponse(text) {
143
- const lines = [];
85
+ function showPopup(cmds, selIdx) {
144
86
  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
- }
87
+ const start = Math.max(0, Math.min(selIdx - 4, cmds.length - 8));
88
+ const end = Math.min(cmds.length, start + 8);
89
+ process.stdout.write(chalk_1.default.dim(" ┌─ commands (↑↓ pick · type letter to filter · tab/enter select) ─┐\n"));
90
+ for (let i = start; i < end; i++) {
91
+ const [cmd, desc] = cmds[i];
92
+ const marker = i === selIdx ? chalk_1.default.cyan(" ▶ ") : " ";
93
+ process.stdout.write(` │${marker}${chalk_1.default.cyan(cmd.padEnd(24))}${chalk_1.default.dim(desc)}${" ".repeat(Math.max(0, 50 - desc.length))}│\n`);
181
94
  }
182
- return lines;
95
+ process.stdout.write(chalk_1.default.dim(` └${"─".repeat(60)}┘\n`));
183
96
  }
184
- /* ── Welcome banner ── */
185
- function printWelcome(agent, model) {
97
+ /* ═══════════════════════════════════════
98
+ Commander
99
+ ═══════════════════════════════════════ */
100
+ const program = new commander_1.Command()
101
+ .name("sky").description("天空织机 Skyloom").version(VERSION);
102
+ program.command("chat").argument("[agent]", "agent name", "fog")
103
+ .option("-m,--model <m>", "model").action(async (a, o) => { await chat(a, o.model); });
104
+ program.command("task").argument("[goal]", "task goal")
105
+ .action(async (g) => { if (g)
106
+ await runTask(g); });
107
+ program.command("web").option("-p,--port <p>", "port", "3000")
108
+ .action((o) => { Promise.resolve().then(() => __importStar(require("../web/server"))).then(m => m.startWebServer(parseInt(o.port || "3000"))); });
109
+ program.command("mcp").action(() => { Promise.resolve().then(() => __importStar(require("../core/mcp_server"))).then(m => m.startMCPServer()); });
110
+ program.command("config").action(() => { const c = (0, config_1.loadConfig)(); process.stdout.write(chalk_1.default.cyan("\nConfig: ") + config_1.USER_CONFIG_DIR + "\n"); for (const [n, a] of Object.entries(c.agents || {}))
111
+ process.stdout.write(` ${chalk_1.default.bold(n)}: ${a.model || "default"}\n`); });
112
+ program.command("init").action(() => { if (!fs.existsSync(config_1.USER_CONFIG_DIR))
113
+ fs.mkdirSync(config_1.USER_CONFIG_DIR, { recursive: true }); process.stdout.write(chalk_1.default.green("✓ ") + config_1.USER_CONFIG_DIR + "\n"); });
114
+ program.command("version").action(() => { process.stdout.write(`Skyloom v${VERSION}\n`); });
115
+ /* ═══════════════════════════════════════
116
+ Welcome
117
+ ═══════════════════════════════════════ */
118
+ function welcome(agent) {
186
119
  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);
120
+ const pad = " ".repeat(Math.max(0, Math.floor((w - 34) / 2)));
121
+ process.stdout.write("\n" + pad + chalk_1.default.cyan("✦ 天 空 织 机 ✦\n"));
122
+ process.stdout.write(pad + chalk_1.default.dim("S K Y L O O M\n\n"));
123
+ const parts = [];
124
+ for (const n of AGENT_NAMES) {
125
+ const a = n === agent.name;
126
+ const s = `${AGENT_DISPLAY[n].split(" ")[0]} ${AGENT_DISPLAY[n].split(" ")[1]}`;
127
+ parts.push(a ? chalk_1.default.bold.cyan(s) : chalk_1.default.dim(s));
196
128
  }
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("");
129
+ process.stdout.write(" " + parts.join(chalk_1.default.dim(" · ")) + "\n\n");
130
+ process.stdout.write(chalk_1.default.dim(" /help for commands · /quit to exit\n\n"));
201
131
  }
202
- /* ── Status bar ── */
203
132
  function statusBar(agent, ctx) {
204
- let ctxStr = "";
205
- let costStr = "$0";
206
- let modelStr = "default";
207
133
  try {
208
134
  const cu = agent.contextUsage();
209
- modelStr = cu.model || "?";
210
135
  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());
136
+ const bar = pct < 50 ? chalk_1.default.green : pct < 80 ? chalk_1.default.yellow : chalk_1.default.red;
137
+ const filled = Math.round(pct / 10);
138
+ const ctxBar = `${bar("█".repeat(filled) + "░".repeat(10 - filled))} ${pct}%`;
139
+ const cost = formatCost(ctx.llm.getTotalCost());
140
+ return chalk_1.default.dim(`${ctxBar} · ${cost} · ${cu.model || "?"}`);
141
+ }
142
+ catch {
143
+ return "";
215
144
  }
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
145
  }
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)}¢`);
146
+ function formatCost(c) {
147
+ if (c >= 1)
148
+ return chalk_1.default.yellow(`$${c.toFixed(2)}`);
149
+ if (c >= 0.01)
150
+ return chalk_1.default.yellow(`$${c.toFixed(4)}`);
151
+ if (c > 0)
152
+ return chalk_1.default.green(`${(c * 100).toFixed(2)}¢`);
227
153
  return "$0";
228
154
  }
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
- });
236
- }
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"));
155
+ /* ═══════════════════════════════════════
156
+ Response render
157
+ ═══════════════════════════════════════ */
158
+ function render(text) {
159
+ const out = [];
160
+ for (const para of text.split("\n\n")) {
161
+ const t = para.trim();
162
+ if (!t)
163
+ continue;
164
+ if (t.startsWith("```")) {
165
+ const lines = t.split("\n");
166
+ out.push(chalk_1.default.dim(" ╭─ code ──"));
167
+ for (let i = 1; i < lines.length - 1; i++)
168
+ out.push(` ${chalk_1.default.dim("│")} ${chalk_1.default.gray(lines[i].slice(0, 72))}`);
169
+ out.push(chalk_1.default.dim(" ╰────────"));
291
170
  }
292
- function accept(line) {
293
- try {
294
- stdin.setRawMode?.(false);
171
+ else {
172
+ for (const line of t.split("\n")) {
173
+ if (line.startsWith("# "))
174
+ out.push(" " + chalk_1.default.bold(line));
175
+ else if (line.startsWith("- ") || line.startsWith("* "))
176
+ out.push(" " + chalk_1.default.dim("• ") + line.slice(2));
177
+ else
178
+ out.push(" " + line);
295
179
  }
296
- catch { }
297
- stdin.pause();
298
- resolve(line);
299
180
  }
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
- });
181
+ }
182
+ return out;
497
183
  }
498
184
  /* ═══════════════════════════════════════
499
- Main chat loop
185
+ Chat loop
500
186
  ═══════════════════════════════════════ */
187
+ /* Check for API key availability */
188
+ function checkApiKeys() {
189
+ const keys = ["DEEPSEEK_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GROQ_API_KEY", "OPENROUTER_API_KEY"];
190
+ for (const k of keys) {
191
+ if (process.env[k])
192
+ return k;
193
+ }
194
+ return null;
195
+ }
501
196
  async function chat(agentName, modelOverride) {
197
+ const haveKey = checkApiKeys();
198
+ if (!haveKey) {
199
+ process.stdout.write("\n" + chalk_1.default.yellow(" ⚠ No API key configured.\n"));
200
+ process.stdout.write(chalk_1.default.dim(" Set one: $env:DEEPSEEK_API_KEY = \"sk-your-key\" (PowerShell)\n"));
201
+ process.stdout.write(chalk_1.default.dim(" export DEEPSEEK_API_KEY=sk-your-key (Bash)\n\n"));
202
+ process.stdout.write(chalk_1.default.dim(" Then run: sky\n\n"));
203
+ process.exit(1);
204
+ }
502
205
  const ctx = (0, factory_1.createSystemContext)();
503
206
  let agent = ctx.agentMap.get(agentName);
504
207
  if (!agent) {
505
- logLine(chalk_1.default.red(`Unknown agent: ${agentName}`));
208
+ process.stdout.write(chalk_1.default.red("Unknown agent: " + agentName) + "\n");
506
209
  return;
507
210
  }
508
211
  await agent.init();
509
- const model = modelOverride || "default";
510
- printWelcome(agent, model);
511
- const inputHistory = [];
512
- while (true) {
513
- /* ── Read input ── */
514
- const inp = await readWithPopup(agent, ctx);
212
+ // eslint-disable-next-line prefer-const
213
+ let currentAgent = agent; // mutable for agent switching
214
+ welcome(agent);
215
+ process.stdout.write(chalk_1.default.dim(" Key: " + haveKey + "\n\n"));
216
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
217
+ function ask() { rl.question(chalk_1.default.cyan(" " + currentAgent.displayName + " ❯ "), handler); }
218
+ async function handler(inp) {
219
+ inp = inp.trim();
515
220
  if (!inp) {
516
- logLine("");
517
- continue;
518
- }
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();
221
+ ask();
222
+ return;
524
223
  }
525
- readWithPopup._popupHistory = inputHistory;
526
- const cmd = inp.trim();
527
- const cmdLower = cmd.toLowerCase();
528
- /* ── Slash commands ── */
529
- let handled = false;
530
- // Agent switching
224
+ const cmdL = inp.toLowerCase();
225
+ // Agent switch
531
226
  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;
227
+ if (cmdL === "/" + n) {
228
+ const a = ctx.agentMap.get(n);
229
+ if (a) {
230
+ await a.init();
231
+ currentAgent = a;
543
232
  }
544
- handled = true;
545
- break;
233
+ process.stdout.write(chalk_1.default.dim(" ⟳ " + AGENT_DISPLAY[n] + "\n"));
234
+ ask();
235
+ return;
546
236
  }
547
237
  }
548
- if (cmdLower === "/quit" || cmdLower === "/exit")
549
- break;
550
- if (cmdLower === "/help") {
551
- printHelp();
552
- handled = true;
553
- }
554
- if (cmdLower === "/clear") {
555
- console.clear();
556
- handled = true;
557
- }
558
- if (cmdLower === "/version") {
559
- logLine(` Skyloom v${VERSION}`);
560
- handled = true;
238
+ if (cmdL === "/quit" || cmdL === "/exit") {
239
+ process.stdout.write(chalk_1.default.dim("\n Session ended\n"));
240
+ rl.close();
241
+ await ctx.closeAll();
242
+ process.exit(0);
243
+ return;
561
244
  }
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;
245
+ if (cmdL === "/help") {
246
+ process.stdout.write(helpText());
247
+ ask();
248
+ return;
567
249
  }
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;
250
+ if (cmdL === "/clear") {
251
+ console.clear();
252
+ welcome(agent);
253
+ process.stdout.write(chalk_1.default.dim(" Key: " + haveKey + "\n\n"));
254
+ ask();
255
+ return;
589
256
  }
590
- if (cmdLower === "/workspace") {
591
- logLine(chalk_1.default.dim(`\n Workspace: ${ctx.workspacePath || "default"}`));
592
- handled = true;
257
+ if (cmdL === "/status") {
258
+ process.stdout.write(chalk_1.default.bold("\n " + currentAgent.displayName + " (" + currentAgent.name + ")\n") + chalk_1.default.dim(" State: " + currentAgent.state + " · Memory: " + currentAgent.memory.shortTerm.length + " msgs\n\n"));
259
+ ask();
260
+ return;
593
261
  }
594
- if (cmdLower === "/mcp") {
595
- logLine(chalk_1.default.dim(`\n MCP servers: ${ctx.mcpStatus?.length ? ctx.mcpStatus.join(", ") : "none configured"}`));
596
- handled = true;
262
+ if (cmdL === "/cost") {
263
+ process.stdout.write(chalk_1.default.bold("\n Total: " + formatCost(ctx.llm.getTotalCost()) + "\n\n"));
264
+ ask();
265
+ return;
597
266
  }
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;
267
+ if (cmdL === "/compact") {
268
+ const r = await currentAgent.compact();
269
+ process.stdout.write(chalk_1.default.green(" ✓ " + r + "\n\n"));
270
+ ask();
271
+ return;
610
272
  }
611
- if (cmdLower.startsWith("/model")) {
612
- logLine(chalk_1.default.dim(` Model: ${modelOverride || "default"}. Configure in ~/.skyloom/config.yaml`));
613
- handled = true;
273
+ if (cmdL === "/version") {
274
+ process.stdout.write(" Skyloom v" + VERSION + "\n");
275
+ ask();
276
+ return;
614
277
  }
615
- if (cmdLower.startsWith("/task ")) {
616
- const goal = cmd.slice(6).trim();
617
- if (goal) {
618
- logLine(chalk_1.default.cyan(`\n ✦ Orchestrating: ${goal}\n`));
619
- await runTask(goal);
620
- }
621
- handled = true;
278
+ if (cmdL.startsWith("/task ")) {
279
+ const g = inp.slice(6);
280
+ process.stdout.write(chalk_1.default.cyan("\n ✦ " + g + "\n\n"));
281
+ await runTask(g);
282
+ ask();
283
+ return;
622
284
  }
623
- if (handled) {
624
- logLine("");
625
- continue;
285
+ if (inp.startsWith("/")) {
286
+ process.stdout.write(helpText());
287
+ ask();
288
+ return;
626
289
  }
627
- /* ── Route message ── */
628
- const mode = MODE.current;
629
- if (mode === mode_1.InteractiveMode.PLAN) {
630
- await runTask(cmd);
631
- logLine("");
632
- continue;
633
- }
634
- const cls = (0, router_1.classify)(cmd);
635
- if (cls === "orchestrate" && mode !== mode_1.InteractiveMode.AUTO) {
636
- await runTask(cmd);
637
- logLine("");
638
- continue;
639
- }
640
- /* ── Chat ── */
290
+ // ── Chat ──
291
+ process.stdout.write(chalk_1.default.dim(" " + currentAgent.displayName + " thinking...\r"));
641
292
  try {
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 */ }
661
- }
662
- }
293
+ const response = await currentAgent.chat(inp);
294
+ process.stdout.write("\r" + " ".repeat(40) + "\r\n");
295
+ const lines = render(response);
296
+ for (const l of lines)
297
+ process.stdout.write(l + "\n");
298
+ process.stdout.write("\n");
663
299
  }
664
300
  catch (e) {
665
- logLine(chalk_1.default.red(`\n ✗ Error: ${e.message || e}\n`));
301
+ process.stdout.write("\r" + " ".repeat(40) + "\r");
302
+ process.stdout.write(chalk_1.default.red(" ✗ " + (e.message || e) + "\n\n"));
666
303
  }
304
+ ask();
667
305
  }
668
- logLine(chalk_1.default.dim("\n Session ended"));
669
- await ctx.closeAll();
670
- process.exit(0);
306
+ ask();
671
307
  }
672
308
  /* ═══════════════════════════════════════
673
- Task execution
309
+ Task
674
310
  ═══════════════════════════════════════ */
675
- async function runTask(goal, resume) {
311
+ async function runTask(goal) {
676
312
  const ctx = (0, factory_1.createSystemContext)();
677
313
  await ctx.initAll();
678
- const [, results, summary] = await (0, factory_1.orchestrateTask)(goal, ctx.agentMap, null, {
679
- resultTruncate: 500, maxTaskRetries: 3, maxReplanRounds: 1, resume,
680
- });
681
- logLine(chalk_1.default.bold("\n Task Results"));
682
- logLine(chalk_1.default.dim(" ─".repeat(30)));
683
- for (const r of results) {
684
- logLine(` ${r.success ? chalk_1.default.green("✓") : chalk_1.default.red("✗")} ${chalk_1.default.cyan(r.agent)}: ${r.description.slice(0, 60)}`);
685
- }
686
- logLine(chalk_1.default.bold("\n Summary"));
687
- logLine(chalk_1.default.dim(" ─".repeat(30)));
688
- logLine(` ${summary.slice(0, 1000)}`);
689
- logLine("");
314
+ const [, results, summary] = await (0, factory_1.orchestrateTask)(goal, ctx.agentMap);
315
+ for (const r of results)
316
+ process.stdout.write(` ${r.success ? chalk_1.default.green("✓") : chalk_1.default.red("✗")} ${chalk_1.default.cyan(r.agent)}: ${r.description.slice(0, 60)}\n`);
317
+ process.stdout.write(chalk_1.default.bold("\n " + summary.slice(0, 800) + "\n\n"));
690
318
  await ctx.closeAll();
691
319
  }
692
- function printHelp() {
693
- logLine(chalk_1.default.bold("\n Slash Commands"));
694
- logLine(chalk_1.default.dim(" ─".repeat(40)));
320
+ function helpText() {
695
321
  const groups = [
696
322
  ["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"]]],
323
+ ["Chat", [["/help", "Commands"], ["/clear", "Clear"], ["/compact", "Compress"], ["/retry", "Resend"]]],
324
+ ["Info", [["/status", "Status"], ["/cost", "Cost"], ["/memory", "Memory"], ["/sessions", "Sessions"], ["/workspace", "Workspace"], ["/version", "Version"]]],
325
+ ["Orch.", [["/task <goal>", "Multi-agent"]]],
700
326
  ];
327
+ let s = "";
701
328
  for (const [title, cmds] of groups) {
702
- logLine(chalk_1.default.cyan(` ${title}`));
329
+ s += chalk_1.default.cyan(` ${title}\n`);
703
330
  for (const [c, d] of cmds)
704
- logLine(` ${chalk_1.default.cyan(c.padEnd(18))}${chalk_1.default.dim(d)}`);
331
+ s += ` ${chalk_1.default.cyan(c.padEnd(18))}${chalk_1.default.dim(d)}\n`;
705
332
  }
706
- logLine("");
333
+ s += "\n";
334
+ return s;
707
335
  }
708
336
  /* ═══════════════════════════════════════
709
337
  Entry
@@ -716,19 +344,17 @@ async function main() {
716
344
  }
717
345
  if (AGENT_NAMES.includes(args[0])) {
718
346
  let m;
719
- for (let i = 1; i < args.length; i++) {
347
+ for (let i = 1; i < args.length; i++)
720
348
  if ((args[i] === "-m" || args[i] === "--model") && i + 1 < args.length)
721
349
  m = args[++i];
722
- }
723
350
  await chat(args[0], m);
724
351
  return;
725
352
  }
726
- const subCmds = ["chat", "task", "web", "config", "init", "version", "mcp", "help"];
727
- if (!subCmds.includes(args[0]) && !args[0].startsWith("-")) {
353
+ if (!["chat", "task", "web", "config", "init", "version", "mcp", "help"].includes(args[0]) && !args[0].startsWith("-")) {
728
354
  await chat("fog");
729
355
  return;
730
356
  }
731
357
  program.parse(process.argv);
732
358
  }
733
- main().catch(e => { logLine(chalk_1.default.red(`Fatal: ${e.message}`)); process.exit(1); });
359
+ main().catch(e => { process.stderr.write(chalk_1.default.red(`Fatal: ${e.message}\n`)); process.exit(1); });
734
360
  //# sourceMappingURL=main.js.map