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