@paradow/ledge 0.19.1 → 0.21.0
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/{attach-3BKQSEWC.js → attach-IVICSJYB.js} +11 -11
- package/dist/cli/{bridge-APNFBPNE.js → bridge-H3ZPGIUY.js} +2 -2
- package/dist/cli/{chunk-3HCID6NW.js → chunk-24AHC55Y.js} +3 -3
- package/dist/cli/{chunk-3EIRKAX7.js → chunk-2VSEIOGA.js} +4 -4
- package/dist/cli/{chunk-TRTS24PF.js → chunk-35BPMR6P.js} +2 -2
- package/dist/cli/{chunk-43D6NMYH.js → chunk-6IYPRUQG.js} +1 -1
- package/dist/cli/{chunk-ZUYOA55W.js → chunk-AJFC33TH.js} +2 -2
- package/dist/cli/{chunk-LOOOHYZR.js → chunk-ALEZJFJ3.js} +8 -8
- package/dist/cli/{chunk-BVBX222F.js → chunk-EBBAOQ2Z.js} +7 -6
- package/dist/cli/{chunk-WP77O5LC.js → chunk-EKXZ45MQ.js} +1182 -621
- package/dist/cli/{chunk-HP3G2TYJ.js → chunk-EX66KH66.js} +730 -646
- package/dist/cli/{chunk-7ZAA75HG.js → chunk-HOOHS3MX.js} +1 -1
- package/dist/cli/{chunk-FQZ3IX6E.js → chunk-K7UEKSB2.js} +2 -2
- package/dist/cli/{chunk-GXFU4XAX.js → chunk-KACRWFN3.js} +1 -1
- package/dist/cli/chunk-NFXFY5CT.js +6 -0
- package/dist/cli/{chunk-WJ6YMZTT.js → chunk-NLL2EQJ2.js} +2 -2
- package/dist/cli/{chunk-KKNE7BFZ.js → chunk-WRE3PUTV.js} +35 -11
- package/dist/cli/{chunk-WCS24ICX.js → chunk-ZQON4EV7.js} +2 -2
- package/dist/cli/{chunk-Y5DJ5INP.js → chunk-ZZMZDAF6.js} +3 -3
- package/dist/cli/{daemon-YHRAZSRS.js → daemon-5FPZG5R3.js} +87 -25
- package/dist/cli/decide-2ZKSX7O5.js +56 -0
- package/dist/cli/{handover-UXISKYPS.js → handover-AZ35QZRK.js} +5 -5
- package/dist/cli/{hook-5UAARBWU.js → hook-KANGO46Y.js} +21 -14
- package/dist/cli/index.js +23 -16
- package/dist/cli/launch-DEKJ5HI4.js +34 -0
- package/dist/cli/{send-NLVR6PL2.js → send-BFPJBXXH.js} +5 -5
- package/dist/cli/{setup-EEIXRAMU.js → setup-47ODKJBM.js} +4 -4
- package/dist/cli/{status-2OCYHOWZ.js → status-JPF4NZNI.js} +3 -3
- package/dist/cli/{task-AOOWHPMN.js → task-UTRG4KA5.js} +5 -5
- package/dist/cli/{team-TVNSVYWW.js → team-SZJHGA7D.js} +6 -6
- package/dist/cli/{tui-5XPPX4GR.js → tui-GEJHBWP5.js} +817 -367
- package/dist/cli/{uninstall-UUN7LIIT.js → uninstall-QNWRD27D.js} +4 -4
- package/dist/cli/{version-HB3S4IVG.js → version-YUO6SLAF.js} +1 -1
- package/dist/cli/who-NYPVBO64.js +11 -0
- package/package.json +1 -1
- package/dist/cli/chunk-TVIAHOOD.js +0 -6
- package/dist/cli/launch-YQYCDZ7N.js +0 -34
- package/dist/cli/who-52MENZTT.js +0 -11
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
attachEnv
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-WP77O5LC.js";
|
|
5
|
-
import "./chunk-3EIRKAX7.js";
|
|
6
|
-
import "./chunk-OLLQBK6N.js";
|
|
7
|
-
import "./chunk-KKNE7BFZ.js";
|
|
8
|
-
import "./chunk-3HCID6NW.js";
|
|
9
|
-
import "./chunk-TRTS24PF.js";
|
|
10
|
-
import "./chunk-GXFU4XAX.js";
|
|
3
|
+
} from "./chunk-ALEZJFJ3.js";
|
|
11
4
|
import {
|
|
5
|
+
imageInfo
|
|
6
|
+
} from "./chunk-EKXZ45MQ.js";
|
|
7
|
+
import "./chunk-2VSEIOGA.js";
|
|
8
|
+
import {
|
|
9
|
+
MAX_FRAME
|
|
10
|
+
} from "./chunk-OLLQBK6N.js";
|
|
11
|
+
import "./chunk-WRE3PUTV.js";
|
|
12
|
+
import "./chunk-24AHC55Y.js";
|
|
13
|
+
import "./chunk-35BPMR6P.js";
|
|
14
|
+
import "./chunk-KACRWFN3.js";
|
|
15
|
+
import {
|
|
16
|
+
ATTACHMENTS_MAX,
|
|
17
|
+
ATTACHMENT_LOCAL_MAX,
|
|
12
18
|
CONTEXT_NEAR,
|
|
13
19
|
CONTEXT_NEAR_MAX,
|
|
14
20
|
CONTEXT_NEAR_MIN,
|
|
@@ -19,6 +25,7 @@ import {
|
|
|
19
25
|
MachinesSchema,
|
|
20
26
|
NOTE_MAX,
|
|
21
27
|
NOTE_NOT_TYPED,
|
|
28
|
+
PREVIEW_MAX,
|
|
22
29
|
PROJECT_COLORS,
|
|
23
30
|
PROTOCOL_PORT,
|
|
24
31
|
ProjectsSchema,
|
|
@@ -32,13 +39,14 @@ import {
|
|
|
32
39
|
projectOf,
|
|
33
40
|
proposalRows,
|
|
34
41
|
readDaemonToken,
|
|
35
|
-
taskClosed
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
import "./chunk-
|
|
42
|
+
taskClosed,
|
|
43
|
+
transmissions
|
|
44
|
+
} from "./chunk-EX66KH66.js";
|
|
45
|
+
import "./chunk-NLL2EQJ2.js";
|
|
46
|
+
import "./chunk-6IYPRUQG.js";
|
|
39
47
|
import {
|
|
40
48
|
ledgeDir
|
|
41
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-HOOHS3MX.js";
|
|
42
50
|
|
|
43
51
|
// src/tui/index.tsx
|
|
44
52
|
import { render } from "ink";
|
|
@@ -480,6 +488,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
480
488
|
const inspections = /* @__PURE__ */ new Map();
|
|
481
489
|
const listings = /* @__PURE__ */ new Map();
|
|
482
490
|
const scans = /* @__PURE__ */ new Map();
|
|
491
|
+
const attachmentAsks = /* @__PURE__ */ new Map();
|
|
483
492
|
const modelLists = /* @__PURE__ */ new Map();
|
|
484
493
|
const operations = /* @__PURE__ */ new Map();
|
|
485
494
|
const closeRows = /* @__PURE__ */ new Map();
|
|
@@ -527,7 +536,12 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
527
536
|
let listSeq = 0;
|
|
528
537
|
const local = (id) => id === LEDGE_GIVER ? id : qualify("local", id);
|
|
529
538
|
const teamOut = (t) => ({ ...t, seats: t.seats.map((s) => ({ ...s, sessionId: s.sessionId === null ? null : qualify("local", s.sessionId) })) });
|
|
530
|
-
const taskOut = (t) => ({
|
|
539
|
+
const taskOut = (t) => ({
|
|
540
|
+
...t,
|
|
541
|
+
giver: local(t.giver),
|
|
542
|
+
owner: local(t.owner),
|
|
543
|
+
log: t.log.map((e) => ({ ...e, ...e.from ? { from: local(e.from) } : {}, ...e.to ? { to: local(e.to) } : {} }))
|
|
544
|
+
});
|
|
531
545
|
const putTasks = (list) => {
|
|
532
546
|
for (const t of list) tasks.set(t.id, taskOut(t));
|
|
533
547
|
taskList = [...tasks.values()].sort((a, b) => a.at.localeCompare(b.at));
|
|
@@ -817,6 +831,13 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
817
831
|
}
|
|
818
832
|
return;
|
|
819
833
|
}
|
|
834
|
+
if (m.type === "attachment.stored" || m.type === "attachment.data" || m.type === "attachment.error") {
|
|
835
|
+
if (attachmentAsks.get(m.requestId) === machine) {
|
|
836
|
+
attachmentAsks.delete(m.requestId);
|
|
837
|
+
emit2(m);
|
|
838
|
+
}
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
820
841
|
if (m.type === "repos.found") {
|
|
821
842
|
if (scans.get(m.requestId) === machine) {
|
|
822
843
|
scans.delete(m.requestId);
|
|
@@ -846,7 +867,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
846
867
|
fail(req.qid, `${target2?.config.label ?? m.result.machineId} is offline`);
|
|
847
868
|
return;
|
|
848
869
|
}
|
|
849
|
-
target2.client.send({ type: "chat.ask", qid: req.qid, text: m.result.text, tool: req.tool });
|
|
870
|
+
target2.client.send({ type: "chat.ask", qid: req.qid, text: m.result.text, tool: req.tool, ...req.images ? { images: req.images } : {} });
|
|
850
871
|
}
|
|
851
872
|
return;
|
|
852
873
|
}
|
|
@@ -935,6 +956,10 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
935
956
|
p.fresh = false;
|
|
936
957
|
const op = operations.get(c.id), closing = closeRows.has(c.id);
|
|
937
958
|
if (endOperation(c.id)) emit2({ type: "error", message: `${c.label} is offline`, ...op === "launch" ? { request: "session.launch" } : closing ? { request: "session.close" } : {} });
|
|
959
|
+
for (const [requestId, owner] of attachmentAsks) if (owner === c.id) {
|
|
960
|
+
attachmentAsks.delete(requestId);
|
|
961
|
+
emit2({ type: "attachment.error", requestId, reason: "offline" });
|
|
962
|
+
}
|
|
938
963
|
answerUnfoundInspections(c.id);
|
|
939
964
|
answerUnfoundListings(c.id, "offline");
|
|
940
965
|
answerFailedScans(c.id, "offline");
|
|
@@ -1019,7 +1044,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
1019
1044
|
}
|
|
1020
1045
|
const coordinator = [...peers.values()].find((p) => available(p) && p.snapshot?.daemon?.routing);
|
|
1021
1046
|
if (!coordinator) return fail(msg.qid, "Update a connected daemon to enable multi-machine chat");
|
|
1022
|
-
routing = { qid: msg.qid, text: msg.text, tool: msg.tool, coordinator: coordinator.config.id };
|
|
1047
|
+
routing = { qid: msg.qid, text: msg.text, tool: msg.tool, ...msg.images ? { images: msg.images } : {}, coordinator: coordinator.config.id };
|
|
1023
1048
|
coordinator.client.send({ type: "chat.route", qid: msg.qid, text: msg.text, tool: msg.tool, context });
|
|
1024
1049
|
return;
|
|
1025
1050
|
}
|
|
@@ -1031,10 +1056,11 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
1031
1056
|
}
|
|
1032
1057
|
let machine = selected;
|
|
1033
1058
|
let outgoing = msg;
|
|
1034
|
-
if ("id" in msg) {
|
|
1059
|
+
if ("id" in msg && msg.id !== void 0) {
|
|
1035
1060
|
const origin = splitId(msg.id);
|
|
1036
1061
|
if (!origin) {
|
|
1037
|
-
emit2({ type: "error",
|
|
1062
|
+
if (msg.type === "attachment.put" || msg.type === "attachment.get") emit2({ type: "attachment.error", requestId: msg.requestId, reason: "offline" });
|
|
1063
|
+
else emit2({ type: "error", id: msg.id, message: "Unknown machine", request: msg.type });
|
|
1038
1064
|
return;
|
|
1039
1065
|
}
|
|
1040
1066
|
machine = origin.machine;
|
|
@@ -1049,6 +1075,18 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
1049
1075
|
machine = b.machine;
|
|
1050
1076
|
outgoing = { ...outgoing, qid: b.raw };
|
|
1051
1077
|
}
|
|
1078
|
+
if (msg.type === "attachment.put" || msg.type === "attachment.get") {
|
|
1079
|
+
if (msg.id === void 0) machine = msg.type === "attachment.get" && msg.machine || LOCAL_MACHINE.id;
|
|
1080
|
+
if (outgoing.type === "attachment.get") {
|
|
1081
|
+
const { machine: _m, ...rest } = outgoing;
|
|
1082
|
+
outgoing = rest;
|
|
1083
|
+
}
|
|
1084
|
+
if (!available(peers.get(machine))) {
|
|
1085
|
+
emit2({ type: "attachment.error", requestId: msg.requestId, reason: "offline" });
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
attachmentAsks.set(msg.requestId, machine);
|
|
1089
|
+
}
|
|
1052
1090
|
if (msg.type === "document.read") {
|
|
1053
1091
|
if (configs.length > 1 && !msg.machineId && !msg.sessionId) {
|
|
1054
1092
|
emit2({ type: "document.error", requestId: msg.requestId, path: msg.path, code: "read_failed" });
|
|
@@ -1284,15 +1322,68 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
1284
1322
|
// ../fleet/src/proposals.ts
|
|
1285
1323
|
var COLORS = Object.keys(PROJECT_COLORS);
|
|
1286
1324
|
|
|
1325
|
+
// ../fleet/src/attachments.ts
|
|
1326
|
+
import { useEffect, useState } from "react";
|
|
1327
|
+
var AttachmentFailure = class extends Error {
|
|
1328
|
+
constructor(reason) {
|
|
1329
|
+
super(`attachment: ${reason}`);
|
|
1330
|
+
this.reason = reason;
|
|
1331
|
+
this.name = "AttachmentFailure";
|
|
1332
|
+
}
|
|
1333
|
+
reason;
|
|
1334
|
+
};
|
|
1335
|
+
var TIMEOUT_MS = 2e4;
|
|
1336
|
+
var counter = 0;
|
|
1337
|
+
var nextRequestId = () => `att-${Date.now().toString(36)}-${(counter++).toString(36)}`;
|
|
1338
|
+
function request(client, build, pick, timeoutMs) {
|
|
1339
|
+
const requestId = nextRequestId();
|
|
1340
|
+
return new Promise((resolve, reject) => {
|
|
1341
|
+
let timer;
|
|
1342
|
+
const off = client.onMessage((m) => {
|
|
1343
|
+
if (!("requestId" in m) || m.requestId !== requestId) return;
|
|
1344
|
+
if (m.type === "attachment.error") {
|
|
1345
|
+
done();
|
|
1346
|
+
reject(new AttachmentFailure(m.reason));
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
const value = pick(m);
|
|
1350
|
+
if (value !== void 0) {
|
|
1351
|
+
done();
|
|
1352
|
+
resolve(value);
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
const done = () => {
|
|
1356
|
+
off();
|
|
1357
|
+
if (timer) clearTimeout(timer);
|
|
1358
|
+
};
|
|
1359
|
+
timer = setTimeout(() => {
|
|
1360
|
+
off();
|
|
1361
|
+
reject(new AttachmentFailure("timeout"));
|
|
1362
|
+
}, timeoutMs);
|
|
1363
|
+
client.send(build(requestId));
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
function putImage(client, image, route = {}, timeoutMs = TIMEOUT_MS) {
|
|
1367
|
+
return request(client, (requestId) => ({ type: "attachment.put", requestId, image, ...route.id ? { id: route.id } : {} }), (m) => m.type === "attachment.stored" ? m.ref : void 0, timeoutMs);
|
|
1368
|
+
}
|
|
1369
|
+
function getImage(client, attachment, route = {}, preview = false, timeoutMs = TIMEOUT_MS) {
|
|
1370
|
+
return request(
|
|
1371
|
+
client,
|
|
1372
|
+
(requestId) => ({ type: "attachment.get", requestId, attachment, ...preview ? { preview: true } : {}, ...route.id ? { id: route.id } : {}, ...route.machine ? { machine: route.machine } : {} }),
|
|
1373
|
+
(m) => m.type === "attachment.data" ? { mime: m.mime, data: m.data } : void 0,
|
|
1374
|
+
timeoutMs
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1287
1378
|
// src/tui/App.tsx
|
|
1288
|
-
import { useCallback, useEffect as
|
|
1289
|
-
import { useApp, useInput as
|
|
1379
|
+
import { useCallback, useEffect as useEffect10, useMemo as useMemo6, useRef as useRef9, useState as useState11 } from "react";
|
|
1380
|
+
import { useApp, useInput as useInput6, useStdout } from "ink";
|
|
1290
1381
|
|
|
1291
1382
|
// src/tui/actions.ts
|
|
1292
|
-
import { useEffect as
|
|
1383
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef2 } from "react";
|
|
1293
1384
|
|
|
1294
1385
|
// ../fleet/src/decisions.ts
|
|
1295
|
-
import { useEffect, useRef, useState } from "react";
|
|
1386
|
+
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
1296
1387
|
function requestKey(s) {
|
|
1297
1388
|
const p = s.pending;
|
|
1298
1389
|
return p ? `${p.kind}
|
|
@@ -1307,8 +1398,8 @@ function markOf(s) {
|
|
|
1307
1398
|
function useDecisions(o) {
|
|
1308
1399
|
const { sessions, errors } = o;
|
|
1309
1400
|
const live = useRef(/* @__PURE__ */ new Map());
|
|
1310
|
-
const [inFlight, setInFlight] =
|
|
1311
|
-
const [landed, setLanded] =
|
|
1401
|
+
const [inFlight, setInFlight] = useState2(() => /* @__PURE__ */ new Set());
|
|
1402
|
+
const [landed, setLanded] = useState2(null);
|
|
1312
1403
|
const latest = useRef({ o });
|
|
1313
1404
|
latest.current = { o };
|
|
1314
1405
|
const seen = useRef(errors.at(-1)?.seq ?? 0);
|
|
@@ -1329,7 +1420,7 @@ function useDecisions(o) {
|
|
|
1329
1420
|
}
|
|
1330
1421
|
return x;
|
|
1331
1422
|
};
|
|
1332
|
-
|
|
1423
|
+
useEffect2(() => {
|
|
1333
1424
|
const byId = new Map(sessions.map((s) => [s.id, s]));
|
|
1334
1425
|
for (const [id, x] of [...live.current]) {
|
|
1335
1426
|
const s = byId.get(id);
|
|
@@ -1345,7 +1436,7 @@ function useDecisions(o) {
|
|
|
1345
1436
|
}
|
|
1346
1437
|
for (const id of [...settled.current.keys()]) if (!byId.has(id)) unsettle(id);
|
|
1347
1438
|
}, [sessions]);
|
|
1348
|
-
|
|
1439
|
+
useEffect2(() => {
|
|
1349
1440
|
const last = errors.at(-1)?.seq ?? 0;
|
|
1350
1441
|
if (last < seen.current) seen.current = 0;
|
|
1351
1442
|
for (const e of errors) {
|
|
@@ -1362,12 +1453,12 @@ function useDecisions(o) {
|
|
|
1362
1453
|
if (x.text !== void 0) x.onRefused?.(x.text);
|
|
1363
1454
|
}
|
|
1364
1455
|
}, [errors]);
|
|
1365
|
-
|
|
1456
|
+
useEffect2(() => {
|
|
1366
1457
|
if (!landed) return;
|
|
1367
1458
|
const timer = setTimeout(() => setLanded(null), FLASH_MS);
|
|
1368
1459
|
return () => clearTimeout(timer);
|
|
1369
1460
|
}, [landed]);
|
|
1370
|
-
|
|
1461
|
+
useEffect2(() => () => {
|
|
1371
1462
|
for (const x of [...live.current.values(), ...settled.current.values()]) clearTimeout(x.timer);
|
|
1372
1463
|
live.current.clear();
|
|
1373
1464
|
settled.current.clear();
|
|
@@ -1405,8 +1496,9 @@ function useDecisions(o) {
|
|
|
1405
1496
|
return send(s, "session.answer", { type: "session.answer", id: s.id, requestId: p.requestId, answers: [...answers], ...cancel ? { cancel: true } : {} });
|
|
1406
1497
|
},
|
|
1407
1498
|
reply(s, text, onRefused, how) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1499
|
+
const attachments = how?.attachments?.length ? how.attachments : void 0;
|
|
1500
|
+
if (s.offline || s.source === "hook_only" || !text.trim() && !attachments || live.current.has(s.id) || s.pending && s.pending.kind !== "question") return false;
|
|
1501
|
+
return send(s, "session.reply", { type: "session.reply", id: s.id, text, ...attachments ? { attachments } : {}, ...how?.steer ? { steer: true } : {} }, { text, onRefused });
|
|
1410
1502
|
}
|
|
1411
1503
|
};
|
|
1412
1504
|
}
|
|
@@ -1446,7 +1538,7 @@ function useActions(o) {
|
|
|
1446
1538
|
const { t, toast } = o;
|
|
1447
1539
|
const decisions = useDecisions({ client: o.client, sessions: o.sessions, errors: o.errors, notify: (text) => toast({ text, tone: "error" }), copy: t.decisions });
|
|
1448
1540
|
const onLand = useRef2(/* @__PURE__ */ new Map());
|
|
1449
|
-
|
|
1541
|
+
useEffect3(() => {
|
|
1450
1542
|
const id = decisions.landed;
|
|
1451
1543
|
if (!id) return;
|
|
1452
1544
|
const text = onLand.current.get(id);
|
|
@@ -1479,8 +1571,8 @@ function useActions(o) {
|
|
|
1479
1571
|
toast({ text: t.toast.noNote(s.name), tone: "info" });
|
|
1480
1572
|
}
|
|
1481
1573
|
},
|
|
1482
|
-
reply(s, text, onRefused) {
|
|
1483
|
-
const sent = decisions.reply(s, text, onRefused);
|
|
1574
|
+
reply(s, text, onRefused, how) {
|
|
1575
|
+
const sent = decisions.reply(s, text, onRefused, how);
|
|
1484
1576
|
if (sent) onLand.current.set(s.id, t.toast.reply(s.name));
|
|
1485
1577
|
else toast({ text: s.offline ? t.toast.offline(s.name) : s.source === "hook_only" ? t.toast.readOnly(s.name) : t.toast.busy(s.name), tone: "info" });
|
|
1486
1578
|
return sent;
|
|
@@ -1518,8 +1610,127 @@ function runAttached(argv, env = process.env) {
|
|
|
1518
1610
|
});
|
|
1519
1611
|
}
|
|
1520
1612
|
|
|
1613
|
+
// src/tui/clipboard.ts
|
|
1614
|
+
import { execFile } from "child_process";
|
|
1615
|
+
import { mkdtemp, readFile, rm } from "fs/promises";
|
|
1616
|
+
import { tmpdir } from "os";
|
|
1617
|
+
import { join } from "path";
|
|
1618
|
+
var CLIPBOARD_TIMEOUT_MS = 1e4;
|
|
1619
|
+
var runFile = (file, args, o) => new Promise((resolve, reject) => {
|
|
1620
|
+
execFile(file, args, { ...o, timeout: CLIPBOARD_TIMEOUT_MS }, (err, stdout) => err ? reject(err) : resolve({ stdout }));
|
|
1621
|
+
});
|
|
1622
|
+
var POWERSHELL = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe";
|
|
1623
|
+
var MAX_BUFFER = 4 * 1024 * 1024;
|
|
1624
|
+
var LOCAL_MAX_BUFFER = 20 * 1024 * 1024;
|
|
1625
|
+
var SCRIPT = [
|
|
1626
|
+
"Add-Type -AssemblyName System.Windows.Forms,System.Drawing;",
|
|
1627
|
+
"$src = [System.Windows.Forms.Clipboard]::GetImage();",
|
|
1628
|
+
"if ($null -eq $src) { exit 0 }",
|
|
1629
|
+
"$codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object { $_.MimeType -eq 'image/jpeg' };",
|
|
1630
|
+
"function Encode($long, $qualities, $max) {",
|
|
1631
|
+
"$k = [Math]::Min(1.0, $long / [Math]::Max($src.Width, $src.Height));",
|
|
1632
|
+
"$w = [Math]::Max(1, [int][Math]::Round($src.Width * $k)); $h = [Math]::Max(1, [int][Math]::Round($src.Height * $k));",
|
|
1633
|
+
"$bmp = New-Object System.Drawing.Bitmap($w, $h); $ms = $null;",
|
|
1634
|
+
"try {",
|
|
1635
|
+
"$g = [System.Drawing.Graphics]::FromImage($bmp);",
|
|
1636
|
+
"try { $g.Clear([System.Drawing.Color]::White); $g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic; $g.DrawImage($src, 0, 0, $w, $h) } finally { $g.Dispose() }",
|
|
1637
|
+
"foreach ($q in $qualities) {",
|
|
1638
|
+
"if ($null -ne $ms) { $ms.Dispose(); $ms = $null }",
|
|
1639
|
+
"$p = New-Object System.Drawing.Imaging.EncoderParameters(1);",
|
|
1640
|
+
"try {",
|
|
1641
|
+
"$p.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::Quality, [long]$q);",
|
|
1642
|
+
"$ms = New-Object System.IO.MemoryStream; $bmp.Save($ms, $codec, $p)",
|
|
1643
|
+
"} finally { $p.Dispose() }",
|
|
1644
|
+
"if ($ms.Length -le $max) { break }",
|
|
1645
|
+
"}",
|
|
1646
|
+
"return @{ w = $w; h = $h; b = [Convert]::ToBase64String($ms.ToArray()) }",
|
|
1647
|
+
"} finally { if ($null -ne $ms) { $ms.Dispose() }; $bmp.Dispose() }",
|
|
1648
|
+
"}",
|
|
1649
|
+
"try {",
|
|
1650
|
+
"$main = Encode 1600 @(85, 70, 55, 40) 400000;",
|
|
1651
|
+
"$prev = Encode 480 @(70) 40000;",
|
|
1652
|
+
"[Console]::Out.Write((ConvertTo-Json -Compress @{ w = $main.w; h = $main.h; main = $main.b; prev = $prev.b }))",
|
|
1653
|
+
"} finally { $src.Dispose() }"
|
|
1654
|
+
].join(" ");
|
|
1655
|
+
async function fromWindows(run2) {
|
|
1656
|
+
const { stdout } = await run2(POWERSHELL, ["-NoProfile", "-STA", "-Command", SCRIPT], { cwd: "/mnt/c", maxBuffer: MAX_BUFFER, encoding: "buffer" });
|
|
1657
|
+
const line = stdout.toString("utf8").trim();
|
|
1658
|
+
if (!line) return null;
|
|
1659
|
+
const j = JSON.parse(line);
|
|
1660
|
+
const preview = j.prev && Buffer.from(j.prev, "base64").length <= PREVIEW_MAX ? { mime: "image/jpeg", data: j.prev } : void 0;
|
|
1661
|
+
return { image: { mime: "image/jpeg", data: j.main, ...preview ? { preview } : {} }, width: j.w, height: j.h, bytes: Buffer.from(j.main, "base64").length };
|
|
1662
|
+
}
|
|
1663
|
+
function fromBytes(b) {
|
|
1664
|
+
const info = imageInfo(b);
|
|
1665
|
+
return info ? { image: { mime: info.mime, data: b.toString("base64") }, width: info.width, height: info.height, bytes: b.length } : null;
|
|
1666
|
+
}
|
|
1667
|
+
async function fromLinux(run2) {
|
|
1668
|
+
const attempts = [["wl-paste", ["--type", "image/png"]], ["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]];
|
|
1669
|
+
for (const [file, args] of attempts) {
|
|
1670
|
+
try {
|
|
1671
|
+
const got = fromBytes((await run2(file, args, { maxBuffer: LOCAL_MAX_BUFFER, encoding: "buffer" })).stdout);
|
|
1672
|
+
if (got) return got;
|
|
1673
|
+
} catch {
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
return null;
|
|
1677
|
+
}
|
|
1678
|
+
async function fromMac(run2) {
|
|
1679
|
+
const dir = await mkdtemp(join(tmpdir(), "ledge-clip-"));
|
|
1680
|
+
const file = join(dir, "clip.png");
|
|
1681
|
+
try {
|
|
1682
|
+
const script = `set f to open for access POSIX file "${file}" with write permission
|
|
1683
|
+
set eof f to 0
|
|
1684
|
+
write (the clipboard as \xABclass PNGf\xBB) to f
|
|
1685
|
+
close access f`;
|
|
1686
|
+
await run2("osascript", ["-e", script], { maxBuffer: MAX_BUFFER, encoding: "buffer" });
|
|
1687
|
+
return fromBytes(await readFile(file));
|
|
1688
|
+
} catch {
|
|
1689
|
+
return null;
|
|
1690
|
+
} finally {
|
|
1691
|
+
await rm(dir, { recursive: true, force: true });
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
async function readClipboardImage(env = process.env, run2 = runFile, platform = process.platform) {
|
|
1695
|
+
try {
|
|
1696
|
+
if (platform === "darwin") return await fromMac(run2);
|
|
1697
|
+
return env.WSL_DISTRO_NAME ? await fromWindows(run2) : await fromLinux(run2);
|
|
1698
|
+
} catch {
|
|
1699
|
+
return null;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// src/tui/openImage.ts
|
|
1704
|
+
import { spawn as spawn2 } from "child_process";
|
|
1705
|
+
function windowsPath(file, spawnImpl) {
|
|
1706
|
+
return new Promise((resolve, reject) => {
|
|
1707
|
+
const child = spawnImpl("wslpath", ["-w", file], { stdio: ["ignore", "pipe", "ignore"] });
|
|
1708
|
+
let out = "";
|
|
1709
|
+
child.stdout.on("data", (d) => {
|
|
1710
|
+
out += d;
|
|
1711
|
+
});
|
|
1712
|
+
child.on("error", reject);
|
|
1713
|
+
child.on("close", (code) => code === 0 && out.trim() ? resolve(out.trim()) : reject(new Error(`wslpath exited with ${code}`)));
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
function launch(spawnImpl, file, args, cwd) {
|
|
1717
|
+
return new Promise((resolve, reject) => {
|
|
1718
|
+
const child = spawnImpl(file, args, { detached: true, stdio: "ignore", ...cwd ? { cwd } : {} });
|
|
1719
|
+
child.once("error", reject);
|
|
1720
|
+
child.once("spawn", () => {
|
|
1721
|
+
child.unref();
|
|
1722
|
+
resolve();
|
|
1723
|
+
});
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
async function openImage(file, env = process.env, spawnImpl = spawn2, platform = process.platform) {
|
|
1727
|
+
if (platform === "darwin") return launch(spawnImpl, "open", [file]);
|
|
1728
|
+
if (env.WSL_DISTRO_NAME) return launch(spawnImpl, "/mnt/c/Windows/explorer.exe", [await windowsPath(file, spawnImpl)], "/mnt/c");
|
|
1729
|
+
return launch(spawnImpl, "xdg-open", [file]);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1521
1732
|
// src/tui/context.tsx
|
|
1522
|
-
import { createContext, useContext, useEffect as
|
|
1733
|
+
import { createContext, useContext, useEffect as useEffect4, useRef as useRef3 } from "react";
|
|
1523
1734
|
var SCREENS = ["overview", "session", "ask", "launch", "settings"];
|
|
1524
1735
|
var TuiContext = createContext(null);
|
|
1525
1736
|
var useTui = () => {
|
|
@@ -1531,7 +1742,7 @@ function useScreenKeys(handler) {
|
|
|
1531
1742
|
const { keys } = useTui();
|
|
1532
1743
|
const latest = useRef3(handler);
|
|
1533
1744
|
latest.current = handler;
|
|
1534
|
-
|
|
1745
|
+
useEffect4(() => {
|
|
1535
1746
|
const fn = (i, k) => latest.current(i, k);
|
|
1536
1747
|
keys.current = fn;
|
|
1537
1748
|
return () => {
|
|
@@ -1541,7 +1752,7 @@ function useScreenKeys(handler) {
|
|
|
1541
1752
|
}
|
|
1542
1753
|
function useEditing(on) {
|
|
1543
1754
|
const { setEditing } = useTui();
|
|
1544
|
-
|
|
1755
|
+
useEffect4(() => {
|
|
1545
1756
|
setEditing(on);
|
|
1546
1757
|
return () => setEditing(false);
|
|
1547
1758
|
}, [on, setEditing]);
|
|
@@ -1550,6 +1761,15 @@ function useEditing(on) {
|
|
|
1550
1761
|
// src/tui/i18n/lang.tsx
|
|
1551
1762
|
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
1552
1763
|
|
|
1764
|
+
// ../ui/src/images/encode.ts
|
|
1765
|
+
function formatBytes(n, lang) {
|
|
1766
|
+
if (n >= 1e6) {
|
|
1767
|
+
const v = (Math.round(n / 1e5) / 10).toFixed(1);
|
|
1768
|
+
return `${lang === "fr" ? v.replace(".", ",") : v} ${lang === "fr" ? "Mo" : "MB"}`;
|
|
1769
|
+
}
|
|
1770
|
+
return `${Math.round(n / 1e3)} ${lang === "fr" ? "Ko" : "KB"}`;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1553
1773
|
// src/tui/i18n/fr.ts
|
|
1554
1774
|
var plural = (n, one, many) => n > 1 ? many : one;
|
|
1555
1775
|
var fr = {
|
|
@@ -1572,6 +1792,8 @@ var fr = {
|
|
|
1572
1792
|
tokenMissing: "pas de jeton : lancez une session avec `ledge claude` ou `ledge daemon`"
|
|
1573
1793
|
},
|
|
1574
1794
|
keys: {
|
|
1795
|
+
image: "image",
|
|
1796
|
+
openImage: "ouvrir l'image",
|
|
1575
1797
|
navigate: "naviguer",
|
|
1576
1798
|
open: "ouvrir",
|
|
1577
1799
|
answer: "r\xE9pondre",
|
|
@@ -1761,6 +1983,18 @@ var fr = {
|
|
|
1761
1983
|
settings: "R\xE9glages",
|
|
1762
1984
|
screens: "changer d\u2019\xE9cran"
|
|
1763
1985
|
},
|
|
1986
|
+
images: {
|
|
1987
|
+
capture: "capture",
|
|
1988
|
+
size: (n) => formatBytes(n, "fr"),
|
|
1989
|
+
open: (key) => `${key} ouvrir dans la visionneuse`,
|
|
1990
|
+
tooLarge: "Image trop lourde, m\xEAme r\xE9duite",
|
|
1991
|
+
notImage: "Ce fichier n'est pas une image",
|
|
1992
|
+
failed: "Image non envoy\xE9e",
|
|
1993
|
+
max: "5 images au plus par message",
|
|
1994
|
+
dialog: "R\xE9pondez d'abord \xE0 la question, l'image partira apr\xE8s",
|
|
1995
|
+
none: "Pas d'image dans le presse-papiers",
|
|
1996
|
+
openFailed: (why) => `Image non ouverte : ${why}`
|
|
1997
|
+
},
|
|
1764
1998
|
toast: {
|
|
1765
1999
|
handover: (n) => `${n} \xE9crit son bilan : une nouvelle session prendra sa place`,
|
|
1766
2000
|
handoverRefused: (why) => `Rel\xE8ve refus\xE9e : ${why}`,
|
|
@@ -1816,6 +2050,8 @@ var en = {
|
|
|
1816
2050
|
tokenMissing: "no token: start a session with `ledge claude` or run `ledge daemon`"
|
|
1817
2051
|
},
|
|
1818
2052
|
keys: {
|
|
2053
|
+
image: "image",
|
|
2054
|
+
openImage: "open image",
|
|
1819
2055
|
navigate: "move",
|
|
1820
2056
|
open: "open",
|
|
1821
2057
|
answer: "answer",
|
|
@@ -1996,6 +2232,18 @@ var en = {
|
|
|
1996
2232
|
},
|
|
1997
2233
|
settings: { title: "Settings", language: "Language", context: "Max context", contextHint: "past it: hand over", contextUnknown: "update the daemon", contextSaved: (pct) => `Max context: ${pct} %`, saved: "Setting saved.", saveFailed: (m) => `Setting applied, but not saved: ${m}` },
|
|
1998
2234
|
help: { title: "Help", global: "Everywhere", overview: "Overview", session: "Session", ask: "Ask Ledge", launch: "New session", settings: "Settings", screens: "switch screen" },
|
|
2235
|
+
images: {
|
|
2236
|
+
capture: "capture",
|
|
2237
|
+
size: (n) => formatBytes(n, "en"),
|
|
2238
|
+
open: (key) => `${key} open in viewer`,
|
|
2239
|
+
tooLarge: "Image too large, even reduced",
|
|
2240
|
+
notImage: "This file is not an image",
|
|
2241
|
+
failed: "Image not sent",
|
|
2242
|
+
max: "5 images per message at most",
|
|
2243
|
+
dialog: "Answer the question first, the image will go after",
|
|
2244
|
+
none: "No image in the clipboard",
|
|
2245
|
+
openFailed: (why) => `Image not opened: ${why}`
|
|
2246
|
+
},
|
|
1999
2247
|
toast: {
|
|
2000
2248
|
handover: (n) => `${n} is writing its brief: a fresh session will take its place`,
|
|
2001
2249
|
handoverRefused: (why) => `Relief refused: ${why}`,
|
|
@@ -2112,12 +2360,15 @@ function nextWaiting(yours, selected) {
|
|
|
2112
2360
|
}
|
|
2113
2361
|
function exchanges(sessions, max, tasks = [], ledge = "Ledge") {
|
|
2114
2362
|
const byId = new Map(sessions.map((s) => [s.id, s]));
|
|
2363
|
+
const bare = (id) => splitId(id)?.id ?? id;
|
|
2364
|
+
const find2 = (id) => byId.get(id) ?? sessions.find((x) => bare(x.id) === bare(id));
|
|
2365
|
+
const nameOf = (id, then) => id === LEDGE_GIVER ? ledge : find2(id)?.name ?? then;
|
|
2115
2366
|
return [...sessions.flatMap((s) => {
|
|
2116
2367
|
const m = s.lastMessage;
|
|
2117
2368
|
if (!m) return [];
|
|
2118
|
-
const peer = m.peer ?
|
|
2119
|
-
return [{ at: m.at, from: s.name, to: peer?.name ?? m.to, summary: m.summary }];
|
|
2120
|
-
}), ...tasks.
|
|
2369
|
+
const peer = m.peer ? find2(m.peer) : void 0;
|
|
2370
|
+
return [{ at: m.at, from: s.name, to: peer?.name ?? m.to, summary: m.summary, kind: "message" }];
|
|
2371
|
+
}), ...tasks.flatMap((x) => transmissions(x).map((tr) => ({ at: tr.at, from: nameOf(tr.from, tr.fromName), to: nameOf(tr.to, tr.toName), summary: x.title, kind: "task" })))].sort((a, b) => b.at.localeCompare(a.at)).slice(0, max);
|
|
2121
2372
|
}
|
|
2122
2373
|
var nearContext = (sessions, near) => sessions.filter((s) => s.state !== "exited" && !s.archivedAt && (s.handover ? s.handover.state === "failed" : contextNear(s.context, near)));
|
|
2123
2374
|
var closedLedgeTasks = (tasks) => tasks.filter((x) => x.giver === LEDGE_GIVER && taskClosed(x) && !x.seen);
|
|
@@ -2165,7 +2416,7 @@ var title = (text) => `\x1B]0;${text.replace(/[\u0000-\u001f\u007f]/g, " ")}\x07
|
|
|
2165
2416
|
import { useMemo as useMemo2 } from "react";
|
|
2166
2417
|
import { Box as Box6, Text as Text6, useWindowSize as useWindowSize2 } from "ink";
|
|
2167
2418
|
import { randomUUID } from "crypto";
|
|
2168
|
-
import { useState as
|
|
2419
|
+
import { useState as useState3 } from "react";
|
|
2169
2420
|
|
|
2170
2421
|
// ../ui/src/bots/poseFor.ts
|
|
2171
2422
|
var FROM_SESSION = {
|
|
@@ -2429,8 +2680,8 @@ function Overview() {
|
|
|
2429
2680
|
const closed = closedLedgeTasks(tasks);
|
|
2430
2681
|
const threshold = useContextNear(tui.client);
|
|
2431
2682
|
const near = useMemo2(() => nearContext(d.sessions, threshold), [d.sessions, threshold]);
|
|
2432
|
-
const [tasksShown, setTasksShown] =
|
|
2433
|
-
const [joining, setJoining] =
|
|
2683
|
+
const [tasksShown, setTasksShown] = useState3(true);
|
|
2684
|
+
const [joining, setJoining] = useState3(null);
|
|
2434
2685
|
const node = selected ? view.tree.find((n) => n.branches.some((b) => b.sessions.includes(selected)) || n.teams.some((x) => x.sessions.includes(selected))) : void 0;
|
|
2435
2686
|
const seatTeam = selected?.team ? teams?.find((x) => x.id === selected.team && x.state !== "dissolved") : void 0;
|
|
2436
2687
|
const choices2 = node?.teams.map((x) => x.team).filter((x) => x.state !== "dissolved" && x.id !== selected?.team) ?? [];
|
|
@@ -2654,10 +2905,14 @@ function Overview() {
|
|
|
2654
2905
|
" ",
|
|
2655
2906
|
/* @__PURE__ */ jsx7(Text6, { color: C.fg, children: x.from }),
|
|
2656
2907
|
" ",
|
|
2657
|
-
/* @__PURE__ */ jsx7(Text6, { color: C.violet, children: g.arrow }),
|
|
2908
|
+
/* @__PURE__ */ jsx7(Text6, { color: x.kind === "task" ? C.green : C.violet, children: g.arrow }),
|
|
2658
2909
|
" ",
|
|
2659
2910
|
/* @__PURE__ */ jsx7(Text6, { color: C.fg, children: x.to }),
|
|
2660
2911
|
" ",
|
|
2912
|
+
x.kind === "task" ? /* @__PURE__ */ jsxs5(Text6, { color: C.green, children: [
|
|
2913
|
+
g.task,
|
|
2914
|
+
" "
|
|
2915
|
+
] }) : null,
|
|
2661
2916
|
/* @__PURE__ */ jsx7(Text6, { color: C.dim, children: oneLine(x.summary) })
|
|
2662
2917
|
] }, i));
|
|
2663
2918
|
})() })
|
|
@@ -2698,8 +2953,126 @@ function SessionRow({ s, on, nameW, topicW, stateW, indent = 0 }) {
|
|
|
2698
2953
|
}
|
|
2699
2954
|
|
|
2700
2955
|
// src/tui/screens/Session.tsx
|
|
2701
|
-
import { useEffect as
|
|
2702
|
-
import { Box as Box9, Text as
|
|
2956
|
+
import { useEffect as useEffect7, useMemo as useMemo3, useState as useState7 } from "react";
|
|
2957
|
+
import { Box as Box9, Text as Text11, useInput as useInput3, useWindowSize as useWindowSize3 } from "ink";
|
|
2958
|
+
|
|
2959
|
+
// src/tui/images.tsx
|
|
2960
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
2961
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2962
|
+
import { join as join2 } from "path";
|
|
2963
|
+
import { useRef as useRef6, useState as useState4 } from "react";
|
|
2964
|
+
import { Text as Text7 } from "ink";
|
|
2965
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2966
|
+
var counter2 = 0;
|
|
2967
|
+
var INLINE_BUDGET = MAX_FRAME - 64 * 1024;
|
|
2968
|
+
function useChips(put) {
|
|
2969
|
+
const { t, toast } = useTui();
|
|
2970
|
+
const [items, setItems] = useState4([]);
|
|
2971
|
+
const held = useRef6([]);
|
|
2972
|
+
const commit = (next2) => {
|
|
2973
|
+
held.current = next2;
|
|
2974
|
+
setItems(next2);
|
|
2975
|
+
};
|
|
2976
|
+
const patch = (key, over) => commit(held.current.map((c) => c.key === key ? { ...c, ...over } : c));
|
|
2977
|
+
const reasonOf = (e) => {
|
|
2978
|
+
const reason = e instanceof AttachmentFailure ? e.reason : null;
|
|
2979
|
+
return reason === "too_large" ? t.images.tooLarge : reason === "not_image" ? t.images.notImage : t.images.failed;
|
|
2980
|
+
};
|
|
2981
|
+
return {
|
|
2982
|
+
items,
|
|
2983
|
+
add({ image, width, height, bytes }) {
|
|
2984
|
+
if (held.current.length >= ATTACHMENTS_MAX) {
|
|
2985
|
+
toast({ text: t.images.max, tone: "info" });
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
const key = counter2++;
|
|
2989
|
+
const inline = held.current.reduce((n, c) => n + (c.image?.data.length ?? 0), 0);
|
|
2990
|
+
if (bytes > ATTACHMENT_LOCAL_MAX || !put && inline + image.data.length > INLINE_BUDGET) {
|
|
2991
|
+
commit([...held.current, { key, width, height, state: "refused", reason: t.images.tooLarge }]);
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
if (!put) {
|
|
2995
|
+
commit([...held.current, { key, width, height, state: "ready", image }]);
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
commit([...held.current, { key, width, height, state: "uploading" }]);
|
|
2999
|
+
put(image).then((ref) => patch(key, { state: "ready", id: ref.id }), (e) => patch(key, { state: "refused", reason: reasonOf(e) }));
|
|
3000
|
+
},
|
|
3001
|
+
removeLast() {
|
|
3002
|
+
commit(held.current.slice(0, -1));
|
|
3003
|
+
},
|
|
3004
|
+
ready: items.every((c) => c.state === "ready"),
|
|
3005
|
+
ids: () => held.current.flatMap((c) => c.state === "ready" && c.id ? [c.id] : []),
|
|
3006
|
+
uploads: () => held.current.flatMap((c) => c.state === "ready" && c.image ? [c.image] : []),
|
|
3007
|
+
clear() {
|
|
3008
|
+
const taken = held.current;
|
|
3009
|
+
commit([]);
|
|
3010
|
+
return taken;
|
|
3011
|
+
},
|
|
3012
|
+
putBack(taken) {
|
|
3013
|
+
commit([...taken, ...held.current].slice(0, ATTACHMENTS_MAX));
|
|
3014
|
+
}
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
function ChipsLine({ chips }) {
|
|
3018
|
+
const { t } = useTui();
|
|
3019
|
+
if (chips.length === 0) return null;
|
|
3020
|
+
return /* @__PURE__ */ jsx8(Text7, { wrap: "truncate", children: chips.map((c, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
3021
|
+
i ? " " : "",
|
|
3022
|
+
/* @__PURE__ */ jsxs6(Text7, { color: c.state === "refused" ? C.red : C.dim, children: [
|
|
3023
|
+
"\u25A3 ",
|
|
3024
|
+
t.images.capture,
|
|
3025
|
+
" ",
|
|
3026
|
+
c.width,
|
|
3027
|
+
"\xD7",
|
|
3028
|
+
c.height,
|
|
3029
|
+
" ",
|
|
3030
|
+
c.state === "uploading" ? "\u2026" : "\u2715"
|
|
3031
|
+
] }),
|
|
3032
|
+
c.reason ? /* @__PURE__ */ jsxs6(Text7, { color: C.red, children: [
|
|
3033
|
+
" ",
|
|
3034
|
+
c.reason
|
|
3035
|
+
] }) : null
|
|
3036
|
+
] }, c.key)) });
|
|
3037
|
+
}
|
|
3038
|
+
var EXT = { "image/png": "png", "image/jpeg": "jpg", "image/gif": "gif", "image/webp": "webp" };
|
|
3039
|
+
function usePaste(chips) {
|
|
3040
|
+
const { readImage, toast, t } = useTui();
|
|
3041
|
+
const reading = useRef6(false);
|
|
3042
|
+
return () => {
|
|
3043
|
+
if (reading.current) return;
|
|
3044
|
+
reading.current = true;
|
|
3045
|
+
void readImage().then((got) => {
|
|
3046
|
+
if (got) chips.add(got);
|
|
3047
|
+
else toast({ text: t.images.none, tone: "info" });
|
|
3048
|
+
}, () => toast({ text: t.images.none, tone: "info" })).finally(() => {
|
|
3049
|
+
reading.current = false;
|
|
3050
|
+
});
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
function useOpenImage() {
|
|
3054
|
+
const { client, openFile, toast, t } = useTui();
|
|
3055
|
+
const presses = useRef6(0);
|
|
3056
|
+
const last = useRef6("");
|
|
3057
|
+
return (items) => {
|
|
3058
|
+
const signature = items.map((x) => x.ref.id).join(",");
|
|
3059
|
+
if (signature !== last.current) {
|
|
3060
|
+
last.current = signature;
|
|
3061
|
+
presses.current = 0;
|
|
3062
|
+
}
|
|
3063
|
+
const item = items[items.length - 1 - presses.current++ % items.length];
|
|
3064
|
+
if (!item) return;
|
|
3065
|
+
const { ref, route } = item;
|
|
3066
|
+
void (async () => {
|
|
3067
|
+
const { mime, data } = await getImage(client, ref.id, route);
|
|
3068
|
+
const dir = join2(tmpdir2(), "ledge-images");
|
|
3069
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
3070
|
+
const file = join2(dir, `${ref.id}.${EXT[mime]}`);
|
|
3071
|
+
await writeFile(file, Buffer.from(data, "base64"), { mode: 384 });
|
|
3072
|
+
await openFile(file);
|
|
3073
|
+
})().catch((e) => toast({ text: t.images.openFailed(e instanceof AttachmentFailure ? e.reason : e.message), tone: "error" }));
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
2703
3076
|
|
|
2704
3077
|
// ../ui/src/thread/turns.ts
|
|
2705
3078
|
var byTime = (a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0;
|
|
@@ -2761,6 +3134,9 @@ function target(item) {
|
|
|
2761
3134
|
var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
2762
3135
|
|
|
2763
3136
|
// src/tui/model/thread.ts
|
|
3137
|
+
function imageLine(ref, t, key) {
|
|
3138
|
+
return [{ text: ` \u25A3 ${t.images.capture} ${ref.width}\xD7${ref.height} \xB7 ${t.images.size(ref.bytes)} \xB7 ${t.images.open(key)}`, color: C.faint }];
|
|
3139
|
+
}
|
|
2764
3140
|
var DIFF_MAX = 30;
|
|
2765
3141
|
function wrap(text, width) {
|
|
2766
3142
|
const out = [];
|
|
@@ -2816,7 +3192,7 @@ function itemLines(it, o) {
|
|
|
2816
3192
|
const body = (text, color) => wrap(text, width - 2).map((l) => [{ text: ` ${l}`, color }]);
|
|
2817
3193
|
switch (it.kind) {
|
|
2818
3194
|
case "prompt":
|
|
2819
|
-
return [[{ text: t.session.you, color: C.coral, bold: true }, { text: ` ${clock(it.at)}`, color: C.faint }], ...body(it.text, C.fg)];
|
|
3195
|
+
return [[{ text: t.session.you, color: C.coral, bold: true }, { text: ` ${clock(it.at)}`, color: C.faint }], ...body(it.text, C.fg), ...(it.images ?? []).map((r) => imageLine(r, t, "o"))];
|
|
2820
3196
|
case "message":
|
|
2821
3197
|
return body(it.text, C.dim);
|
|
2822
3198
|
case "reply":
|
|
@@ -2833,8 +3209,9 @@ function itemLines(it, o) {
|
|
|
2833
3209
|
return [[{ text: ` ${g.message} `, color: C.violet }, { text: `${t.session.sentTo} `, color: C.dim }, { text: it.to, color: C.fg, bold: true }, { text: ` \xAB ${oneLine(it.summary ?? it.text)} \xBB`, color: C.dim }, { text: ` ${t.session.sent[it.status]}`, color: it.status === "failed" ? C.red : C.green }]];
|
|
2834
3210
|
case "received":
|
|
2835
3211
|
return [[{ text: ` ${g.message} `, color: C.violet }, { text: `${t.session.receivedFrom} `, color: C.dim }, { text: it.from, color: C.fg, bold: true }, { text: ` \xAB ${oneLine(it.text)} \xBB`, color: C.dim }]];
|
|
3212
|
+
// A task the session handed over (#92): its plain line says to whom; the summary alone is the title.
|
|
2836
3213
|
case "notice":
|
|
2837
|
-
return [[{ text: ` \xB7 ${oneLine(it.summary ?? it.text)}`, color: C.faint }]];
|
|
3214
|
+
return [[{ text: ` \xB7 ${oneLine(it.outgoing ? it.text : it.summary ?? it.text)}`, color: C.faint }]];
|
|
2838
3215
|
}
|
|
2839
3216
|
}
|
|
2840
3217
|
function foldedLine(turn, o) {
|
|
@@ -2857,21 +3234,25 @@ function threadLines(items, o) {
|
|
|
2857
3234
|
});
|
|
2858
3235
|
return out;
|
|
2859
3236
|
}
|
|
3237
|
+
function threadImages(items, unfolded) {
|
|
3238
|
+
const all = turns(items);
|
|
3239
|
+
return (unfolded ? all : all.slice(-1)).flatMap((turn) => turn.items.flatMap((it) => it.kind === "prompt" ? it.images ?? [] : []));
|
|
3240
|
+
}
|
|
2860
3241
|
|
|
2861
3242
|
// src/tui/ui/ModelPicker.tsx
|
|
2862
|
-
import { useState as
|
|
2863
|
-
import { Box as Box7, Text as
|
|
3243
|
+
import { useState as useState6 } from "react";
|
|
3244
|
+
import { Box as Box7, Text as Text8, useInput } from "ink";
|
|
2864
3245
|
|
|
2865
3246
|
// ../fleet/src/models.ts
|
|
2866
|
-
import { useEffect as
|
|
3247
|
+
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
2867
3248
|
var IDLE = { status: "idle", models: [] };
|
|
2868
3249
|
var LOADING = { status: "loading", models: [] };
|
|
2869
3250
|
var known = /* @__PURE__ */ new Map();
|
|
2870
3251
|
var next = 0;
|
|
2871
3252
|
function useModels(client, machine, tool) {
|
|
2872
3253
|
const key = machine === null || tool === null ? null : `${machine}|${tool}`;
|
|
2873
|
-
const [answer, setAnswer] =
|
|
2874
|
-
|
|
3254
|
+
const [answer, setAnswer] = useState5(null);
|
|
3255
|
+
useEffect5(() => {
|
|
2875
3256
|
if (key === null || machine === null || tool === null || known.has(key)) return;
|
|
2876
3257
|
const requestId = `models-${++next}`;
|
|
2877
3258
|
const off = client.onMessage((m) => {
|
|
@@ -2888,14 +3269,14 @@ function useModels(client, machine, tool) {
|
|
|
2888
3269
|
}
|
|
2889
3270
|
|
|
2890
3271
|
// src/tui/ui/ModelPicker.tsx
|
|
2891
|
-
import { jsx as
|
|
3272
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2892
3273
|
function ModelPicker({ machine, tool, current, onPick, onCancel }) {
|
|
2893
3274
|
const { client, t, g } = useTui();
|
|
2894
3275
|
useEditing(true);
|
|
2895
3276
|
const models = useModels(client, machine, tool);
|
|
2896
3277
|
const rows = [{ id: null, label: t.launch.defaultModel, efforts: [], defaultEffort: void 0 }, ...models.models.map((m) => ({ id: m.id, label: m.label, efforts: m.efforts ?? [], defaultEffort: m.defaultEffort }))];
|
|
2897
|
-
const [at, setAt] =
|
|
2898
|
-
const [effort, setEffort] =
|
|
3278
|
+
const [at, setAt] = useState6(() => Math.max(0, rows.findIndex((r) => r.id === current.model)));
|
|
3279
|
+
const [effort, setEffort] = useState6(current.effort);
|
|
2899
3280
|
const row = rows[Math.min(at, rows.length - 1)];
|
|
2900
3281
|
useInput((input, key) => {
|
|
2901
3282
|
if (key.escape) {
|
|
@@ -2919,16 +3300,16 @@ function ModelPicker({ machine, tool, current, onPick, onCancel }) {
|
|
|
2919
3300
|
}
|
|
2920
3301
|
if (key.return) onPick({ model: row.id, effort: row.efforts.length ? effort ?? null : null });
|
|
2921
3302
|
});
|
|
2922
|
-
if (models.status !== "ready") return /* @__PURE__ */
|
|
2923
|
-
return /* @__PURE__ */
|
|
2924
|
-
models.error ? /* @__PURE__ */
|
|
2925
|
-
rows.map((r, i) => /* @__PURE__ */
|
|
2926
|
-
/* @__PURE__ */
|
|
3303
|
+
if (models.status !== "ready") return /* @__PURE__ */ jsx9(Text8, { color: C.faint, children: t.launch.loading });
|
|
3304
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
3305
|
+
models.error ? /* @__PURE__ */ jsx9(Text8, { color: C.yellow, children: models.error }) : null,
|
|
3306
|
+
rows.map((r, i) => /* @__PURE__ */ jsxs7(Text8, { wrap: "truncate", backgroundColor: i === at ? C.sel : void 0, children: [
|
|
3307
|
+
/* @__PURE__ */ jsxs7(Text8, { color: i === at ? C.coral : C.faint, children: [
|
|
2927
3308
|
i === at ? g.cursor : " ",
|
|
2928
3309
|
" "
|
|
2929
3310
|
] }),
|
|
2930
|
-
/* @__PURE__ */
|
|
2931
|
-
i === at && r.efforts.length ? /* @__PURE__ */
|
|
3311
|
+
/* @__PURE__ */ jsx9(Text8, { color: C.fg, children: r.label }),
|
|
3312
|
+
i === at && r.efforts.length ? /* @__PURE__ */ jsxs7(Text8, { color: C.dim, children: [
|
|
2932
3313
|
" ",
|
|
2933
3314
|
r.efforts.map((e) => e === (effort ?? r.defaultEffort) ? `${g.prev} ${e} ${g.next}` : e).join(" ")
|
|
2934
3315
|
] }) : null
|
|
@@ -2937,24 +3318,24 @@ function ModelPicker({ machine, tool, current, onPick, onCancel }) {
|
|
|
2937
3318
|
}
|
|
2938
3319
|
|
|
2939
3320
|
// src/tui/ui/Lines.tsx
|
|
2940
|
-
import { useEffect as
|
|
2941
|
-
import { Box as Box8, Text as
|
|
2942
|
-
import { jsx as
|
|
3321
|
+
import { useEffect as useEffect6, useRef as useRef7 } from "react";
|
|
3322
|
+
import { Box as Box8, Text as Text9, useBoxMetrics as useBoxMetrics3 } from "ink";
|
|
3323
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2943
3324
|
function Lines({ lines, up = 0, onHeight }) {
|
|
2944
|
-
const ref =
|
|
3325
|
+
const ref = useRef7(null);
|
|
2945
3326
|
const { height } = useBoxMetrics3(ref);
|
|
2946
|
-
|
|
3327
|
+
useEffect6(() => {
|
|
2947
3328
|
if (height > 0) onHeight?.(height);
|
|
2948
3329
|
}, [height, onHeight]);
|
|
2949
3330
|
const h = Math.max(1, height);
|
|
2950
3331
|
const back = Math.min(up, Math.max(0, lines.length - h));
|
|
2951
3332
|
const shown = lines.slice(Math.max(0, lines.length - h - back), lines.length - back);
|
|
2952
|
-
return /* @__PURE__ */
|
|
3333
|
+
return /* @__PURE__ */ jsx10(Box8, { ref, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: shown.map((l, i) => /* @__PURE__ */ jsx10(Box8, { flexShrink: 0, children: /* @__PURE__ */ jsx10(Text9, { wrap: "truncate", children: l.map((x, k) => /* @__PURE__ */ jsx10(Text9, { color: x.color, bold: x.bold, children: x.text || (k === 0 ? " " : "") }, k)) }) }, i)) });
|
|
2953
3334
|
}
|
|
2954
3335
|
|
|
2955
3336
|
// src/tui/ui/TextInput.tsx
|
|
2956
|
-
import { Text as
|
|
2957
|
-
import { jsx as
|
|
3337
|
+
import { Text as Text10, useInput as useInput2 } from "ink";
|
|
3338
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2958
3339
|
function TextInput({ value, onChange, onSubmit, onEscape, placeholder, active, mask }) {
|
|
2959
3340
|
useInput2((input, key) => {
|
|
2960
3341
|
if (key.return) {
|
|
@@ -2977,11 +3358,11 @@ function TextInput({ value, onChange, onSubmit, onEscape, placeholder, active, m
|
|
|
2977
3358
|
if (input) onChange(value + input.replace(/\r\n?/g, "\n"));
|
|
2978
3359
|
}, { isActive: active });
|
|
2979
3360
|
const shown = mask ? "\u2022".repeat(value.length) : value.replace(/\n/g, "\u23CE");
|
|
2980
|
-
if (!shown && !active) return /* @__PURE__ */
|
|
2981
|
-
return /* @__PURE__ */
|
|
2982
|
-
shown ? /* @__PURE__ */
|
|
2983
|
-
active ? /* @__PURE__ */
|
|
2984
|
-
!shown && placeholder ? /* @__PURE__ */
|
|
3361
|
+
if (!shown && !active) return /* @__PURE__ */ jsx11(Text10, { color: C.faint, wrap: "truncate", children: placeholder ?? "" });
|
|
3362
|
+
return /* @__PURE__ */ jsxs8(Text10, { wrap: "truncate-start", children: [
|
|
3363
|
+
shown ? /* @__PURE__ */ jsx11(Text10, { color: C.fg, children: shown }) : null,
|
|
3364
|
+
active ? /* @__PURE__ */ jsx11(Text10, { inverse: true, children: " " }) : null,
|
|
3365
|
+
!shown && placeholder ? /* @__PURE__ */ jsxs8(Text10, { color: C.faint, children: [
|
|
2985
3366
|
" ",
|
|
2986
3367
|
placeholder
|
|
2987
3368
|
] }) : null
|
|
@@ -2989,47 +3370,71 @@ function TextInput({ value, onChange, onSubmit, onEscape, placeholder, active, m
|
|
|
2989
3370
|
}
|
|
2990
3371
|
|
|
2991
3372
|
// src/tui/screens/Session.tsx
|
|
2992
|
-
import { Fragment as Fragment3, jsx as
|
|
3373
|
+
import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2993
3374
|
function SessionScreen() {
|
|
2994
3375
|
const tui = useTui();
|
|
2995
3376
|
const { selected: s, t } = tui;
|
|
2996
|
-
if (!s) return /* @__PURE__ */
|
|
2997
|
-
return /* @__PURE__ */
|
|
3377
|
+
if (!s) return /* @__PURE__ */ jsx12(Frame, { crumb: "", keys: [["esc", t.keys.back]], children: /* @__PURE__ */ jsx12(Text11, { color: C.faint, children: t.session.noSession }) });
|
|
3378
|
+
return /* @__PURE__ */ jsx12(SessionView, { s }, s.id);
|
|
2998
3379
|
}
|
|
2999
3380
|
function SessionView({ s }) {
|
|
3000
3381
|
const { client, d, t, g, now, actions, attach, toast, machines } = useTui();
|
|
3001
3382
|
const { columns } = useWindowSize3();
|
|
3002
|
-
const [mode, setMode] =
|
|
3003
|
-
const [draft, setDraft] =
|
|
3004
|
-
const [diffs, setDiffs] =
|
|
3005
|
-
const [unfolded, setUnfolded] =
|
|
3006
|
-
const [terminal, setTerminal] =
|
|
3007
|
-
const [up, setUp] =
|
|
3383
|
+
const [mode, setMode] = useState7("read");
|
|
3384
|
+
const [draft, setDraft] = useState7("");
|
|
3385
|
+
const [diffs, setDiffs] = useState7(false);
|
|
3386
|
+
const [unfolded, setUnfolded] = useState7(false);
|
|
3387
|
+
const [terminal, setTerminal] = useState7(false);
|
|
3388
|
+
const [up, setUp] = useState7(0);
|
|
3008
3389
|
useEditing(mode === "reply" || mode === "note");
|
|
3009
3390
|
const items = d.threads.get(s.id);
|
|
3010
|
-
|
|
3391
|
+
useEffect7(() => {
|
|
3011
3392
|
if (!items && !s.offline) client.send({ type: "session.thread", id: s.id });
|
|
3012
3393
|
}, [items === void 0, s.id, d.snapshotSeq]);
|
|
3013
3394
|
const width = Math.max(20, columns - 6);
|
|
3014
3395
|
const lines = useMemo3(() => items ? threadLines(items, { t, g, now, width, diffs, unfolded }) : [], [items, t, g, now, width, diffs, unfolded]);
|
|
3015
3396
|
const p = s.pending;
|
|
3016
|
-
const [view, setView] =
|
|
3397
|
+
const [view, setView] = useState7(10);
|
|
3017
3398
|
const maxUp = Math.max(0, lines.length - view);
|
|
3399
|
+
const images = useMemo3(() => (items ? threadImages(items, unfolded) : []).map((ref) => ({ ref, route: { id: s.id } })), [items, unfolded, s.id]);
|
|
3400
|
+
const chips = useChips((image) => putImage(client, image, { id: s.id }));
|
|
3401
|
+
const paste = usePaste(chips);
|
|
3402
|
+
const openImage2 = useOpenImage();
|
|
3018
3403
|
const machine = splitId(s.id)?.machine ?? "local";
|
|
3019
3404
|
const canSwitch = machines.find((m) => m.id === machine)?.daemon?.modelSwitch === true;
|
|
3020
3405
|
const send = (text) => {
|
|
3021
|
-
if (
|
|
3022
|
-
|
|
3023
|
-
|
|
3406
|
+
if (mode === "note") {
|
|
3407
|
+
if (!text.trim()) {
|
|
3408
|
+
setMode("read");
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
actions.denyWithNote(s, text.trim(), (x) => setDraft(x));
|
|
3412
|
+
} else {
|
|
3413
|
+
if (!text.trim() && chips.items.length === 0) {
|
|
3414
|
+
setMode("read");
|
|
3415
|
+
return;
|
|
3416
|
+
}
|
|
3417
|
+
if (chips.items.length && !chips.ready) return;
|
|
3418
|
+
if (chips.items.length && p?.kind === "question") {
|
|
3419
|
+
toast({ text: t.images.dialog, tone: "info" });
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
const taken = chips.clear();
|
|
3423
|
+
if (!actions.reply(s, text, (x) => {
|
|
3424
|
+
setDraft(x);
|
|
3425
|
+
chips.putBack(taken);
|
|
3426
|
+
}, { attachments: taken.flatMap((c) => c.id ? [c.id] : []) })) {
|
|
3427
|
+
chips.putBack(taken);
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3024
3430
|
}
|
|
3025
|
-
const back = (x) => {
|
|
3026
|
-
setDraft(x);
|
|
3027
|
-
};
|
|
3028
|
-
if (mode === "note") actions.denyWithNote(s, text.trim(), back);
|
|
3029
|
-
else if (!actions.reply(s, text, back)) return;
|
|
3030
3431
|
setDraft("");
|
|
3031
3432
|
setMode("read");
|
|
3032
3433
|
};
|
|
3434
|
+
useInput3((input, key) => {
|
|
3435
|
+
if (key.meta && input === "v") paste();
|
|
3436
|
+
else if ((key.backspace || key.delete) && draft === "") chips.removeLast();
|
|
3437
|
+
}, { isActive: mode === "reply" });
|
|
3033
3438
|
useScreenKeys((input, key) => {
|
|
3034
3439
|
if (mode === "model") return true;
|
|
3035
3440
|
if (mode !== "read") {
|
|
@@ -3071,6 +3476,10 @@ function SessionView({ s }) {
|
|
|
3071
3476
|
setTerminal((x) => !x);
|
|
3072
3477
|
return true;
|
|
3073
3478
|
}
|
|
3479
|
+
if (input === "o" && images.length) {
|
|
3480
|
+
openImage2(images);
|
|
3481
|
+
return true;
|
|
3482
|
+
}
|
|
3074
3483
|
if (input === "a") {
|
|
3075
3484
|
attach(s);
|
|
3076
3485
|
return true;
|
|
@@ -3108,9 +3517,9 @@ function SessionView({ s }) {
|
|
|
3108
3517
|
});
|
|
3109
3518
|
const state2 = botState(s);
|
|
3110
3519
|
const title2 = `${s.name} \xB7 ${oneLine(s.topic ?? s.title)} \xB7 ${TOOL_NAME[s.tool]} \xB7 ${modelLabel(s, t.session.defaultModel)}`;
|
|
3111
|
-
const keys = mode === "model" ? [["\u2191 \u2193", t.keys.choose], ["\u2190 \u2192", t.launch.effort], ["\u21B5", t.keys.choose], ["esc", t.keys.cancel]] : mode !== "read" ? [["\u21B5", t.keys.send], ["esc", t.keys.back]] : [["esc", t.keys.back], ...p ? [["y n A", t.keys.answer]] : [], ["r", t.keys.write], ["j k", t.keys.scroll], [t.keys.space, t.keys.unfold], ["d", t.keys.diffs], ["t", t.keys.terminal], ["a", t.keys.attach], ["m", t.keys.model], ["i", t.keys.interrupt]];
|
|
3112
|
-
return /* @__PURE__ */
|
|
3113
|
-
/* @__PURE__ */
|
|
3520
|
+
const keys = mode === "model" ? [["\u2191 \u2193", t.keys.choose], ["\u2190 \u2192", t.launch.effort], ["\u21B5", t.keys.choose], ["esc", t.keys.cancel]] : mode !== "read" ? [["\u21B5", t.keys.send], ...mode === "reply" ? [["alt+v", t.keys.image]] : [], ["esc", t.keys.back]] : [["esc", t.keys.back], ...p ? [["y n A", t.keys.answer]] : [], ["r", t.keys.write], ["j k", t.keys.scroll], [t.keys.space, t.keys.unfold], ["d", t.keys.diffs], ["t", t.keys.terminal], ["a", t.keys.attach], ["m", t.keys.model], ["i", t.keys.interrupt], ...images.length ? [["o", t.keys.openImage]] : []];
|
|
3521
|
+
return /* @__PURE__ */ jsxs9(Frame, { crumb: `${s.repo ?? s.project}${s.branch ? ` \u203A ${g.branch} ${s.branch}` : ""} \u203A ${s.name} \xB7 ${machineLabel(s)}`, keys, children: [
|
|
3522
|
+
/* @__PURE__ */ jsx12(TitledBox, { title: terminal ? t.overview.terminal(paneName(s)) : title2, flexGrow: 1, children: mode === "model" ? /* @__PURE__ */ jsx12(
|
|
3114
3523
|
ModelPicker,
|
|
3115
3524
|
{
|
|
3116
3525
|
machine,
|
|
@@ -3123,47 +3532,50 @@ function SessionView({ s }) {
|
|
|
3123
3532
|
setMode("read");
|
|
3124
3533
|
}
|
|
3125
3534
|
}
|
|
3126
|
-
) : terminal ? /* @__PURE__ */
|
|
3127
|
-
p ? /* @__PURE__ */
|
|
3128
|
-
/* @__PURE__ */
|
|
3129
|
-
/* @__PURE__ */
|
|
3130
|
-
p.kind !== "question" ? /* @__PURE__ */
|
|
3131
|
-
p.tool ? /* @__PURE__ */
|
|
3535
|
+
) : terminal ? /* @__PURE__ */ jsx12(Tail, { lines: d.previews.get(s.id) ?? [], color: C.dim, empty: t.overview.noPreview, emptyColor: C.faint }) : !items ? /* @__PURE__ */ jsx12(Text11, { color: C.faint, children: t.session.loading }) : lines.length === 0 ? /* @__PURE__ */ jsx12(Text11, { color: C.faint, children: t.session.empty }) : /* @__PURE__ */ jsx12(Lines, { lines, up, onHeight: setView }) }),
|
|
3536
|
+
p ? /* @__PURE__ */ jsxs9(TitledBox, { title: t.session.since(t.state[state2], ago(s.lastActivityAt, now, t.time)), color: isPending(s) ? C.red : C.line, titleColor: STATE_COLOR[state2], children: [
|
|
3537
|
+
/* @__PURE__ */ jsxs9(Text11, { wrap: "truncate", children: [
|
|
3538
|
+
/* @__PURE__ */ jsx12(Text11, { color: C.fg, children: p.kind === "question" ? oneLine(p.text) : `${t.session.wantsTo(s.name)} ` }),
|
|
3539
|
+
p.kind !== "question" ? /* @__PURE__ */ jsx12(Text11, { color: C.fg, bold: true, children: oneLine(p.text) }) : null,
|
|
3540
|
+
p.tool ? /* @__PURE__ */ jsxs9(Text11, { color: C.faint, children: [
|
|
3132
3541
|
" ",
|
|
3133
3542
|
p.tool
|
|
3134
3543
|
] }) : null
|
|
3135
3544
|
] }),
|
|
3136
|
-
/* @__PURE__ */
|
|
3137
|
-
/* @__PURE__ */
|
|
3138
|
-
/* @__PURE__ */
|
|
3139
|
-
offers(s.tool, p.kind, "always") ? /* @__PURE__ */
|
|
3140
|
-
offers(s.tool, p.kind, "note") ? /* @__PURE__ */
|
|
3545
|
+
/* @__PURE__ */ jsx12(Box9, { marginTop: 1, columnGap: 2, flexWrap: "wrap", children: p.kind === "question" ? choices(p).slice(0, 9).map((c, i) => /* @__PURE__ */ jsx12(Button, { label: `${c.n} \xB7 ${c.label}`, k: String(c.n), primary: i === 0 }, c.n)) : /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
3546
|
+
/* @__PURE__ */ jsx12(Button, { label: t.overview.allow, k: "y", primary: true }),
|
|
3547
|
+
/* @__PURE__ */ jsx12(Button, { label: t.overview.deny, k: "n" }),
|
|
3548
|
+
offers(s.tool, p.kind, "always") ? /* @__PURE__ */ jsx12(Button, { label: t.overview.always, k: "A" }) : null,
|
|
3549
|
+
offers(s.tool, p.kind, "note") ? /* @__PURE__ */ jsx12(Button, { label: t.overview.denyWithNote, k: "r" }) : null
|
|
3141
3550
|
] }) })
|
|
3142
3551
|
] }) : null,
|
|
3143
|
-
/* @__PURE__ */
|
|
3144
|
-
/* @__PURE__ */
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
{
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3552
|
+
/* @__PURE__ */ jsxs9(TitledBox, { title: mode === "note" ? t.overview.denyWithNote : t.keys.write, color: mode === "read" ? C.line : C.coral, children: [
|
|
3553
|
+
/* @__PURE__ */ jsx12(ChipsLine, { chips: chips.items }),
|
|
3554
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3555
|
+
/* @__PURE__ */ jsxs9(Text11, { color: C.coral, children: [
|
|
3556
|
+
g.cursor,
|
|
3557
|
+
" "
|
|
3558
|
+
] }),
|
|
3559
|
+
/* @__PURE__ */ jsx12(Box9, { flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx12(
|
|
3560
|
+
TextInput,
|
|
3561
|
+
{
|
|
3562
|
+
value: draft,
|
|
3563
|
+
onChange: setDraft,
|
|
3564
|
+
onSubmit: send,
|
|
3565
|
+
onEscape: () => setMode("read"),
|
|
3566
|
+
active: mode === "reply" || mode === "note",
|
|
3567
|
+
placeholder: mode === "note" ? t.session.noteFor(s.name) : t.session.replyTo(s.name)
|
|
3568
|
+
}
|
|
3569
|
+
) })
|
|
3570
|
+
] })
|
|
3571
|
+
] })
|
|
3160
3572
|
] });
|
|
3161
3573
|
}
|
|
3162
3574
|
|
|
3163
3575
|
// src/tui/screens/Ask.tsx
|
|
3164
3576
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3165
|
-
import { useEffect as
|
|
3166
|
-
import { Box as Box10, Text as
|
|
3577
|
+
import { useEffect as useEffect8, useMemo as useMemo4, useRef as useRef8, useState as useState8 } from "react";
|
|
3578
|
+
import { Box as Box10, Text as Text12, useInput as useInput4, useWindowSize as useWindowSize4 } from "ink";
|
|
3167
3579
|
|
|
3168
3580
|
// src/tui/model/chat.ts
|
|
3169
3581
|
function stepLabel(s, t) {
|
|
@@ -3177,6 +3589,7 @@ function chatLines(turns2, live, o) {
|
|
|
3177
3589
|
if (out.length) out.push([{ text: "" }]);
|
|
3178
3590
|
for (const l of wrap(turn.question, width - 2)) out.push([{ text: `${g.cursor} `, color: C.coral }, { text: l, color: C.fg, bold: true }]);
|
|
3179
3591
|
out.push([{ text: ` ${t.ask.you} \xB7 ${clock(turn.askedAt)}`, color: C.faint }]);
|
|
3592
|
+
for (const a of turn.attachments ?? []) out.push(imageLine(a, t, "alt+o"));
|
|
3180
3593
|
const steps = [...new Set(turn.steps.map((s) => stepLabel(s, t.ask.steps)))];
|
|
3181
3594
|
if (steps.length) out.push([{ text: ` ${t.ask.read} : ${steps.join(" \xB7 ")}`, color: C.faint }]);
|
|
3182
3595
|
const text = turn.answer ?? (live?.qid === turn.qid ? live.text : void 0);
|
|
@@ -3197,8 +3610,8 @@ function openCards(turns2) {
|
|
|
3197
3610
|
if (turn.team && !turn.team.teamId) out.push({ kind: "team", qid: turn.qid });
|
|
3198
3611
|
const tasks = (turn.tasks ?? []).map((c, i) => c.taskId ? -1 : i).filter((i) => i >= 0);
|
|
3199
3612
|
if (tasks.length) out.push({ kind: "task", qid: turn.qid, rows: tasks });
|
|
3200
|
-
const
|
|
3201
|
-
if (
|
|
3613
|
+
const launch2 = proposalRows(turn).map((p, i) => p.launchedId ? -1 : i).filter((i) => i >= 0);
|
|
3614
|
+
if (launch2.length) out.push({ kind: "launch", qid: turn.qid, rows: launch2 });
|
|
3202
3615
|
if (turn.relay && turn.relay.status !== "sent") out.push({ kind: "relay", qid: turn.qid });
|
|
3203
3616
|
const close = (turn.closes ?? []).map((c, i) => c.closedAt ? -1 : i).filter((i) => i >= 0);
|
|
3204
3617
|
if (close.length) out.push({ kind: "close", qid: turn.qid, rows: close });
|
|
@@ -3207,20 +3620,34 @@ function openCards(turns2) {
|
|
|
3207
3620
|
}
|
|
3208
3621
|
|
|
3209
3622
|
// src/tui/screens/Ask.tsx
|
|
3210
|
-
import { jsx as
|
|
3623
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3211
3624
|
var CARD_MS = 2e4;
|
|
3212
3625
|
function Ask() {
|
|
3213
3626
|
const { client, d, machines, t, g, toast, go } = useTui();
|
|
3214
3627
|
const { columns } = useWindowSize4();
|
|
3215
|
-
const [draft, setDraft] =
|
|
3216
|
-
const [focus, setFocus] =
|
|
3217
|
-
const
|
|
3628
|
+
const [draft, setDraft] = useState8("");
|
|
3629
|
+
const [focus, setFocus] = useState8(-1);
|
|
3630
|
+
const chips = useChips(null);
|
|
3631
|
+
const paste = usePaste(chips);
|
|
3632
|
+
const openImage2 = useOpenImage();
|
|
3633
|
+
const [busy, setBusy] = useState8(null);
|
|
3218
3634
|
useEditing(focus === -1);
|
|
3635
|
+
const sentAsk = useRef8(null);
|
|
3636
|
+
useEffect8(() => {
|
|
3637
|
+
const s = sentAsk.current;
|
|
3638
|
+
const turn = s && d.chat.find((x) => x.qid === s.qid);
|
|
3639
|
+
if (!s || !turn) return;
|
|
3640
|
+
if (turn.error) {
|
|
3641
|
+
sentAsk.current = null;
|
|
3642
|
+
chips.putBack(s.taken);
|
|
3643
|
+
setDraft((x) => x || s.text);
|
|
3644
|
+
} else if (turn.answeredAt) sentAsk.current = null;
|
|
3645
|
+
}, [d.chat]);
|
|
3219
3646
|
const cards = useMemo4(() => openCards(d.chat), [d.chat]);
|
|
3220
|
-
|
|
3647
|
+
useEffect8(() => {
|
|
3221
3648
|
if (focus >= cards.length) setFocus(cards.length ? cards.length - 1 : -1);
|
|
3222
3649
|
}, [cards.length, focus]);
|
|
3223
|
-
|
|
3650
|
+
useEffect8(() => {
|
|
3224
3651
|
if (!busy) return;
|
|
3225
3652
|
const turn = d.chat.find((x) => x.qid === busy.qid);
|
|
3226
3653
|
if (turn !== busy.turn || (d.errors.at(-1)?.seq ?? 0) > busy.errorSeq) {
|
|
@@ -3233,14 +3660,16 @@ function Ask() {
|
|
|
3233
3660
|
const local = machines.find((m) => m.id === "local");
|
|
3234
3661
|
const asking = d.chat.find((x) => !x.answeredAt && !x.error);
|
|
3235
3662
|
const ask = (text) => {
|
|
3236
|
-
|
|
3663
|
+
const images = chips.uploads();
|
|
3664
|
+
if (!text.trim() && images.length === 0) return;
|
|
3237
3665
|
if (!local?.ready) {
|
|
3238
3666
|
toast({ text: t.ask.offline, tone: "info" });
|
|
3239
3667
|
return;
|
|
3240
3668
|
}
|
|
3241
3669
|
const qid = `q-${randomUUID2()}`;
|
|
3242
3670
|
daemonStore.beginTurn({ qid, question: text.trim(), askedAt: (/* @__PURE__ */ new Date()).toISOString(), steps: [] });
|
|
3243
|
-
client.send({ type: "chat.ask", qid, text: text.trim() });
|
|
3671
|
+
client.send({ type: "chat.ask", qid, text: text.trim(), ...images.length ? { images } : {} });
|
|
3672
|
+
sentAsk.current = { qid, taken: chips.clear(), text: text.trim() };
|
|
3244
3673
|
setDraft("");
|
|
3245
3674
|
};
|
|
3246
3675
|
const act = (card) => {
|
|
@@ -3268,13 +3697,26 @@ function Ask() {
|
|
|
3268
3697
|
} else if (card.kind === "relay") client.send({ type: turn.relay?.status === "queued" ? "session.relay.cancel" : "session.relay", qid: card.qid });
|
|
3269
3698
|
else client.send({ type: "session.close", qid: card.qid, rows: card.rows });
|
|
3270
3699
|
};
|
|
3271
|
-
const latest =
|
|
3700
|
+
const latest = useRef8({ asking, cards, focus });
|
|
3272
3701
|
latest.current = { asking, cards, focus };
|
|
3273
|
-
|
|
3702
|
+
const turnImages = useMemo4(() => d.chat.flatMap((x) => (x.attachments ?? []).map((ref) => ({ ref, route: { machine: x.machineId } }))), [d.chat]);
|
|
3703
|
+
useInput4((input, key) => {
|
|
3274
3704
|
if (key.ctrl && input === "n") {
|
|
3275
3705
|
client.send({ type: "chat.reset" });
|
|
3276
3706
|
return;
|
|
3277
3707
|
}
|
|
3708
|
+
if (key.meta && input === "v" && latest.current.focus === -1) {
|
|
3709
|
+
paste();
|
|
3710
|
+
return;
|
|
3711
|
+
}
|
|
3712
|
+
if ((key.backspace || key.delete) && latest.current.focus === -1 && draft === "") {
|
|
3713
|
+
chips.removeLast();
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
if (key.meta && input === "o" && turnImages.length) {
|
|
3717
|
+
openImage2(turnImages);
|
|
3718
|
+
return;
|
|
3719
|
+
}
|
|
3278
3720
|
if (key.tab) {
|
|
3279
3721
|
const n = latest.current.cards.length;
|
|
3280
3722
|
setFocus((f) => n === 0 ? -1 : f + 1 >= n ? -1 : f + 1);
|
|
@@ -3307,18 +3749,21 @@ function Ask() {
|
|
|
3307
3749
|
const sessions = listed(d.sessions).length;
|
|
3308
3750
|
const width = Math.max(20, columns - 6);
|
|
3309
3751
|
const lines = useMemo4(() => chatLines(d.chat, d.chatLive, { t, g, width }), [d.chat, d.chatLive, t, g, width]);
|
|
3310
|
-
return /* @__PURE__ */
|
|
3311
|
-
/* @__PURE__ */
|
|
3312
|
-
cards.slice(0, 2).map((c, i) => /* @__PURE__ */
|
|
3313
|
-
/* @__PURE__ */
|
|
3314
|
-
/* @__PURE__ */
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3752
|
+
return /* @__PURE__ */ jsxs10(Frame, { crumb: `${t.tabs.ask}`, keys: [["\u21B5", t.keys.send], ["alt+v", t.keys.image], ...turnImages.length ? [["alt+o", t.keys.openImage]] : [], ["tab", t.keys.nextCard], ["s", t.keys.save], ["ctrl+n", t.keys.newChat], ["ctrl+c", t.keys.stopAnswer], ["esc", t.keys.back]], children: [
|
|
3753
|
+
/* @__PURE__ */ jsx13(TitledBox, { title: t.ask.operator(sessions, Math.max(1, machines.length)), flexGrow: 1, children: lines.length === 0 ? /* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.ask.empty }) : /* @__PURE__ */ jsx13(Lines, { lines }) }),
|
|
3754
|
+
cards.slice(0, 2).map((c, i) => /* @__PURE__ */ jsx13(CardBox, { card: c, turn: d.chat.find((x) => x.qid === c.qid), focused: i === focus, busy: busy?.qid === c.qid }, `${c.kind}:${c.qid}`)),
|
|
3755
|
+
/* @__PURE__ */ jsxs10(TitledBox, { title: t.ask.question, flexShrink: 0, color: focus === -1 ? C.coral : C.line, children: [
|
|
3756
|
+
/* @__PURE__ */ jsx13(ChipsLine, { chips: chips.items }),
|
|
3757
|
+
/* @__PURE__ */ jsxs10(Box10, { children: [
|
|
3758
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.coral, children: [
|
|
3759
|
+
g.cursor,
|
|
3760
|
+
" "
|
|
3761
|
+
] }),
|
|
3762
|
+
/* @__PURE__ */ jsx13(Box10, { flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx13(TextInput, { value: draft, onChange: setDraft, onSubmit: ask, onEscape: () => {
|
|
3763
|
+
if (!draft) go("overview");
|
|
3764
|
+
}, active: focus === -1 }) })
|
|
3765
|
+
] })
|
|
3766
|
+
] })
|
|
3322
3767
|
] });
|
|
3323
3768
|
}
|
|
3324
3769
|
function CardBox({ card, turn, focused, busy }) {
|
|
@@ -3327,120 +3772,120 @@ function CardBox({ card, turn, focused, busy }) {
|
|
|
3327
3772
|
if (card.kind === "team") {
|
|
3328
3773
|
const team = turn.team;
|
|
3329
3774
|
const auto = team.seats.filter((x) => x.autonomy === "auto").map((x) => `@${x.role}`);
|
|
3330
|
-
return /* @__PURE__ */
|
|
3331
|
-
/* @__PURE__ */
|
|
3332
|
-
/* @__PURE__ */
|
|
3333
|
-
/* @__PURE__ */
|
|
3775
|
+
return /* @__PURE__ */ jsxs10(TitledBox, { title: t.ask.teamCard(team.name), flexShrink: 0, color, titleColor: C.coral, children: [
|
|
3776
|
+
/* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", children: [
|
|
3777
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.dim, children: t.ask.folder.padEnd(13) }),
|
|
3778
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: team.cwd })
|
|
3334
3779
|
] }),
|
|
3335
|
-
/* @__PURE__ */
|
|
3336
|
-
/* @__PURE__ */
|
|
3780
|
+
/* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", children: [
|
|
3781
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.dim, children: t.ask.table.padEnd(13) }),
|
|
3337
3782
|
g.team,
|
|
3338
3783
|
" ",
|
|
3339
3784
|
t.ask.newTable(team.projectName ?? team.project)
|
|
3340
3785
|
] }),
|
|
3341
|
-
team.seats.map((x) => /* @__PURE__ */
|
|
3342
|
-
/* @__PURE__ */
|
|
3343
|
-
/* @__PURE__ */
|
|
3786
|
+
team.seats.map((x) => /* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", children: [
|
|
3787
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.dim, children: "".padEnd(13) }),
|
|
3788
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.violet, children: [
|
|
3344
3789
|
"@",
|
|
3345
3790
|
x.role
|
|
3346
3791
|
] }),
|
|
3347
3792
|
" ",
|
|
3348
3793
|
x.label,
|
|
3349
3794
|
" ",
|
|
3350
|
-
/* @__PURE__ */
|
|
3795
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: [TOOL_NAME[x.tool], x.model, x.effort].filter(Boolean).join(" \xB7 ") }),
|
|
3351
3796
|
" ",
|
|
3352
|
-
/* @__PURE__ */
|
|
3797
|
+
/* @__PURE__ */ jsx13(Text12, { color: x.autonomy === "auto" ? C.yellow : C.faint, children: t.ask.autonomy[x.autonomy ?? "ask"] })
|
|
3353
3798
|
] }, x.role)),
|
|
3354
|
-
auto.length ? /* @__PURE__ */
|
|
3355
|
-
team.error ? /* @__PURE__ */
|
|
3356
|
-
/* @__PURE__ */
|
|
3357
|
-
/* @__PURE__ */
|
|
3358
|
-
!team.template ? team.savedPath ? /* @__PURE__ */
|
|
3359
|
-
/* @__PURE__ */
|
|
3799
|
+
auto.length ? /* @__PURE__ */ jsx13(Text12, { color: C.yellow, wrap: "truncate", children: t.ask.autoWarning(auto.join(", ")) }) : null,
|
|
3800
|
+
team.error ? /* @__PURE__ */ jsx13(Text12, { color: C.red, wrap: "truncate", children: team.error }) : null,
|
|
3801
|
+
/* @__PURE__ */ jsxs10(Box10, { columnGap: 2, children: [
|
|
3802
|
+
/* @__PURE__ */ jsx13(Button, { label: busy ? `${g.live} ${t.ask.launchTeam}` : t.ask.launchTeam, k: "\u21B5", primary: focused }),
|
|
3803
|
+
!team.template ? team.savedPath ? /* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.ask.saved }) : /* @__PURE__ */ jsx13(Button, { label: t.ask.save, k: "s" }) : null,
|
|
3804
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.ask.nothingWithoutYou })
|
|
3360
3805
|
] })
|
|
3361
3806
|
] });
|
|
3362
3807
|
}
|
|
3363
3808
|
if (card.kind === "task") {
|
|
3364
3809
|
const tasks = turn.tasks;
|
|
3365
3810
|
const waits = !!turn.team && !turn.team.teamId;
|
|
3366
|
-
return /* @__PURE__ */
|
|
3811
|
+
return /* @__PURE__ */ jsxs10(TitledBox, { title: t.ask.taskCard, flexShrink: 0, color, titleColor: C.violet, children: [
|
|
3367
3812
|
card.rows.map((r) => {
|
|
3368
3813
|
const c = tasks[r];
|
|
3369
|
-
return /* @__PURE__ */
|
|
3370
|
-
/* @__PURE__ */
|
|
3814
|
+
return /* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", children: [
|
|
3815
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.fg, children: c.title }),
|
|
3371
3816
|
" ",
|
|
3372
|
-
/* @__PURE__ */
|
|
3373
|
-
c.error ? /* @__PURE__ */
|
|
3817
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: c.toName ?? c.to }),
|
|
3818
|
+
c.error ? /* @__PURE__ */ jsxs10(Text12, { color: C.red, children: [
|
|
3374
3819
|
" ",
|
|
3375
3820
|
c.error
|
|
3376
3821
|
] }) : null
|
|
3377
3822
|
] }, r);
|
|
3378
3823
|
}),
|
|
3379
|
-
/* @__PURE__ */
|
|
3380
|
-
/* @__PURE__ */
|
|
3381
|
-
waits ? /* @__PURE__ */
|
|
3824
|
+
/* @__PURE__ */ jsxs10(Box10, { columnGap: 2, children: [
|
|
3825
|
+
/* @__PURE__ */ jsx13(Button, { label: busy ? `${g.live} ${t.ask.give}` : t.ask.give, k: "\u21B5", primary: focused && !waits }),
|
|
3826
|
+
waits ? /* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.ask.waitTeam }) : null
|
|
3382
3827
|
] })
|
|
3383
3828
|
] });
|
|
3384
3829
|
}
|
|
3385
3830
|
if (card.kind === "launch") {
|
|
3386
3831
|
const rows = proposalRows(turn);
|
|
3387
|
-
return /* @__PURE__ */
|
|
3832
|
+
return /* @__PURE__ */ jsxs10(TitledBox, { title: t.ask.launchCard, flexShrink: 0, color, titleColor: C.coral, children: [
|
|
3388
3833
|
card.rows.map((r) => {
|
|
3389
3834
|
const p = rows[r];
|
|
3390
|
-
return /* @__PURE__ */
|
|
3391
|
-
/* @__PURE__ */
|
|
3392
|
-
/* @__PURE__ */
|
|
3393
|
-
/* @__PURE__ */
|
|
3835
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
3836
|
+
/* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", children: [
|
|
3837
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.dim, children: t.ask.folder.padEnd(13) }),
|
|
3838
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.fg, children: p.project }),
|
|
3394
3839
|
" ",
|
|
3395
|
-
/* @__PURE__ */
|
|
3840
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: p.cwd })
|
|
3396
3841
|
] }),
|
|
3397
|
-
/* @__PURE__ */
|
|
3398
|
-
/* @__PURE__ */
|
|
3842
|
+
/* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", children: [
|
|
3843
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.dim, children: t.ask.agent.padEnd(13) }),
|
|
3399
3844
|
TOOL_NAME[p.tool]
|
|
3400
3845
|
] }),
|
|
3401
|
-
p.prompt ? /* @__PURE__ */
|
|
3402
|
-
/* @__PURE__ */
|
|
3846
|
+
p.prompt ? /* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", children: [
|
|
3847
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.dim, children: t.ask.instruction.padEnd(13) }),
|
|
3403
3848
|
oneLine(p.prompt)
|
|
3404
3849
|
] }) : null,
|
|
3405
|
-
p.error ? /* @__PURE__ */
|
|
3850
|
+
p.error ? /* @__PURE__ */ jsx13(Text12, { color: C.red, wrap: "truncate", children: p.error }) : null
|
|
3406
3851
|
] }, r);
|
|
3407
3852
|
}),
|
|
3408
|
-
/* @__PURE__ */
|
|
3409
|
-
/* @__PURE__ */
|
|
3410
|
-
/* @__PURE__ */
|
|
3411
|
-
/* @__PURE__ */
|
|
3853
|
+
/* @__PURE__ */ jsxs10(Box10, { columnGap: 2, children: [
|
|
3854
|
+
/* @__PURE__ */ jsx13(Button, { label: busy ? `${g.live} ${t.ask.launch}` : t.ask.launch, k: "\u21B5", primary: focused }),
|
|
3855
|
+
/* @__PURE__ */ jsx13(Button, { label: t.ask.ignore, k: "esc" }),
|
|
3856
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.ask.nothingWithoutYou })
|
|
3412
3857
|
] })
|
|
3413
3858
|
] });
|
|
3414
3859
|
}
|
|
3415
3860
|
if (card.kind === "relay") {
|
|
3416
3861
|
const r = turn.relay;
|
|
3417
|
-
return /* @__PURE__ */
|
|
3418
|
-
/* @__PURE__ */
|
|
3419
|
-
r.error ? /* @__PURE__ */
|
|
3420
|
-
/* @__PURE__ */
|
|
3421
|
-
/* @__PURE__ */
|
|
3422
|
-
r.status === "queued" ? /* @__PURE__ */
|
|
3862
|
+
return /* @__PURE__ */ jsxs10(TitledBox, { title: t.ask.relayCard(r.name), flexShrink: 0, color, titleColor: C.violet, children: [
|
|
3863
|
+
/* @__PURE__ */ jsx13(Text12, { wrap: "truncate", color: C.fg, children: oneLine(r.text) }),
|
|
3864
|
+
r.error ? /* @__PURE__ */ jsx13(Text12, { color: C.red, wrap: "truncate", children: r.error }) : null,
|
|
3865
|
+
/* @__PURE__ */ jsxs10(Box10, { columnGap: 2, children: [
|
|
3866
|
+
/* @__PURE__ */ jsx13(Button, { label: r.status === "queued" ? t.ask.cancelRelay : t.ask.relay, k: "\u21B5", primary: focused }),
|
|
3867
|
+
r.status === "queued" ? /* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.ask.queued }) : null
|
|
3423
3868
|
] })
|
|
3424
3869
|
] });
|
|
3425
3870
|
}
|
|
3426
3871
|
const closes = turn.closes ?? [];
|
|
3427
|
-
return /* @__PURE__ */
|
|
3428
|
-
card.rows.map((r) => /* @__PURE__ */
|
|
3429
|
-
/* @__PURE__ */
|
|
3872
|
+
return /* @__PURE__ */ jsxs10(TitledBox, { title: t.ask.closeCard, flexShrink: 0, color, titleColor: C.red, children: [
|
|
3873
|
+
card.rows.map((r) => /* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", children: [
|
|
3874
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.fg, children: closes[r].name }),
|
|
3430
3875
|
" ",
|
|
3431
|
-
/* @__PURE__ */
|
|
3432
|
-
closes[r].error ? /* @__PURE__ */
|
|
3876
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: TOOL_NAME[closes[r].tool] }),
|
|
3877
|
+
closes[r].error ? /* @__PURE__ */ jsxs10(Text12, { color: C.red, children: [
|
|
3433
3878
|
" ",
|
|
3434
3879
|
closes[r].error
|
|
3435
3880
|
] }) : null
|
|
3436
3881
|
] }, r)),
|
|
3437
|
-
/* @__PURE__ */
|
|
3882
|
+
/* @__PURE__ */ jsx13(Box10, { columnGap: 2, children: /* @__PURE__ */ jsx13(Button, { label: t.ask.close, k: "\u21B5", primary: focused }) })
|
|
3438
3883
|
] });
|
|
3439
3884
|
}
|
|
3440
3885
|
|
|
3441
3886
|
// src/tui/screens/Launch.tsx
|
|
3442
|
-
import { useEffect as
|
|
3443
|
-
import { Box as Box11, Text as
|
|
3887
|
+
import { useEffect as useEffect9, useMemo as useMemo5, useState as useState9 } from "react";
|
|
3888
|
+
import { Box as Box11, Text as Text13, useInput as useInput5 } from "ink";
|
|
3444
3889
|
|
|
3445
3890
|
// ../fleet/src/launch.ts
|
|
3446
3891
|
var MULTI_REPO_TOOLS = /* @__PURE__ */ new Set(["claude", "omp"]);
|
|
@@ -3508,25 +3953,25 @@ function launchMessage(f, name) {
|
|
|
3508
3953
|
|
|
3509
3954
|
// src/tui/screens/Launch.tsx
|
|
3510
3955
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3511
|
-
import { Fragment as Fragment4, jsx as
|
|
3956
|
+
import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3512
3957
|
var FIELDS = ["prompt", "tool", "model", "effort", "project", "machine", "repos", "folder", "name"];
|
|
3513
3958
|
var TEXT = /* @__PURE__ */ new Set(["prompt", "folder"]);
|
|
3514
3959
|
var LAUNCH_MS = 2e4;
|
|
3515
3960
|
var cycle = (list, cur, by) => list[((Math.max(0, list.indexOf(cur)) + by) % list.length + list.length) % list.length];
|
|
3516
3961
|
function Launch() {
|
|
3517
|
-
const [team, setTeam] =
|
|
3518
|
-
return team ? /* @__PURE__ */
|
|
3962
|
+
const [team, setTeam] = useState9(false);
|
|
3963
|
+
return team ? /* @__PURE__ */ jsx14(TeamLaunch, { onSession: () => setTeam(false) }) : /* @__PURE__ */ jsx14(SessionLaunch, { onTeam: () => setTeam(true) });
|
|
3519
3964
|
}
|
|
3520
3965
|
function SessionLaunch({ onTeam }) {
|
|
3521
3966
|
const { client, d, machines, projects, t, g, toast, go, select } = useTui();
|
|
3522
3967
|
const list = projects?.projects ?? [];
|
|
3523
|
-
const [form, setForm] =
|
|
3524
|
-
const [field, setField] =
|
|
3525
|
-
const [folder, setFolder] =
|
|
3526
|
-
const [repoAt, setRepoAt] =
|
|
3527
|
-
const [launching, setLaunching] =
|
|
3968
|
+
const [form, setForm] = useState9(() => launchDefaults(list, machines, {}, "claude"));
|
|
3969
|
+
const [field, setField] = useState9("prompt");
|
|
3970
|
+
const [folder, setFolder] = useState9("");
|
|
3971
|
+
const [repoAt, setRepoAt] = useState9(0);
|
|
3972
|
+
const [launching, setLaunching] = useState9(null);
|
|
3528
3973
|
useEditing(true);
|
|
3529
|
-
|
|
3974
|
+
useEffect9(() => {
|
|
3530
3975
|
if (form.machine === null) {
|
|
3531
3976
|
const up = machines.find((m) => m.ready);
|
|
3532
3977
|
if (up) setForm((f) => withMachine(f, list, up.id));
|
|
@@ -3546,10 +3991,10 @@ function SessionLaunch({ onTeam }) {
|
|
|
3546
3991
|
const withFolder = folder.trim() ? { ...form, repos: MULTI_REPO_TOOLS.has(form.tool) ? [folder.trim(), ...form.repos.filter((r) => r !== folder.trim())] : [folder.trim()] } : form;
|
|
3547
3992
|
const projectIdOf = (s) => projects ? projectOf(s, projects) : null;
|
|
3548
3993
|
const drawn = useMemo5(() => previewOf(withFolder, d.sessions, projectIdOf), [form.machine, form.project, d.sessions.length]);
|
|
3549
|
-
const [name, setName] =
|
|
3994
|
+
const [name, setName] = useState9(null);
|
|
3550
3995
|
const shownName = name ?? drawn.name;
|
|
3551
3996
|
const missing = missingOf(withFolder, machines);
|
|
3552
|
-
|
|
3997
|
+
useEffect9(() => {
|
|
3553
3998
|
if (!launching) return;
|
|
3554
3999
|
if ((d.lastLaunched?.seq ?? 0) > launching.seq) {
|
|
3555
4000
|
select(d.lastLaunched.id);
|
|
@@ -3567,7 +4012,7 @@ function SessionLaunch({ onTeam }) {
|
|
|
3567
4012
|
const timer = setTimeout(() => setLaunching(null), Math.max(0, launching.at + LAUNCH_MS - Date.now()));
|
|
3568
4013
|
return () => clearTimeout(timer);
|
|
3569
4014
|
}, [launching, d.lastLaunched, d.errors]);
|
|
3570
|
-
const
|
|
4015
|
+
const launch2 = () => {
|
|
3571
4016
|
if (launching) return;
|
|
3572
4017
|
if (missing) {
|
|
3573
4018
|
toast({ text: t.launch.missing[missing], tone: "info" });
|
|
@@ -3577,13 +4022,13 @@ function SessionLaunch({ onTeam }) {
|
|
|
3577
4022
|
setLaunching({ seq: d.lastLaunched?.seq ?? 0, errorSeq: d.errors.at(-1)?.seq ?? 0, at: Date.now(), name: shownName });
|
|
3578
4023
|
client.send(launchMessage(withFolder, shownName));
|
|
3579
4024
|
};
|
|
3580
|
-
|
|
4025
|
+
useInput5((input, key) => {
|
|
3581
4026
|
if (key.escape) {
|
|
3582
4027
|
go("overview");
|
|
3583
4028
|
return;
|
|
3584
4029
|
}
|
|
3585
4030
|
if (key.ctrl && input === "s") {
|
|
3586
|
-
|
|
4031
|
+
launch2();
|
|
3587
4032
|
return;
|
|
3588
4033
|
}
|
|
3589
4034
|
if (key.ctrl && input === "t") {
|
|
@@ -3603,7 +4048,7 @@ function SessionLaunch({ onTeam }) {
|
|
|
3603
4048
|
return;
|
|
3604
4049
|
}
|
|
3605
4050
|
if (key.return) {
|
|
3606
|
-
|
|
4051
|
+
launch2();
|
|
3607
4052
|
return;
|
|
3608
4053
|
}
|
|
3609
4054
|
if (TEXT.has(field)) return;
|
|
@@ -3630,72 +4075,72 @@ function SessionLaunch({ onTeam }) {
|
|
|
3630
4075
|
setRepoAt(0);
|
|
3631
4076
|
}
|
|
3632
4077
|
});
|
|
3633
|
-
const row = (f, label, value) => /* @__PURE__ */
|
|
3634
|
-
/* @__PURE__ */
|
|
3635
|
-
/* @__PURE__ */
|
|
3636
|
-
/* @__PURE__ */
|
|
4078
|
+
const row = (f, label, value) => /* @__PURE__ */ jsxs11(Box11, { backgroundColor: field === f ? C.sel : void 0, children: [
|
|
4079
|
+
/* @__PURE__ */ jsx14(Text13, { color: field === f ? C.coral : C.faint, children: field === f ? "\u258C" : " " }),
|
|
4080
|
+
/* @__PURE__ */ jsx14(Box11, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx14(Text13, { color: C.dim, children: label }) }),
|
|
4081
|
+
/* @__PURE__ */ jsx14(Box11, { flexGrow: 1, overflow: "hidden", children: value })
|
|
3637
4082
|
] }, f);
|
|
3638
|
-
const choice = (items, cur, label, color) => /* @__PURE__ */
|
|
3639
|
-
x === cur ? /* @__PURE__ */
|
|
4083
|
+
const choice = (items, cur, label, color) => /* @__PURE__ */ jsx14(Text13, { wrap: "truncate", children: items.map((x, i) => /* @__PURE__ */ jsxs11(Text13, { children: [
|
|
4084
|
+
x === cur ? /* @__PURE__ */ jsx14(Text13, { color: C.fg, bold: true, children: `${g.prev} ${label(x)} ${g.next}` }) : /* @__PURE__ */ jsx14(Text13, { color: color?.(x) ?? C.dim, children: label(x) }),
|
|
3640
4085
|
" "
|
|
3641
4086
|
] }, i)) });
|
|
3642
|
-
return /* @__PURE__ */
|
|
3643
|
-
/* @__PURE__ */
|
|
3644
|
-
/* @__PURE__ */
|
|
3645
|
-
/* @__PURE__ */
|
|
3646
|
-
/* @__PURE__ */
|
|
3647
|
-
/* @__PURE__ */
|
|
3648
|
-
/* @__PURE__ */
|
|
4087
|
+
return /* @__PURE__ */ jsxs11(Frame, { crumb: t.launch.title, keys: [["tab", t.keys.nextField], ["\u2190 \u2192", t.keys.choose], [t.keys.space, t.keys.tick], ["ctrl+s", t.keys.start], ["ctrl+t", t.keys.teamMode], ["ctrl+r", t.keys.redraw], ["esc", t.keys.cancel]], children: [
|
|
4088
|
+
/* @__PURE__ */ jsxs11(TitledBox, { title: t.launch.title, flexGrow: 1, children: [
|
|
4089
|
+
/* @__PURE__ */ jsxs11(Box11, { children: [
|
|
4090
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.faint, children: " " }),
|
|
4091
|
+
/* @__PURE__ */ jsx14(Box11, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx14(Text13, { color: C.dim, children: t.launch.mode }) }),
|
|
4092
|
+
/* @__PURE__ */ jsxs11(Text13, { children: [
|
|
4093
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.coral, children: [
|
|
3649
4094
|
g.radioOn,
|
|
3650
4095
|
" ",
|
|
3651
4096
|
t.launch.session
|
|
3652
4097
|
] }),
|
|
3653
4098
|
" ",
|
|
3654
|
-
/* @__PURE__ */
|
|
4099
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.dim, children: [
|
|
3655
4100
|
g.radioOff,
|
|
3656
4101
|
" ",
|
|
3657
4102
|
t.launch.team
|
|
3658
4103
|
] }),
|
|
3659
4104
|
" ",
|
|
3660
|
-
/* @__PURE__ */
|
|
4105
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.faint, children: "ctrl+t" })
|
|
3661
4106
|
] })
|
|
3662
4107
|
] }),
|
|
3663
|
-
row("prompt", t.launch.instruction, /* @__PURE__ */
|
|
3664
|
-
/* @__PURE__ */
|
|
3665
|
-
row("tool", t.launch.agent, /* @__PURE__ */
|
|
4108
|
+
row("prompt", t.launch.instruction, /* @__PURE__ */ jsx14(TextInput, { value: form.prompt, onChange: (v) => setForm((f) => ({ ...f, prompt: v })), active: field === "prompt" })),
|
|
4109
|
+
/* @__PURE__ */ jsx14(Text13, { children: " " }),
|
|
4110
|
+
row("tool", t.launch.agent, /* @__PURE__ */ jsx14(Text13, { wrap: "truncate", children: tools.map((x) => /* @__PURE__ */ jsxs11(Text13, { color: x === form.tool ? C.coral : C.dim, children: [
|
|
3666
4111
|
x === form.tool ? g.radioOn : g.radioOff,
|
|
3667
4112
|
" ",
|
|
3668
4113
|
TOOL_NAME[x],
|
|
3669
4114
|
" "
|
|
3670
4115
|
] }, x)) })),
|
|
3671
|
-
row("model", t.launch.model, models.status === "loading" ? /* @__PURE__ */
|
|
3672
|
-
row("effort", t.launch.effort, efforts.length > 1 ? choice(efforts, form.effort, (x) => x ?? t.launch.defaultModel) : /* @__PURE__ */
|
|
4116
|
+
row("model", t.launch.model, models.status === "loading" ? /* @__PURE__ */ jsx14(Text13, { color: C.faint, children: t.launch.loading }) : choice(modelIds, form.model, (x) => x === null ? t.launch.defaultModel : models.models.find((m) => m.id === x)?.label ?? x)),
|
|
4117
|
+
row("effort", t.launch.effort, efforts.length > 1 ? choice(efforts, form.effort, (x) => x ?? t.launch.defaultModel) : /* @__PURE__ */ jsx14(Text13, { color: C.faint, children: t.launch.defaultModel })),
|
|
3673
4118
|
row("project", t.launch.project, choice([...list.map((p) => p.id), null], form.project, (x) => x === null ? t.launch.none : list.find((p) => p.id === x).name, (x) => projectColor(list.find((p) => p.id === x)?.color))),
|
|
3674
|
-
row("machine", t.launch.machine, /* @__PURE__ */
|
|
3675
|
-
/* @__PURE__ */
|
|
4119
|
+
row("machine", t.launch.machine, /* @__PURE__ */ jsx14(Text13, { wrap: "truncate", children: machines.map((m) => /* @__PURE__ */ jsxs11(Text13, { color: m.id === form.machine ? C.fg : C.dim, bold: m.id === form.machine, children: [
|
|
4120
|
+
/* @__PURE__ */ jsx14(Text13, { color: m.ready ? C.green : C.faint, children: g.machine }),
|
|
3676
4121
|
" ",
|
|
3677
4122
|
m.label,
|
|
3678
4123
|
" "
|
|
3679
4124
|
] }, m.id)) })),
|
|
3680
|
-
row("repos", t.launch.repos, repoOptions.length === 0 ? /* @__PURE__ */
|
|
3681
|
-
/* @__PURE__ */
|
|
4125
|
+
row("repos", t.launch.repos, repoOptions.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: C.faint, children: t.launch.none }) : /* @__PURE__ */ jsx14(Text13, { wrap: "truncate", children: repoOptions.map((r, i) => /* @__PURE__ */ jsxs11(Text13, { backgroundColor: field === "repos" && i === repoAt ? "#3a3532" : void 0, color: form.repos.includes(r) ? C.fg : C.dim, children: [
|
|
4126
|
+
/* @__PURE__ */ jsx14(Text13, { color: form.repos.includes(r) ? C.green : C.faint, children: form.repos.includes(r) ? g.boxOn : g.boxOff }),
|
|
3682
4127
|
" ",
|
|
3683
4128
|
r.split("/").filter(Boolean).at(-1) ?? r,
|
|
3684
4129
|
" ",
|
|
3685
|
-
/* @__PURE__ */
|
|
4130
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.faint, children: r }),
|
|
3686
4131
|
" "
|
|
3687
4132
|
] }, r)) })),
|
|
3688
|
-
row("folder", t.launch.folder, /* @__PURE__ */
|
|
3689
|
-
row("name", t.launch.name, /* @__PURE__ */
|
|
4133
|
+
row("folder", t.launch.folder, /* @__PURE__ */ jsx14(TextInput, { value: folder, onChange: setFolder, active: field === "folder", placeholder: t.launch.folderHint })),
|
|
4134
|
+
row("name", t.launch.name, /* @__PURE__ */ jsxs11(Text13, { children: [
|
|
3690
4135
|
shownName,
|
|
3691
4136
|
" ",
|
|
3692
|
-
/* @__PURE__ */
|
|
4137
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.faint, children: t.launch.drawn })
|
|
3693
4138
|
] }))
|
|
3694
4139
|
] }),
|
|
3695
|
-
/* @__PURE__ */
|
|
3696
|
-
/* @__PURE__ */
|
|
3697
|
-
/* @__PURE__ */
|
|
3698
|
-
/* @__PURE__ */
|
|
4140
|
+
/* @__PURE__ */ jsxs11(TitledBox, { title: t.launch.preview, color: C.coral, titleColor: C.coral, children: [
|
|
4141
|
+
/* @__PURE__ */ jsxs11(Text13, { wrap: "truncate", children: [
|
|
4142
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.fg, bold: true, children: project ? t.launch.joins(shownName, project.name) : shownName }),
|
|
4143
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.faint, children: [
|
|
3699
4144
|
" \xB7 ",
|
|
3700
4145
|
machine?.label ?? "-",
|
|
3701
4146
|
" \xB7 ",
|
|
@@ -3705,10 +4150,10 @@ function SessionLaunch({ onTeam }) {
|
|
|
3705
4150
|
form.effort ? ` \xB7 ${form.effort}` : ""
|
|
3706
4151
|
] })
|
|
3707
4152
|
] }),
|
|
3708
|
-
/* @__PURE__ */
|
|
3709
|
-
/* @__PURE__ */
|
|
3710
|
-
/* @__PURE__ */
|
|
3711
|
-
missing ? /* @__PURE__ */
|
|
4153
|
+
/* @__PURE__ */ jsxs11(Box11, { marginTop: 1, columnGap: 2, children: [
|
|
4154
|
+
/* @__PURE__ */ jsx14(Button, { label: launching ? t.launch.launching : t.launch.launch, k: "ctrl+s", primary: true }),
|
|
4155
|
+
/* @__PURE__ */ jsx14(Button, { label: t.launch.cancel, k: "esc" }),
|
|
4156
|
+
missing ? /* @__PURE__ */ jsx14(Text13, { color: C.yellow, children: t.launch.missing[missing] }) : /* @__PURE__ */ jsx14(Text13, { color: C.faint, children: t.launch.thenAttach })
|
|
3712
4157
|
] })
|
|
3713
4158
|
] })
|
|
3714
4159
|
] });
|
|
@@ -3718,23 +4163,23 @@ var AUTONOMY_COLOR = { ask: C.faint, edits: C.dim, auto: C.yellow };
|
|
|
3718
4163
|
function TeamLaunch({ onSession }) {
|
|
3719
4164
|
const { client, machines, projects, t, g, toast, go } = useTui();
|
|
3720
4165
|
const list = projects?.projects ?? [];
|
|
3721
|
-
const [projectId, setProjectId] =
|
|
4166
|
+
const [projectId, setProjectId] = useState9(list[0]?.id ?? null);
|
|
3722
4167
|
const project = list.find((p) => p.id === projectId);
|
|
3723
4168
|
const folders = useMemo5(() => {
|
|
3724
4169
|
const own = (project?.repos ?? []).filter((r) => r.machine === "local").map((r) => r.path);
|
|
3725
4170
|
return own.length ? own : (machines.find((m) => m.id === "local")?.recents ?? []).slice(0, 6).map((r) => r.cwd);
|
|
3726
4171
|
}, [project, machines]);
|
|
3727
|
-
const [folderAt, setFolderAt] =
|
|
4172
|
+
const [folderAt, setFolderAt] = useState9(0);
|
|
3728
4173
|
const cwd = folders[folderAt] ?? folders[0] ?? null;
|
|
3729
|
-
const [field, setField] =
|
|
3730
|
-
const [defs, setDefs] =
|
|
3731
|
-
const [template, setTemplate] =
|
|
3732
|
-
const [seats, setSeats] =
|
|
3733
|
-
const [seatAt, setSeatAt] =
|
|
3734
|
-
const [name, setName] =
|
|
3735
|
-
const [launching, setLaunching] =
|
|
4174
|
+
const [field, setField] = useState9("template");
|
|
4175
|
+
const [defs, setDefs] = useState9(null);
|
|
4176
|
+
const [template, setTemplate] = useState9(null);
|
|
4177
|
+
const [seats, setSeats] = useState9([]);
|
|
4178
|
+
const [seatAt, setSeatAt] = useState9(0);
|
|
4179
|
+
const [name, setName] = useState9("");
|
|
4180
|
+
const [launching, setLaunching] = useState9(false);
|
|
3736
4181
|
useEditing(true);
|
|
3737
|
-
|
|
4182
|
+
useEffect9(() => {
|
|
3738
4183
|
if (!cwd) {
|
|
3739
4184
|
setDefs({ roles: [], templates: [] });
|
|
3740
4185
|
return;
|
|
@@ -3759,7 +4204,7 @@ function TeamLaunch({ onSession }) {
|
|
|
3759
4204
|
}) : seats.filter((x) => x.on).map((x) => ({ id: x.role.id, tool: x.tool, autonomy: x.role.autonomy ?? "ask" }));
|
|
3760
4205
|
const auto = chosen.filter((x) => x.autonomy === "auto").map((x) => `@${x.id}`);
|
|
3761
4206
|
const teamName = name.trim() || tpl?.name || project?.name || "team";
|
|
3762
|
-
const
|
|
4207
|
+
const launch2 = () => {
|
|
3763
4208
|
if (launching || !cwd) return;
|
|
3764
4209
|
if (!chosen.length) {
|
|
3765
4210
|
toast({ text: t.launch.noSeat, tone: "info" });
|
|
@@ -3780,13 +4225,13 @@ function TeamLaunch({ onSession }) {
|
|
|
3780
4225
|
};
|
|
3781
4226
|
const tools = ["claude", "codex", "cursor", "omp"];
|
|
3782
4227
|
const templates = [...(defs?.templates ?? []).map((x) => x.id), null];
|
|
3783
|
-
|
|
4228
|
+
useInput5((input, key) => {
|
|
3784
4229
|
if (key.escape) {
|
|
3785
4230
|
go("overview");
|
|
3786
4231
|
return;
|
|
3787
4232
|
}
|
|
3788
4233
|
if (key.ctrl && input === "s") {
|
|
3789
|
-
|
|
4234
|
+
launch2();
|
|
3790
4235
|
return;
|
|
3791
4236
|
}
|
|
3792
4237
|
if (key.ctrl && input === "t") {
|
|
@@ -3822,69 +4267,69 @@ function TeamLaunch({ onSession }) {
|
|
|
3822
4267
|
else if (by) setSeats((xs) => xs.map((x, i) => i === seatAt ? { ...x, tool: cycle(tools, x.tool, by) } : x));
|
|
3823
4268
|
}
|
|
3824
4269
|
});
|
|
3825
|
-
const row = (f, label, value) => /* @__PURE__ */
|
|
3826
|
-
/* @__PURE__ */
|
|
3827
|
-
/* @__PURE__ */
|
|
3828
|
-
/* @__PURE__ */
|
|
4270
|
+
const row = (f, label, value) => /* @__PURE__ */ jsxs11(Box11, { backgroundColor: field === f ? C.sel : void 0, children: [
|
|
4271
|
+
/* @__PURE__ */ jsx14(Text13, { color: field === f ? C.coral : C.faint, children: field === f ? "\u258C" : " " }),
|
|
4272
|
+
/* @__PURE__ */ jsx14(Box11, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx14(Text13, { color: C.dim, children: label }) }),
|
|
4273
|
+
/* @__PURE__ */ jsx14(Box11, { flexGrow: 1, overflow: "hidden", flexDirection: "column", children: value })
|
|
3829
4274
|
] }, f);
|
|
3830
|
-
return /* @__PURE__ */
|
|
3831
|
-
/* @__PURE__ */
|
|
3832
|
-
/* @__PURE__ */
|
|
3833
|
-
/* @__PURE__ */
|
|
3834
|
-
/* @__PURE__ */
|
|
3835
|
-
/* @__PURE__ */
|
|
3836
|
-
/* @__PURE__ */
|
|
4275
|
+
return /* @__PURE__ */ jsxs11(Frame, { crumb: t.launch.teamTitle, keys: [["tab", t.keys.nextField], ["\u2190 \u2192", t.keys.choose], [t.keys.space, t.keys.tick], ["ctrl+s", t.keys.start], ["ctrl+t", t.keys.teamMode], ["esc", t.keys.cancel]], children: [
|
|
4276
|
+
/* @__PURE__ */ jsxs11(TitledBox, { title: t.launch.teamTitle, flexGrow: 1, children: [
|
|
4277
|
+
/* @__PURE__ */ jsxs11(Box11, { children: [
|
|
4278
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.faint, children: " " }),
|
|
4279
|
+
/* @__PURE__ */ jsx14(Box11, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx14(Text13, { color: C.dim, children: t.launch.mode }) }),
|
|
4280
|
+
/* @__PURE__ */ jsxs11(Text13, { children: [
|
|
4281
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.dim, children: [
|
|
3837
4282
|
g.radioOff,
|
|
3838
4283
|
" ",
|
|
3839
4284
|
t.launch.session
|
|
3840
4285
|
] }),
|
|
3841
4286
|
" ",
|
|
3842
|
-
/* @__PURE__ */
|
|
4287
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.coral, children: [
|
|
3843
4288
|
g.radioOn,
|
|
3844
4289
|
" ",
|
|
3845
4290
|
t.launch.team
|
|
3846
4291
|
] }),
|
|
3847
4292
|
" ",
|
|
3848
|
-
/* @__PURE__ */
|
|
4293
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.faint, children: "ctrl+t" })
|
|
3849
4294
|
] })
|
|
3850
4295
|
] }),
|
|
3851
|
-
row("project", t.launch.project, /* @__PURE__ */
|
|
4296
|
+
row("project", t.launch.project, /* @__PURE__ */ jsx14(Text13, { wrap: "truncate", children: list.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: C.faint, children: t.launch.none }) : list.map((p) => /* @__PURE__ */ jsxs11(Text13, { color: p.id === projectId ? C.fg : projectColor(p.color), bold: p.id === projectId, children: [
|
|
3852
4297
|
p.id === projectId ? `${g.prev} ${p.name} ${g.next}` : p.name,
|
|
3853
4298
|
" "
|
|
3854
4299
|
] }, p.id)) })),
|
|
3855
|
-
row("folder", t.launch.folder, /* @__PURE__ */
|
|
3856
|
-
/* @__PURE__ */
|
|
3857
|
-
folders.length > 1 ? /* @__PURE__ */
|
|
4300
|
+
row("folder", t.launch.folder, /* @__PURE__ */ jsx14(Text13, { wrap: "truncate", children: cwd ? /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
4301
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.fg, children: cwd }),
|
|
4302
|
+
folders.length > 1 ? /* @__PURE__ */ jsxs11(Text13, { color: C.faint, children: [
|
|
3858
4303
|
" (",
|
|
3859
4304
|
folderAt + 1,
|
|
3860
4305
|
"/",
|
|
3861
4306
|
folders.length,
|
|
3862
4307
|
")"
|
|
3863
4308
|
] }) : null
|
|
3864
|
-
] }) : /* @__PURE__ */
|
|
3865
|
-
row("template", t.launch.template, defs === null ? /* @__PURE__ */
|
|
4309
|
+
] }) : /* @__PURE__ */ jsx14(Text13, { color: C.faint, children: t.launch.none }) })),
|
|
4310
|
+
row("template", t.launch.template, defs === null ? /* @__PURE__ */ jsx14(Text13, { color: C.faint, children: t.launch.loadingRoles }) : /* @__PURE__ */ jsx14(Text13, { wrap: "truncate", children: templates.map((x) => /* @__PURE__ */ jsxs11(Text13, { color: x === template ? C.fg : C.dim, bold: x === template, children: [
|
|
3866
4311
|
x === template ? `${g.prev} ${x ?? t.launch.custom} ${g.next}` : x ?? t.launch.custom,
|
|
3867
4312
|
" "
|
|
3868
4313
|
] }, x ?? "-")) })),
|
|
3869
|
-
row("seats", t.launch.seats, tpl ? /* @__PURE__ */
|
|
3870
|
-
/* @__PURE__ */
|
|
4314
|
+
row("seats", t.launch.seats, tpl ? /* @__PURE__ */ jsx14(Text13, { wrap: "truncate", children: chosen.map((x) => /* @__PURE__ */ jsxs11(Text13, { children: [
|
|
4315
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.violet, children: [
|
|
3871
4316
|
"@",
|
|
3872
4317
|
x.id
|
|
3873
4318
|
] }),
|
|
3874
4319
|
" ",
|
|
3875
|
-
/* @__PURE__ */
|
|
4320
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.dim, children: TOOL_NAME[x.tool] }),
|
|
3876
4321
|
" "
|
|
3877
|
-
] }, x.id)) }) : seats.length === 0 ? /* @__PURE__ */
|
|
3878
|
-
/* @__PURE__ */
|
|
4322
|
+
] }, x.id)) }) : seats.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: C.faint, children: t.launch.noRole }) : /* @__PURE__ */ jsx14(Fragment4, { children: seats.map((x, i) => /* @__PURE__ */ jsxs11(Text13, { wrap: "truncate", backgroundColor: field === "seats" && i === seatAt ? "#3a3532" : void 0, children: [
|
|
4323
|
+
/* @__PURE__ */ jsx14(Text13, { color: x.on ? C.green : C.faint, children: x.on ? g.boxOn : g.boxOff }),
|
|
3879
4324
|
" ",
|
|
3880
|
-
/* @__PURE__ */
|
|
4325
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.violet, children: [
|
|
3881
4326
|
"@",
|
|
3882
4327
|
x.role.id
|
|
3883
4328
|
] }),
|
|
3884
4329
|
" ",
|
|
3885
4330
|
x.role.label,
|
|
3886
4331
|
" ",
|
|
3887
|
-
/* @__PURE__ */
|
|
4332
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.dim, children: [
|
|
3888
4333
|
g.prev,
|
|
3889
4334
|
" ",
|
|
3890
4335
|
TOOL_NAME[x.tool],
|
|
@@ -3892,47 +4337,47 @@ function TeamLaunch({ onSession }) {
|
|
|
3892
4337
|
g.next
|
|
3893
4338
|
] }),
|
|
3894
4339
|
" ",
|
|
3895
|
-
/* @__PURE__ */
|
|
4340
|
+
/* @__PURE__ */ jsx14(Text13, { color: AUTONOMY_COLOR[x.role.autonomy ?? "ask"], children: t.ask.autonomy[x.role.autonomy ?? "ask"] })
|
|
3896
4341
|
] }, x.role.id)) })),
|
|
3897
|
-
row("name", t.launch.teamName, /* @__PURE__ */
|
|
4342
|
+
row("name", t.launch.teamName, /* @__PURE__ */ jsx14(TextInput, { value: name, onChange: setName, active: field === "name", placeholder: teamName }))
|
|
3898
4343
|
] }),
|
|
3899
|
-
/* @__PURE__ */
|
|
3900
|
-
/* @__PURE__ */
|
|
3901
|
-
/* @__PURE__ */
|
|
4344
|
+
/* @__PURE__ */ jsxs11(TitledBox, { title: t.launch.preview, color: C.coral, titleColor: C.coral, children: [
|
|
4345
|
+
/* @__PURE__ */ jsxs11(Text13, { wrap: "truncate", children: [
|
|
4346
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.coral, children: g.team }),
|
|
3902
4347
|
" ",
|
|
3903
|
-
/* @__PURE__ */
|
|
4348
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.fg, bold: true, children: t.launch.teamPreview(teamName, project?.name ?? cwd ?? "-") })
|
|
3904
4349
|
] }),
|
|
3905
|
-
/* @__PURE__ */
|
|
3906
|
-
/* @__PURE__ */
|
|
4350
|
+
/* @__PURE__ */ jsx14(Text13, { wrap: "truncate", children: chosen.map((x) => /* @__PURE__ */ jsxs11(Text13, { children: [
|
|
4351
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.violet, children: [
|
|
3907
4352
|
"@",
|
|
3908
4353
|
x.id
|
|
3909
4354
|
] }),
|
|
3910
4355
|
" ",
|
|
3911
|
-
/* @__PURE__ */
|
|
4356
|
+
/* @__PURE__ */ jsx14(Text13, { color: C.dim, children: TOOL_NAME[x.tool] }),
|
|
3912
4357
|
" ",
|
|
3913
|
-
/* @__PURE__ */
|
|
4358
|
+
/* @__PURE__ */ jsx14(Text13, { color: AUTONOMY_COLOR[x.autonomy], children: t.ask.autonomy[x.autonomy] }),
|
|
3914
4359
|
" "
|
|
3915
4360
|
] }, x.id)) }),
|
|
3916
|
-
auto.length ? /* @__PURE__ */
|
|
3917
|
-
/* @__PURE__ */
|
|
3918
|
-
/* @__PURE__ */
|
|
3919
|
-
/* @__PURE__ */
|
|
4361
|
+
auto.length ? /* @__PURE__ */ jsx14(Text13, { color: C.yellow, wrap: "truncate", children: t.ask.autoWarning(auto.join(", ")) }) : null,
|
|
4362
|
+
/* @__PURE__ */ jsxs11(Box11, { marginTop: 1, columnGap: 2, children: [
|
|
4363
|
+
/* @__PURE__ */ jsx14(Button, { label: launching ? t.launch.launching : t.ask.launchTeam, k: "ctrl+s", primary: true }),
|
|
4364
|
+
/* @__PURE__ */ jsx14(Button, { label: t.launch.cancel, k: "esc" })
|
|
3920
4365
|
] })
|
|
3921
4366
|
] })
|
|
3922
4367
|
] });
|
|
3923
4368
|
}
|
|
3924
4369
|
|
|
3925
4370
|
// src/tui/screens/Settings.tsx
|
|
3926
|
-
import { useState as
|
|
3927
|
-
import { Box as Box12, Text as
|
|
3928
|
-
import { jsx as
|
|
4371
|
+
import { useState as useState10 } from "react";
|
|
4372
|
+
import { Box as Box12, Text as Text14 } from "ink";
|
|
4373
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3929
4374
|
function Settings() {
|
|
3930
4375
|
const tui = useTui();
|
|
3931
4376
|
const { t, g, lang, setLang } = tui;
|
|
3932
4377
|
const near = useContextNear(tui.client);
|
|
3933
4378
|
const known2 = tui.client.fleet?.contextNear() !== void 0;
|
|
3934
4379
|
const rows = [{ id: "lang", label: t.settings.language }, { id: "context", label: t.settings.context }];
|
|
3935
|
-
const [at, setAt] =
|
|
4380
|
+
const [at, setAt] = useState10(0);
|
|
3936
4381
|
useScreenKeys((input, key) => {
|
|
3937
4382
|
if (key.upArrow || input === "k") {
|
|
3938
4383
|
setAt((i) => (i - 1 + rows.length) % rows.length);
|
|
@@ -3956,20 +4401,20 @@ function Settings() {
|
|
|
3956
4401
|
}
|
|
3957
4402
|
return true;
|
|
3958
4403
|
});
|
|
3959
|
-
return /* @__PURE__ */
|
|
3960
|
-
/* @__PURE__ */
|
|
3961
|
-
/* @__PURE__ */
|
|
3962
|
-
r.id === "lang" ? /* @__PURE__ */
|
|
4404
|
+
return /* @__PURE__ */ jsx15(Frame, { crumb: t.settings.title, keys: [["\u2191 \u2193", t.keys.navigate], ["\u2190 \u2192", t.keys.change], ["esc", t.keys.back]], children: /* @__PURE__ */ jsx15(TitledBox, { title: t.settings.title, flexGrow: 1, children: rows.map((r, i) => /* @__PURE__ */ jsxs12(Box12, { backgroundColor: i === at ? C.sel : void 0, children: [
|
|
4405
|
+
/* @__PURE__ */ jsx15(Text14, { color: i === at ? C.coral : C.faint, children: i === at ? "\u258C" : " " }),
|
|
4406
|
+
/* @__PURE__ */ jsx15(Box12, { width: 16, children: /* @__PURE__ */ jsx15(Text14, { color: C.dim, children: r.label }) }),
|
|
4407
|
+
r.id === "lang" ? /* @__PURE__ */ jsx15(Text14, { children: LANGS.map((l) => /* @__PURE__ */ jsxs12(Text14, { color: l === lang ? C.fg : C.dim, bold: l === lang, children: [
|
|
3963
4408
|
l === lang ? `${g.radioOn} ` : `${g.radioOff} `,
|
|
3964
4409
|
t.lang[l],
|
|
3965
4410
|
" "
|
|
3966
|
-
] }, l)) }) : /* @__PURE__ */
|
|
3967
|
-
/* @__PURE__ */
|
|
4411
|
+
] }, l)) }) : /* @__PURE__ */ jsxs12(Text14, { children: [
|
|
4412
|
+
/* @__PURE__ */ jsxs12(Text14, { color: C.fg, bold: true, children: [
|
|
3968
4413
|
"\u25C2 ",
|
|
3969
4414
|
Math.round(near * 100),
|
|
3970
4415
|
" % \u25B8"
|
|
3971
4416
|
] }),
|
|
3972
|
-
/* @__PURE__ */
|
|
4417
|
+
/* @__PURE__ */ jsxs12(Text14, { color: C.faint, children: [
|
|
3973
4418
|
" ",
|
|
3974
4419
|
known2 ? t.settings.contextHint : t.settings.contextUnknown
|
|
3975
4420
|
] })
|
|
@@ -3978,8 +4423,8 @@ function Settings() {
|
|
|
3978
4423
|
}
|
|
3979
4424
|
|
|
3980
4425
|
// src/tui/screens/Help.tsx
|
|
3981
|
-
import { Box as Box13, Text as
|
|
3982
|
-
import { jsx as
|
|
4426
|
+
import { Box as Box13, Text as Text15 } from "ink";
|
|
4427
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3983
4428
|
function Help() {
|
|
3984
4429
|
const { t } = useTui();
|
|
3985
4430
|
const k = t.keys;
|
|
@@ -3991,23 +4436,23 @@ function Help() {
|
|
|
3991
4436
|
[t.help.launch, [["tab \u2191 \u2193", k.nextField], ["\u2190 \u2192", k.choose], [t.keys.space, k.tick], ["ctrl+r", t.keys.redraw], ["ctrl+s", k.start], ["esc", k.cancel]]],
|
|
3992
4437
|
[t.help.settings, [["\u2191 \u2193", k.navigate], ["\u2190 \u2192", k.change]]]
|
|
3993
4438
|
];
|
|
3994
|
-
return /* @__PURE__ */
|
|
3995
|
-
/* @__PURE__ */
|
|
3996
|
-
keys.map(([key, label]) => /* @__PURE__ */
|
|
3997
|
-
/* @__PURE__ */
|
|
3998
|
-
/* @__PURE__ */
|
|
4439
|
+
return /* @__PURE__ */ jsx16(Frame, { crumb: t.help.title, keys: [["esc", k.back]], children: /* @__PURE__ */ jsx16(TitledBox, { title: t.help.title, flexGrow: 1, children: /* @__PURE__ */ jsx16(Box13, { flexWrap: "wrap", columnGap: 4, children: groups.map(([title2, keys]) => /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", width: 40, marginBottom: 1, children: [
|
|
4440
|
+
/* @__PURE__ */ jsx16(Text15, { color: C.coral, bold: true, children: title2 }),
|
|
4441
|
+
keys.map(([key, label]) => /* @__PURE__ */ jsxs13(Text15, { wrap: "truncate", children: [
|
|
4442
|
+
/* @__PURE__ */ jsx16(Text15, { color: C.fg, children: key.padEnd(16) }),
|
|
4443
|
+
/* @__PURE__ */ jsx16(Text15, { color: C.dim, children: label })
|
|
3999
4444
|
] }, key + label))
|
|
4000
4445
|
] }, title2)) }) }) });
|
|
4001
4446
|
}
|
|
4002
4447
|
|
|
4003
4448
|
// src/tui/App.tsx
|
|
4004
|
-
import { jsx as
|
|
4449
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
4005
4450
|
var TOAST_MS = 5e3;
|
|
4006
4451
|
function App(props) {
|
|
4007
|
-
const [lang, setLangState] =
|
|
4008
|
-
return /* @__PURE__ */
|
|
4452
|
+
const [lang, setLangState] = useState11(props.lang);
|
|
4453
|
+
return /* @__PURE__ */ jsx17(I18nProvider, { lang, children: /* @__PURE__ */ jsx17(Shell, { ...props, lang, setLang: setLangState }) });
|
|
4009
4454
|
}
|
|
4010
|
-
function Shell({ client, lang, setLang: setLangState, saveLang, notify, glyphs, env, run: run2 }) {
|
|
4455
|
+
function Shell({ client, lang, setLang: setLangState, saveLang, notify, glyphs, env, run: run2, readImage, openFile }) {
|
|
4011
4456
|
const { exit, suspendTerminal } = useApp();
|
|
4012
4457
|
const { stdout } = useStdout();
|
|
4013
4458
|
const write = useCallback((seq) => {
|
|
@@ -4020,18 +4465,18 @@ function Shell({ client, lang, setLang: setLangState, saveLang, notify, glyphs,
|
|
|
4020
4465
|
const tasks = useTasks(client);
|
|
4021
4466
|
const fleet = client.fleet;
|
|
4022
4467
|
const t = MESSAGES[lang];
|
|
4023
|
-
const [screen, setScreen] =
|
|
4024
|
-
const [back, setBack] =
|
|
4025
|
-
const [selectedId, setSelectedId] =
|
|
4026
|
-
const [lastToast, setToast] =
|
|
4027
|
-
const [editing, setEditingState] =
|
|
4028
|
-
const [now, setNow] =
|
|
4029
|
-
const keys =
|
|
4030
|
-
|
|
4468
|
+
const [screen, setScreen] = useState11("overview");
|
|
4469
|
+
const [back, setBack] = useState11("overview");
|
|
4470
|
+
const [selectedId, setSelectedId] = useState11(null);
|
|
4471
|
+
const [lastToast, setToast] = useState11(null);
|
|
4472
|
+
const [editing, setEditingState] = useState11(false);
|
|
4473
|
+
const [now, setNow] = useState11(() => Date.now());
|
|
4474
|
+
const keys = useRef9(null);
|
|
4475
|
+
useEffect10(() => {
|
|
4031
4476
|
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
4032
4477
|
return () => clearInterval(timer);
|
|
4033
4478
|
}, []);
|
|
4034
|
-
|
|
4479
|
+
useEffect10(() => {
|
|
4035
4480
|
if (!lastToast) return;
|
|
4036
4481
|
const timer = setTimeout(() => setToast(null), TOAST_MS);
|
|
4037
4482
|
return () => clearTimeout(timer);
|
|
@@ -4039,14 +4484,14 @@ function Shell({ client, lang, setLang: setLangState, saveLang, notify, glyphs,
|
|
|
4039
4484
|
const toast = useCallback((x) => setToast(x), []);
|
|
4040
4485
|
const setEditing = useCallback((on) => setEditingState(on), []);
|
|
4041
4486
|
const view = useMemo6(() => overview(d.sessions, projects, teams ?? [], tasks), [d.sessions, projects, teams, tasks]);
|
|
4042
|
-
const order =
|
|
4043
|
-
|
|
4487
|
+
const order = useRef9([]);
|
|
4488
|
+
useEffect10(() => {
|
|
4044
4489
|
setSelectedId((cur) => cur === null && view.yours[0] ? view.yours[0].id : keepSelection(view.order, cur, order.current));
|
|
4045
4490
|
order.current = view.order;
|
|
4046
4491
|
}, [view]);
|
|
4047
4492
|
const selected = (selectedId ? d.byId.get(selectedId) : void 0) ?? null;
|
|
4048
|
-
const seen =
|
|
4049
|
-
|
|
4493
|
+
const seen = useRef9(null);
|
|
4494
|
+
useEffect10(() => {
|
|
4050
4495
|
if (seen.current) for (const text of asked(seen.current, d.sessions, t.notify)) write(notification(text, notify, env));
|
|
4051
4496
|
seen.current = d.byId;
|
|
4052
4497
|
write(title(t.notify.title(listed(d.sessions).filter(isPending).length)));
|
|
@@ -4098,9 +4543,11 @@ function Shell({ client, lang, setLang: setLangState, saveLang, notify, glyphs,
|
|
|
4098
4543
|
actions,
|
|
4099
4544
|
setEditing,
|
|
4100
4545
|
attach,
|
|
4101
|
-
keys
|
|
4546
|
+
keys,
|
|
4547
|
+
readImage: readImage ?? (() => readClipboardImage(env)),
|
|
4548
|
+
openFile: openFile ?? ((file) => openImage(file, env))
|
|
4102
4549
|
};
|
|
4103
|
-
|
|
4550
|
+
useInput6((input, key) => {
|
|
4104
4551
|
if (key.ctrl && input === "c") {
|
|
4105
4552
|
if (keys.current?.(input, key)) return;
|
|
4106
4553
|
exit();
|
|
@@ -4138,7 +4585,7 @@ function Shell({ client, lang, setLang: setLangState, saveLang, notify, glyphs,
|
|
|
4138
4585
|
}
|
|
4139
4586
|
});
|
|
4140
4587
|
const Body = { overview: Overview, session: SessionScreen, ask: Ask, launch: Launch, settings: Settings, help: Help }[screen];
|
|
4141
|
-
return /* @__PURE__ */
|
|
4588
|
+
return /* @__PURE__ */ jsx17(TuiContext.Provider, { value: tui, children: /* @__PURE__ */ jsx17(Body, {}) });
|
|
4142
4589
|
}
|
|
4143
4590
|
|
|
4144
4591
|
// src/tui/fleet.ts
|
|
@@ -4194,7 +4641,9 @@ var UNICODE = {
|
|
|
4194
4641
|
bar: "\u2502",
|
|
4195
4642
|
/** A team's table and the common table (roles spec §3.9). */
|
|
4196
4643
|
team: "\u25A3",
|
|
4197
|
-
common: "\u25AD"
|
|
4644
|
+
common: "\u25AD",
|
|
4645
|
+
/** A task handed between seats in « Échanges » (#92). */
|
|
4646
|
+
task: "\u25A4"
|
|
4198
4647
|
};
|
|
4199
4648
|
var ASCII = {
|
|
4200
4649
|
approval: "!",
|
|
@@ -4227,7 +4676,8 @@ var ASCII = {
|
|
|
4227
4676
|
ellipsis: "...",
|
|
4228
4677
|
bar: "|",
|
|
4229
4678
|
team: "[T]",
|
|
4230
|
-
common: "[ ]"
|
|
4679
|
+
common: "[ ]",
|
|
4680
|
+
task: "[=]"
|
|
4231
4681
|
};
|
|
4232
4682
|
function glyphsFor(env) {
|
|
4233
4683
|
if (env.LEDGE_TUI_ASCII === "1") return ASCII;
|
|
@@ -4237,10 +4687,10 @@ function glyphsFor(env) {
|
|
|
4237
4687
|
|
|
4238
4688
|
// src/tui/settings.ts
|
|
4239
4689
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
4240
|
-
import { join } from "path";
|
|
4690
|
+
import { join as join3 } from "path";
|
|
4241
4691
|
import { z } from "zod";
|
|
4242
4692
|
var SettingsSchema = z.object({ lang: z.enum(["fr", "en"]).optional() }).passthrough();
|
|
4243
|
-
var settingsPath = () =>
|
|
4693
|
+
var settingsPath = () => join3(ledgeDir(), "tui.json");
|
|
4244
4694
|
function defaultLang(env) {
|
|
4245
4695
|
const locale = env.LC_ALL || env.LC_MESSAGES || env.LANG || "";
|
|
4246
4696
|
return /^fr/i.test(locale) ? "fr" : "en";
|
|
@@ -4261,9 +4711,9 @@ function saveSettings(settings) {
|
|
|
4261
4711
|
}
|
|
4262
4712
|
|
|
4263
4713
|
// src/tui/terminalProfile.ts
|
|
4264
|
-
import { execFile, spawn as
|
|
4265
|
-
import { mkdir, readFile, realpath, writeFile } from "fs/promises";
|
|
4266
|
-
import { dirname, join as
|
|
4714
|
+
import { execFile as execFile2, spawn as spawn3 } from "child_process";
|
|
4715
|
+
import { mkdir as mkdir2, readFile as readFile2, realpath, writeFile as writeFile2 } from "fs/promises";
|
|
4716
|
+
import { dirname, join as join4 } from "path";
|
|
4267
4717
|
import { fileURLToPath } from "url";
|
|
4268
4718
|
import { promisify } from "util";
|
|
4269
4719
|
var PROFILE = "Ledge";
|
|
@@ -4273,7 +4723,7 @@ var SETTINGS = [
|
|
|
4273
4723
|
"Packages/Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe/LocalState/settings.json",
|
|
4274
4724
|
"Microsoft/Windows Terminal/settings.json"
|
|
4275
4725
|
];
|
|
4276
|
-
var fragmentDir = (localAppData) =>
|
|
4726
|
+
var fragmentDir = (localAppData) => join4(localAppData, "Microsoft", "Windows Terminal", "Fragments", PROFILE);
|
|
4277
4727
|
var PROFILE_ID = "{d10fee13-0978-595f-a851-b60ec80f11c9}";
|
|
4278
4728
|
function movesToLedgeTab(env, here, script) {
|
|
4279
4729
|
return !here && !!env.WT_SESSION && !!env.WSL_DISTRO_NAME && !env.TMUX && env.WT_PROFILE_ID !== PROFILE_ID && !script.endsWith(".ts");
|
|
@@ -4301,27 +4751,27 @@ function fragment(commandline, iconWin) {
|
|
|
4301
4751
|
}
|
|
4302
4752
|
async function profileLoaded(localAppData) {
|
|
4303
4753
|
for (const rel of SETTINGS) {
|
|
4304
|
-
const text = await
|
|
4754
|
+
const text = await readFile2(join4(localAppData, rel), "utf8").catch(() => "");
|
|
4305
4755
|
if (new RegExp(`"source"\\s*:\\s*"${PROFILE}"`).test(text)) return true;
|
|
4306
4756
|
}
|
|
4307
4757
|
return false;
|
|
4308
4758
|
}
|
|
4309
4759
|
async function writeIfChanged(path, content) {
|
|
4310
|
-
const current = await
|
|
4760
|
+
const current = await readFile2(path).catch(() => null);
|
|
4311
4761
|
if (current && current.equals(Buffer.from(content))) return false;
|
|
4312
|
-
await
|
|
4762
|
+
await writeFile2(path, content);
|
|
4313
4763
|
return true;
|
|
4314
4764
|
}
|
|
4315
4765
|
async function openInLedgeTab(o) {
|
|
4316
4766
|
const commandline = profileCommandline(o.distro, o.launcher);
|
|
4317
4767
|
if (!commandline) return false;
|
|
4318
|
-
await
|
|
4768
|
+
await mkdir2(dirname(o.launcher), { recursive: true });
|
|
4319
4769
|
await writeIfChanged(o.launcher, launcherScript(o.node, o.script, o.ledgeHome));
|
|
4320
4770
|
const dir = fragmentDir(o.localAppData.linux);
|
|
4321
|
-
await
|
|
4322
|
-
await writeIfChanged(
|
|
4771
|
+
await mkdir2(dir, { recursive: true });
|
|
4772
|
+
await writeIfChanged(join4(dir, "ledge.ico"), await readFile2(o.icon ?? ICON));
|
|
4323
4773
|
const iconWin = `${o.localAppData.win}\\Microsoft\\Windows Terminal\\Fragments\\${PROFILE}\\ledge.ico`;
|
|
4324
|
-
await writeIfChanged(
|
|
4774
|
+
await writeIfChanged(join4(dir, "ledge.json"), fragment(commandline, iconWin));
|
|
4325
4775
|
const deadline = Date.now() + (o.waitMs ?? 15e3);
|
|
4326
4776
|
for (let first = true; !await profileLoaded(o.localAppData.linux); first = false) {
|
|
4327
4777
|
if (Date.now() > deadline) return false;
|
|
@@ -4331,7 +4781,7 @@ async function openInLedgeTab(o) {
|
|
|
4331
4781
|
await o.wt(["-w", "0", "new-tab", "-p", PROFILE]);
|
|
4332
4782
|
return true;
|
|
4333
4783
|
}
|
|
4334
|
-
var run = promisify(
|
|
4784
|
+
var run = promisify(execFile2);
|
|
4335
4785
|
async function openTuiInLedgeTab(env, onWait) {
|
|
4336
4786
|
try {
|
|
4337
4787
|
const cmd = "/mnt/c/Windows/System32/cmd.exe";
|
|
@@ -4343,11 +4793,11 @@ async function openTuiInLedgeTab(env, onWait) {
|
|
|
4343
4793
|
distro: env.WSL_DISTRO_NAME,
|
|
4344
4794
|
node: await realpath(process.execPath),
|
|
4345
4795
|
script: await realpath(process.argv[1]),
|
|
4346
|
-
launcher:
|
|
4796
|
+
launcher: join4(ledgeDir(), "tui.sh"),
|
|
4347
4797
|
ledgeHome: env.LEDGE_HOME,
|
|
4348
4798
|
onWait,
|
|
4349
4799
|
wt: (args) => new Promise((resolve, reject) => {
|
|
4350
|
-
const child =
|
|
4800
|
+
const child = spawn3(join4(linux, "Microsoft", "WindowsApps", "wt.exe"), args, { cwd: "/mnt/c", stdio: "ignore", detached: true });
|
|
4351
4801
|
child.once("error", reject);
|
|
4352
4802
|
child.once("spawn", () => {
|
|
4353
4803
|
child.unref();
|
|
@@ -4361,7 +4811,7 @@ async function openTuiInLedgeTab(env, onWait) {
|
|
|
4361
4811
|
}
|
|
4362
4812
|
|
|
4363
4813
|
// src/tui/index.tsx
|
|
4364
|
-
import { jsx as
|
|
4814
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
4365
4815
|
var TUI_USAGE = "ledge tui [--notify bell|osc9|off] [--here]";
|
|
4366
4816
|
function tuiArgs(args) {
|
|
4367
4817
|
let notify = "bell";
|
|
@@ -4398,7 +4848,7 @@ async function runTui(args) {
|
|
|
4398
4848
|
const detach = attachClient(client);
|
|
4399
4849
|
client.connect();
|
|
4400
4850
|
const app = render(
|
|
4401
|
-
/* @__PURE__ */
|
|
4851
|
+
/* @__PURE__ */ jsx18(App, { client, lang, saveLang: (next2) => saveSettings({ ...loadSettings(), lang: next2 }), notify: parsed.notify, glyphs: glyphsFor(process.env), env: process.env }),
|
|
4402
4852
|
// Incremental: a key rewrites the lines it changed, not the whole screen erased then drawn again (the flicker).
|
|
4403
4853
|
{ alternateScreen: true, exitOnCtrlC: false, maxFps: 30, incrementalRendering: true }
|
|
4404
4854
|
);
|