u-foo 1.0.2 → 1.0.3
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/package.json +1 -1
- package/src/chat/index.js +154 -15
- package/src/config.js +14 -2
- package/src/daemon/run.js +4 -2
package/package.json
CHANGED
package/src/chat/index.js
CHANGED
|
@@ -3,7 +3,7 @@ const path = require("path");
|
|
|
3
3
|
const blessed = require("blessed");
|
|
4
4
|
const { spawn, spawnSync } = require("child_process");
|
|
5
5
|
const fs = require("fs");
|
|
6
|
-
const { loadConfig, saveConfig, normalizeLaunchMode } = require("../config");
|
|
6
|
+
const { loadConfig, saveConfig, normalizeLaunchMode, normalizeAgentProvider } = require("../config");
|
|
7
7
|
const { socketPath, isRunning } = require("../daemon");
|
|
8
8
|
|
|
9
9
|
function connectSocket(sockPath) {
|
|
@@ -29,6 +29,14 @@ function startDaemon(projectRoot) {
|
|
|
29
29
|
child.unref();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function stopDaemon(projectRoot) {
|
|
33
|
+
const daemonBin = resolveProjectFile(projectRoot, path.join("bin", "ufoo.js"), path.join("bin", "ufoo.js"));
|
|
34
|
+
spawnSync(process.execPath, [daemonBin, "daemon", "--stop"], {
|
|
35
|
+
stdio: "ignore",
|
|
36
|
+
cwd: projectRoot,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
async function connectWithRetry(sockPath, retries, delayMs) {
|
|
33
41
|
for (let i = 0; i < retries; i += 1) {
|
|
34
42
|
try {
|
|
@@ -54,15 +62,23 @@ async function runChat(projectRoot) {
|
|
|
54
62
|
startDaemon(projectRoot);
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
const daemonBin = resolveProjectFile(projectRoot, path.join("bin", "ufoo.js"), path.join("bin", "ufoo.js"));
|
|
57
66
|
const sock = socketPath(projectRoot);
|
|
58
|
-
let client =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
let client = null;
|
|
68
|
+
|
|
69
|
+
const connectClient = async () => {
|
|
70
|
+
let newClient = await connectWithRetry(sock, 25, 200);
|
|
71
|
+
if (!newClient) {
|
|
72
|
+
// Retry once with a fresh daemon start and longer wait.
|
|
73
|
+
if (!isRunning(projectRoot)) {
|
|
74
|
+
startDaemon(projectRoot);
|
|
75
|
+
}
|
|
76
|
+
newClient = await connectWithRetry(sock, 50, 200);
|
|
63
77
|
}
|
|
64
|
-
|
|
65
|
-
}
|
|
78
|
+
return newClient;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
client = await connectClient();
|
|
66
82
|
if (!client) {
|
|
67
83
|
// Check if daemon failed to start
|
|
68
84
|
if (!isRunning(projectRoot)) {
|
|
@@ -88,6 +104,7 @@ async function runChat(projectRoot) {
|
|
|
88
104
|
|
|
89
105
|
const config = loadConfig(projectRoot);
|
|
90
106
|
let launchMode = config.launchMode;
|
|
107
|
+
let agentProvider = config.agentProvider;
|
|
91
108
|
|
|
92
109
|
// Dynamic input height settings
|
|
93
110
|
// Layout: topLine(1) + content + bottomLine(1) + dashboard(1)
|
|
@@ -1107,6 +1124,12 @@ async function runChat(projectRoot) {
|
|
|
1107
1124
|
let focusMode = "input"; // "input" or "dashboard"
|
|
1108
1125
|
let dashboardView = "agents"; // "agents" or "mode"
|
|
1109
1126
|
let selectedModeIndex = launchMode === "internal" ? 1 : 0;
|
|
1127
|
+
const providerOptions = [
|
|
1128
|
+
{ label: "codex", value: "codex-cli" },
|
|
1129
|
+
{ label: "claude", value: "claude-cli" },
|
|
1130
|
+
];
|
|
1131
|
+
let selectedProviderIndex = agentProvider === "claude-cli" ? 1 : 0;
|
|
1132
|
+
let restartInProgress = false;
|
|
1110
1133
|
|
|
1111
1134
|
function getAgentLabel(agentId) {
|
|
1112
1135
|
return activeAgentLabelMap.get(agentId) || agentId;
|
|
@@ -1131,6 +1154,7 @@ async function runChat(projectRoot) {
|
|
|
1131
1154
|
}
|
|
1132
1155
|
|
|
1133
1156
|
function send(req) {
|
|
1157
|
+
if (!client || client.destroyed) return;
|
|
1134
1158
|
client.write(`${JSON.stringify(req)}\n`);
|
|
1135
1159
|
}
|
|
1136
1160
|
|
|
@@ -1177,6 +1201,49 @@ async function runChat(projectRoot) {
|
|
|
1177
1201
|
screen.render();
|
|
1178
1202
|
}
|
|
1179
1203
|
|
|
1204
|
+
function providerLabel(value) {
|
|
1205
|
+
return value === "claude-cli" ? "claude" : "codex";
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function setAgentProvider(provider) {
|
|
1209
|
+
const next = normalizeAgentProvider(provider);
|
|
1210
|
+
if (next === agentProvider) return;
|
|
1211
|
+
agentProvider = next;
|
|
1212
|
+
selectedProviderIndex = agentProvider === "claude-cli" ? 1 : 0;
|
|
1213
|
+
saveConfig(projectRoot, { agentProvider });
|
|
1214
|
+
logMessage("status", `{magenta-fg}⚙{/magenta-fg} ufoo-agent: ${providerLabel(agentProvider)}`);
|
|
1215
|
+
renderDashboard();
|
|
1216
|
+
screen.render();
|
|
1217
|
+
void restartDaemon();
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
async function restartDaemon() {
|
|
1221
|
+
if (restartInProgress) return;
|
|
1222
|
+
restartInProgress = true;
|
|
1223
|
+
logMessage("status", "{magenta-fg}⚙{/magenta-fg} Restarting daemon...");
|
|
1224
|
+
try {
|
|
1225
|
+
if (client) {
|
|
1226
|
+
client.removeAllListeners();
|
|
1227
|
+
try {
|
|
1228
|
+
client.end();
|
|
1229
|
+
} catch {
|
|
1230
|
+
// ignore
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
stopDaemon(projectRoot);
|
|
1234
|
+
startDaemon(projectRoot);
|
|
1235
|
+
const newClient = await connectClient();
|
|
1236
|
+
if (newClient) {
|
|
1237
|
+
attachClient(newClient);
|
|
1238
|
+
logMessage("status", "{green-fg}✓{/green-fg} Daemon reconnected");
|
|
1239
|
+
} else {
|
|
1240
|
+
logMessage("error", "{red-fg}✗{/red-fg} Failed to reconnect to daemon");
|
|
1241
|
+
}
|
|
1242
|
+
} finally {
|
|
1243
|
+
restartInProgress = false;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1180
1247
|
function clearLog() {
|
|
1181
1248
|
logBox.setContent("");
|
|
1182
1249
|
if (typeof logBox.scrollTo === "function") {
|
|
@@ -1197,6 +1264,15 @@ async function runChat(projectRoot) {
|
|
|
1197
1264
|
return `{cyan-fg}${mode}{/cyan-fg}`;
|
|
1198
1265
|
});
|
|
1199
1266
|
content += `{gray-fg}Mode:{/gray-fg} ${modeParts.join(" ")}`;
|
|
1267
|
+
content += " {gray-fg}│ ←/→ select, Enter confirm, ↓ agent, ↑ back{/gray-fg}";
|
|
1268
|
+
} else if (dashboardView === "provider") {
|
|
1269
|
+
const providerParts = providerOptions.map((opt, i) => {
|
|
1270
|
+
if (i === selectedProviderIndex) {
|
|
1271
|
+
return `{inverse}${opt.label}{/inverse}`;
|
|
1272
|
+
}
|
|
1273
|
+
return `{cyan-fg}${opt.label}{/cyan-fg}`;
|
|
1274
|
+
});
|
|
1275
|
+
content += `{gray-fg}Agent:{/gray-fg} ${providerParts.join(" ")}`;
|
|
1200
1276
|
content += " {gray-fg}│ ←/→ select, Enter confirm, ↑ back{/gray-fg}";
|
|
1201
1277
|
} else {
|
|
1202
1278
|
if (activeAgents.length > 0) {
|
|
@@ -1230,6 +1306,7 @@ async function runChat(projectRoot) {
|
|
|
1230
1306
|
: "none";
|
|
1231
1307
|
content += `{gray-fg}Agents:{/gray-fg} {cyan-fg}${agents}{/cyan-fg}`;
|
|
1232
1308
|
content += ` {gray-fg}Mode:{/gray-fg} {cyan-fg}${launchMode}{/cyan-fg}`;
|
|
1309
|
+
content += ` {gray-fg}Agent:{/gray-fg} {cyan-fg}${providerLabel(agentProvider)}{/cyan-fg}`;
|
|
1233
1310
|
}
|
|
1234
1311
|
dashboard.setContent(content);
|
|
1235
1312
|
}
|
|
@@ -1280,6 +1357,7 @@ async function runChat(projectRoot) {
|
|
|
1280
1357
|
agentListWindowStart = 0;
|
|
1281
1358
|
clampAgentWindow();
|
|
1282
1359
|
selectedModeIndex = launchMode === "internal" ? 1 : 0;
|
|
1360
|
+
selectedProviderIndex = agentProvider === "claude-cli" ? 1 : 0;
|
|
1283
1361
|
screen.grabKeys = true;
|
|
1284
1362
|
renderDashboard();
|
|
1285
1363
|
screen.program.hideCursor();
|
|
@@ -1301,6 +1379,13 @@ async function runChat(projectRoot) {
|
|
|
1301
1379
|
screen.render();
|
|
1302
1380
|
return true;
|
|
1303
1381
|
}
|
|
1382
|
+
if (key.name === "down") {
|
|
1383
|
+
dashboardView = "provider";
|
|
1384
|
+
selectedProviderIndex = agentProvider === "claude-cli" ? 1 : 0;
|
|
1385
|
+
renderDashboard();
|
|
1386
|
+
screen.render();
|
|
1387
|
+
return true;
|
|
1388
|
+
}
|
|
1304
1389
|
if (key.name === "up") {
|
|
1305
1390
|
dashboardView = "agents";
|
|
1306
1391
|
renderDashboard();
|
|
@@ -1319,6 +1404,37 @@ async function runChat(projectRoot) {
|
|
|
1319
1404
|
}
|
|
1320
1405
|
return true;
|
|
1321
1406
|
}
|
|
1407
|
+
if (dashboardView === "provider") {
|
|
1408
|
+
if (key.name === "left") {
|
|
1409
|
+
selectedProviderIndex = selectedProviderIndex <= 0 ? providerOptions.length - 1 : selectedProviderIndex - 1;
|
|
1410
|
+
renderDashboard();
|
|
1411
|
+
screen.render();
|
|
1412
|
+
return true;
|
|
1413
|
+
}
|
|
1414
|
+
if (key.name === "right") {
|
|
1415
|
+
selectedProviderIndex = selectedProviderIndex >= providerOptions.length - 1 ? 0 : selectedProviderIndex + 1;
|
|
1416
|
+
renderDashboard();
|
|
1417
|
+
screen.render();
|
|
1418
|
+
return true;
|
|
1419
|
+
}
|
|
1420
|
+
if (key.name === "up") {
|
|
1421
|
+
dashboardView = "mode";
|
|
1422
|
+
renderDashboard();
|
|
1423
|
+
screen.render();
|
|
1424
|
+
return true;
|
|
1425
|
+
}
|
|
1426
|
+
if (key.name === "enter" || key.name === "return") {
|
|
1427
|
+
const selected = providerOptions[selectedProviderIndex];
|
|
1428
|
+
if (selected) setAgentProvider(selected.value);
|
|
1429
|
+
exitDashboardMode(false);
|
|
1430
|
+
return true;
|
|
1431
|
+
}
|
|
1432
|
+
if (key.name === "escape") {
|
|
1433
|
+
exitDashboardMode(false);
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
return true;
|
|
1437
|
+
}
|
|
1322
1438
|
|
|
1323
1439
|
if (key.name === "left") {
|
|
1324
1440
|
if (activeAgents.length > 0 && selectedAgentIndex > 0) {
|
|
@@ -1380,13 +1496,29 @@ async function runChat(projectRoot) {
|
|
|
1380
1496
|
send({ type: "status" });
|
|
1381
1497
|
}
|
|
1382
1498
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1499
|
+
const detachClient = () => {
|
|
1500
|
+
if (!client) return;
|
|
1501
|
+
client.removeAllListeners("data");
|
|
1502
|
+
client.removeAllListeners("close");
|
|
1503
|
+
try {
|
|
1504
|
+
client.end();
|
|
1505
|
+
client.destroy();
|
|
1506
|
+
} catch {
|
|
1507
|
+
// ignore
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
const attachClient = (newClient) => {
|
|
1512
|
+
if (!newClient) return;
|
|
1513
|
+
detachClient();
|
|
1514
|
+
client = newClient;
|
|
1515
|
+
let buffer = "";
|
|
1516
|
+
client.on("data", (data) => {
|
|
1517
|
+
buffer += data.toString("utf8");
|
|
1518
|
+
const lines = buffer.split(/\r?\n/);
|
|
1519
|
+
buffer = lines.pop() || "";
|
|
1520
|
+
for (const line of lines.filter((l) => l.trim())) {
|
|
1521
|
+
try {
|
|
1390
1522
|
const msg = JSON.parse(line);
|
|
1391
1523
|
if (msg.type === "status") {
|
|
1392
1524
|
const data = msg.data || {};
|
|
@@ -1491,6 +1623,12 @@ async function runChat(projectRoot) {
|
|
|
1491
1623
|
}
|
|
1492
1624
|
}
|
|
1493
1625
|
});
|
|
1626
|
+
client.on("close", () => {
|
|
1627
|
+
client = null;
|
|
1628
|
+
});
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1631
|
+
attachClient(client);
|
|
1494
1632
|
|
|
1495
1633
|
input.on("submit", (value) => {
|
|
1496
1634
|
const text = value.trim();
|
|
@@ -1618,6 +1756,7 @@ async function runChat(projectRoot) {
|
|
|
1618
1756
|
}
|
|
1619
1757
|
loadHistory();
|
|
1620
1758
|
loadInputHistory();
|
|
1759
|
+
renderDashboard();
|
|
1621
1760
|
resizeInput();
|
|
1622
1761
|
requestStatus();
|
|
1623
1762
|
setInterval(requestStatus, 2000);
|
package/src/config.js
CHANGED
|
@@ -3,12 +3,18 @@ const path = require("path");
|
|
|
3
3
|
|
|
4
4
|
const DEFAULT_CONFIG = {
|
|
5
5
|
launchMode: "terminal",
|
|
6
|
+
agentProvider: "codex-cli",
|
|
7
|
+
agentModel: "",
|
|
6
8
|
};
|
|
7
9
|
|
|
8
10
|
function normalizeLaunchMode(value) {
|
|
9
11
|
return value === "internal" ? "internal" : "terminal";
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
function normalizeAgentProvider(value) {
|
|
15
|
+
return value === "claude-cli" ? "claude-cli" : "codex-cli";
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
function configPath(projectRoot) {
|
|
13
19
|
return path.join(projectRoot, ".ufoo", "config.json");
|
|
14
20
|
}
|
|
@@ -16,7 +22,12 @@ function configPath(projectRoot) {
|
|
|
16
22
|
function loadConfig(projectRoot) {
|
|
17
23
|
try {
|
|
18
24
|
const raw = JSON.parse(fs.readFileSync(configPath(projectRoot), "utf8"));
|
|
19
|
-
return {
|
|
25
|
+
return {
|
|
26
|
+
...DEFAULT_CONFIG,
|
|
27
|
+
...raw,
|
|
28
|
+
launchMode: normalizeLaunchMode(raw.launchMode),
|
|
29
|
+
agentProvider: normalizeAgentProvider(raw.agentProvider),
|
|
30
|
+
};
|
|
20
31
|
} catch {
|
|
21
32
|
return { ...DEFAULT_CONFIG };
|
|
22
33
|
}
|
|
@@ -30,8 +41,9 @@ function saveConfig(projectRoot, config) {
|
|
|
30
41
|
...config,
|
|
31
42
|
};
|
|
32
43
|
merged.launchMode = normalizeLaunchMode(merged.launchMode);
|
|
44
|
+
merged.agentProvider = normalizeAgentProvider(merged.agentProvider);
|
|
33
45
|
fs.writeFileSync(target, JSON.stringify(merged, null, 2));
|
|
34
46
|
return merged;
|
|
35
47
|
}
|
|
36
48
|
|
|
37
|
-
module.exports = { loadConfig, saveConfig, normalizeLaunchMode };
|
|
49
|
+
module.exports = { loadConfig, saveConfig, normalizeLaunchMode, normalizeAgentProvider };
|
package/src/daemon/run.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const { startDaemon, stopDaemon, isRunning } = require("./index");
|
|
3
|
+
const { loadConfig } = require("../config");
|
|
3
4
|
|
|
4
5
|
function runDaemonCli(argv) {
|
|
5
6
|
const cmd = argv[1] || "start";
|
|
6
7
|
const projectRoot = process.cwd();
|
|
7
|
-
const
|
|
8
|
+
const config = loadConfig(projectRoot);
|
|
9
|
+
const provider = process.env.UFOO_AGENT_PROVIDER || config.agentProvider || "codex-cli";
|
|
8
10
|
const model =
|
|
9
|
-
process.env.UFOO_AGENT_MODEL || (provider === "claude-cli" ? "opus" : "");
|
|
11
|
+
process.env.UFOO_AGENT_MODEL || config.agentModel || (provider === "claude-cli" ? "opus" : "");
|
|
10
12
|
|
|
11
13
|
if (cmd === "start" || cmd === "--start") {
|
|
12
14
|
if (isRunning(projectRoot)) return;
|