skydive-cli 0.1.0-beta.239 → 0.1.0-beta.260
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/js/bin.mjs
CHANGED
|
@@ -31,7 +31,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
31
31
|
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region package.json
|
|
34
|
-
var version$1 = "0.1.0-beta.
|
|
34
|
+
var version$1 = "0.1.0-beta.260";
|
|
35
35
|
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region src/types.ts
|
|
@@ -2152,7 +2152,7 @@ const chatCommand = {
|
|
|
2152
2152
|
printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
|
|
2153
2153
|
process.exit(1);
|
|
2154
2154
|
}
|
|
2155
|
-
const { runChat } = await import("./boot-
|
|
2155
|
+
const { runChat } = await import("./boot-C5ztfr8K.mjs");
|
|
2156
2156
|
await runChat({
|
|
2157
2157
|
appUrl,
|
|
2158
2158
|
sessionToken: session.value.sessionToken,
|
|
@@ -3330,7 +3330,7 @@ const switchCommand = {
|
|
|
3330
3330
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
3331
3331
|
process.exit(1);
|
|
3332
3332
|
}
|
|
3333
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
3333
|
+
const { runWorkspacePicker } = await import("./boot-C5ztfr8K.mjs");
|
|
3334
3334
|
await runWorkspacePicker(session);
|
|
3335
3335
|
return;
|
|
3336
3336
|
}
|
|
@@ -16,6 +16,7 @@ import os, { homedir, platform, release, tmpdir } from "node:os";
|
|
|
16
16
|
import { createConnection } from "node:net";
|
|
17
17
|
import { access, appendFile, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
18
18
|
import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
19
|
+
import fuzzysort from "fuzzysort";
|
|
19
20
|
import { fileURLToPath } from "node:url";
|
|
20
21
|
import { fileTypeFromBuffer } from "file-type";
|
|
21
22
|
import { structuredPatch } from "diff";
|
|
@@ -1240,6 +1241,68 @@ function windowStart(highlight, total, visible) {
|
|
|
1240
1241
|
return Math.max(0, Math.min(desired, total - visible));
|
|
1241
1242
|
}
|
|
1242
1243
|
|
|
1244
|
+
//#endregion
|
|
1245
|
+
//#region src/chat/tui/fuzzy.ts
|
|
1246
|
+
/**
|
|
1247
|
+
* Rank `items` against `query` across multiple keys with fuzzysort — the
|
|
1248
|
+
* OpenCode pattern. An empty/whitespace query returns everything in the
|
|
1249
|
+
* caller's original order (fuzzysort has nothing to rank); otherwise items
|
|
1250
|
+
* where no key matches are dropped and the rest come back best-first.
|
|
1251
|
+
*/
|
|
1252
|
+
function fuzzyFilter(query, items, keys) {
|
|
1253
|
+
const needle = query.trim();
|
|
1254
|
+
if (!needle) return items.map((item) => ({
|
|
1255
|
+
item,
|
|
1256
|
+
highlights: keys.map(() => null)
|
|
1257
|
+
}));
|
|
1258
|
+
return fuzzysort.go(needle, items, { keys: [...keys] }).map((result) => ({
|
|
1259
|
+
item: result.obj,
|
|
1260
|
+
highlights: keys.map((_key, i) => {
|
|
1261
|
+
const indexes = result[i]?.indexes;
|
|
1262
|
+
return indexes !== void 0 && indexes.length > 0 ? indexes : null;
|
|
1263
|
+
})
|
|
1264
|
+
}));
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Split `text` into contiguous segments for rendering, marking the ones
|
|
1268
|
+
* covered by `indexes` (matched characters) so the view can color them.
|
|
1269
|
+
* Null/absent indexes yield a single unmatched segment.
|
|
1270
|
+
*/
|
|
1271
|
+
function highlightSegments(text, indexes) {
|
|
1272
|
+
if (!text) return [];
|
|
1273
|
+
if (!indexes || indexes.length === 0) return [{
|
|
1274
|
+
text,
|
|
1275
|
+
matched: false
|
|
1276
|
+
}];
|
|
1277
|
+
const matched = new Set(indexes);
|
|
1278
|
+
const segments = [];
|
|
1279
|
+
for (let i = 0; i < text.length; i++) {
|
|
1280
|
+
const isMatch = matched.has(i);
|
|
1281
|
+
const last = segments.at(-1);
|
|
1282
|
+
if (last && last.matched === isMatch) last.text += text[i];
|
|
1283
|
+
else segments.push({
|
|
1284
|
+
text: text[i] ?? "",
|
|
1285
|
+
matched: isMatch
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
return segments;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
//#endregion
|
|
1292
|
+
//#region src/chat/tui/fuzzy-text.tsx
|
|
1293
|
+
/**
|
|
1294
|
+
* Inline text with fuzzy-matched characters colored. Renders as spans, so it
|
|
1295
|
+
* must sit inside a <text>. `fg` styles the unmatched characters (defaults to
|
|
1296
|
+
* inheriting from the enclosing <text>); matches are always warning-colored,
|
|
1297
|
+
* which reads on both the plain and the selected (accent) row.
|
|
1298
|
+
*/
|
|
1299
|
+
function FuzzyText({ text, indexes, fg }) {
|
|
1300
|
+
return /* @__PURE__ */ jsx(Fragment, { children: highlightSegments(text, indexes).map((segment, i) => /* @__PURE__ */ jsx("span", {
|
|
1301
|
+
fg: segment.matched ? theme.warning : fg,
|
|
1302
|
+
children: segment.text
|
|
1303
|
+
}, i)) });
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1243
1306
|
//#endregion
|
|
1244
1307
|
//#region src/chat/tui/screens/agent-picker.tsx
|
|
1245
1308
|
function AgentPickerScreen() {
|
|
@@ -1332,12 +1395,12 @@ function FilterableAgentList({ agents, scope, onPick }) {
|
|
|
1332
1395
|
useEffect(() => {
|
|
1333
1396
|
setHighlight(0);
|
|
1334
1397
|
}, [scope]);
|
|
1335
|
-
const
|
|
1336
|
-
const filtered =
|
|
1398
|
+
const hits = fuzzyFilter(query, agents, agentKeys);
|
|
1399
|
+
const filtered = hits.map((h) => h.item);
|
|
1337
1400
|
const clamped = Math.min(highlight, Math.max(0, filtered.length - 1));
|
|
1338
1401
|
const visibleRows = Math.max(3, height - 6);
|
|
1339
1402
|
const start = windowStart(clamped, filtered.length, visibleRows);
|
|
1340
|
-
const windowed =
|
|
1403
|
+
const windowed = hits.slice(start, start + visibleRows);
|
|
1341
1404
|
useKeyboard((key) => {
|
|
1342
1405
|
if (key.name === "up") setHighlight(Math.max(0, clamped - 1));
|
|
1343
1406
|
else if (key.name === "down") setHighlight(Math.min(Math.max(0, filtered.length - 1), clamped + 1));
|
|
@@ -1399,28 +1462,33 @@ function FilterableAgentList({ agents, scope, onPick }) {
|
|
|
1399
1462
|
query,
|
|
1400
1463
|
"”"
|
|
1401
1464
|
]
|
|
1402
|
-
}) : windowed.map((
|
|
1465
|
+
}) : windowed.map((hit, i) => {
|
|
1466
|
+
const a = hit.item;
|
|
1403
1467
|
const selected = start + i === clamped;
|
|
1404
1468
|
return /* @__PURE__ */ jsxs("text", {
|
|
1405
1469
|
fg: selected ? theme.accent : theme.fg,
|
|
1406
|
-
children: [
|
|
1470
|
+
children: [
|
|
1471
|
+
selected ? "› " : " ",
|
|
1472
|
+
/* @__PURE__ */ jsx(FuzzyText, {
|
|
1473
|
+
text: a.name,
|
|
1474
|
+
indexes: hit.highlights[0]
|
|
1475
|
+
}),
|
|
1476
|
+
a.slug ? /* @__PURE__ */ jsxs(Fragment, { children: [" @", /* @__PURE__ */ jsx(FuzzyText, {
|
|
1477
|
+
text: a.slug,
|
|
1478
|
+
indexes: hit.highlights[1]
|
|
1479
|
+
})] }) : null
|
|
1480
|
+
]
|
|
1407
1481
|
}, a.id);
|
|
1408
1482
|
})
|
|
1409
1483
|
})
|
|
1410
1484
|
]
|
|
1411
1485
|
});
|
|
1412
1486
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
].join(" ").toLowerCase();
|
|
1419
|
-
}
|
|
1420
|
-
function agentLabel(a) {
|
|
1421
|
-
if (a.slug) return `${a.name} @${a.slug}`;
|
|
1422
|
-
return a.name;
|
|
1423
|
-
}
|
|
1487
|
+
const agentKeys = [
|
|
1488
|
+
(a) => a.name,
|
|
1489
|
+
(a) => a.slug ?? "",
|
|
1490
|
+
(a) => a.title ?? ""
|
|
1491
|
+
];
|
|
1424
1492
|
|
|
1425
1493
|
//#endregion
|
|
1426
1494
|
//#region src/chat/tui/agent-name.ts
|
|
@@ -1755,11 +1823,10 @@ function ConversationList({ agent, conversations, onPick }) {
|
|
|
1755
1823
|
const [list, setList] = useState(conversations);
|
|
1756
1824
|
const [confirmId, setConfirmId] = useState(null);
|
|
1757
1825
|
const [error, setError] = useState(null);
|
|
1758
|
-
const
|
|
1759
|
-
const
|
|
1760
|
-
const rows = [{ kind: "new" }, ...filtered.map((conv) => ({
|
|
1826
|
+
const hits = fuzzyFilter(query, list, conversationKeys);
|
|
1827
|
+
const rows = [{ kind: "new" }, ...hits.map((hit) => ({
|
|
1761
1828
|
kind: "conv",
|
|
1762
|
-
|
|
1829
|
+
hit
|
|
1763
1830
|
}))];
|
|
1764
1831
|
const clamped = Math.min(highlight, Math.max(0, rows.length - 1));
|
|
1765
1832
|
const visibleRows = Math.max(3, height - 6);
|
|
@@ -1777,7 +1844,7 @@ function ConversationList({ agent, conversations, onPick }) {
|
|
|
1777
1844
|
else onPick({
|
|
1778
1845
|
kind: "chat",
|
|
1779
1846
|
agent,
|
|
1780
|
-
conversation: row.
|
|
1847
|
+
conversation: row.hit.item
|
|
1781
1848
|
});
|
|
1782
1849
|
};
|
|
1783
1850
|
const deleteConversation = (id) => {
|
|
@@ -1804,7 +1871,7 @@ function ConversationList({ agent, conversations, onPick }) {
|
|
|
1804
1871
|
const row = rows[clamped];
|
|
1805
1872
|
if (row?.kind === "conv") {
|
|
1806
1873
|
setError(null);
|
|
1807
|
-
setConfirmId(row.
|
|
1874
|
+
setConfirmId(row.hit.item.id);
|
|
1808
1875
|
}
|
|
1809
1876
|
} else if (key.name === "return") {
|
|
1810
1877
|
const row = rows[clamped];
|
|
@@ -1849,7 +1916,7 @@ function ConversationList({ agent, conversations, onPick }) {
|
|
|
1849
1916
|
}) : /* @__PURE__ */ jsxs("text", {
|
|
1850
1917
|
fg: theme.dim,
|
|
1851
1918
|
children: [
|
|
1852
|
-
|
|
1919
|
+
hits.length,
|
|
1853
1920
|
"/",
|
|
1854
1921
|
list.length,
|
|
1855
1922
|
" · ↑/↓ move · ↵ open · ctrl+d delete · esc back"
|
|
@@ -1871,21 +1938,22 @@ function ConversationList({ agent, conversations, onPick }) {
|
|
|
1871
1938
|
fg,
|
|
1872
1939
|
children: [
|
|
1873
1940
|
selected ? "› " : " ",
|
|
1874
|
-
|
|
1941
|
+
/* @__PURE__ */ jsx(FuzzyText, {
|
|
1942
|
+
text: row.hit.item.title ?? "(untitled)",
|
|
1943
|
+
indexes: row.hit.item.title ? row.hit.highlights[0] : null
|
|
1944
|
+
}),
|
|
1875
1945
|
/* @__PURE__ */ jsxs("span", {
|
|
1876
1946
|
fg: theme.dim,
|
|
1877
|
-
children: [" ", previewLine(row.
|
|
1947
|
+
children: [" ", previewLine(row.hit.item)]
|
|
1878
1948
|
})
|
|
1879
1949
|
]
|
|
1880
|
-
}, row.
|
|
1950
|
+
}, row.hit.item.id);
|
|
1881
1951
|
})
|
|
1882
1952
|
})
|
|
1883
1953
|
]
|
|
1884
1954
|
});
|
|
1885
1955
|
}
|
|
1886
|
-
|
|
1887
|
-
return [c.title ?? "", c.preview ?? ""].join(" ").toLowerCase();
|
|
1888
|
-
}
|
|
1956
|
+
const conversationKeys = [(c) => c.title ?? "", (c) => c.preview ?? ""];
|
|
1889
1957
|
function previewLine(c) {
|
|
1890
1958
|
const when = relativeTime(c.updatedAt);
|
|
1891
1959
|
const prefix = c.preview ? c.preview.replace(/\s+/g, " ").trim() : "";
|
|
@@ -4311,12 +4379,12 @@ function FilterableModelList({ models, currentModel, modelLocked, agentId, onSel
|
|
|
4311
4379
|
const [saving, setSaving] = useState(false);
|
|
4312
4380
|
const [saveError, setSaveError] = useState(null);
|
|
4313
4381
|
const rest = useStore((s) => s.rest);
|
|
4314
|
-
const
|
|
4315
|
-
const filtered =
|
|
4382
|
+
const hits = fuzzyFilter(query, models, modelKeys);
|
|
4383
|
+
const filtered = hits.map((h) => h.item);
|
|
4316
4384
|
const clamped = Math.min(highlight, Math.max(0, filtered.length - 1));
|
|
4317
4385
|
const visibleRows = Math.max(3, height - 8);
|
|
4318
4386
|
const start = windowStart(clamped, filtered.length, visibleRows);
|
|
4319
|
-
const windowed =
|
|
4387
|
+
const windowed = hits.slice(start, start + visibleRows);
|
|
4320
4388
|
async function select(model) {
|
|
4321
4389
|
if (!rest || saving) return;
|
|
4322
4390
|
if (model.id === currentModel) {
|
|
@@ -4395,7 +4463,8 @@ function FilterableModelList({ models, currentModel, modelLocked, agentId, onSel
|
|
|
4395
4463
|
query,
|
|
4396
4464
|
"”"
|
|
4397
4465
|
]
|
|
4398
|
-
}) : windowed.map((
|
|
4466
|
+
}) : windowed.map((hit, i) => {
|
|
4467
|
+
const m = hit.item;
|
|
4399
4468
|
const selected = start + i === clamped;
|
|
4400
4469
|
const isCurrent = m.id === currentModel;
|
|
4401
4470
|
return /* @__PURE__ */ jsxs("text", {
|
|
@@ -4403,7 +4472,19 @@ function FilterableModelList({ models, currentModel, modelLocked, agentId, onSel
|
|
|
4403
4472
|
children: [
|
|
4404
4473
|
selected ? "› " : " ",
|
|
4405
4474
|
isCurrent ? "● " : " ",
|
|
4406
|
-
|
|
4475
|
+
m.providerDisplay ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FuzzyText, {
|
|
4476
|
+
text: m.providerDisplay,
|
|
4477
|
+
indexes: hit.highlights[1]
|
|
4478
|
+
}), " · "] }) : null,
|
|
4479
|
+
/* @__PURE__ */ jsx(FuzzyText, {
|
|
4480
|
+
text: m.displayName,
|
|
4481
|
+
indexes: hit.highlights[0]
|
|
4482
|
+
}),
|
|
4483
|
+
" ",
|
|
4484
|
+
/* @__PURE__ */ jsx(FuzzyText, {
|
|
4485
|
+
text: m.id,
|
|
4486
|
+
indexes: hit.highlights[2]
|
|
4487
|
+
})
|
|
4407
4488
|
]
|
|
4408
4489
|
}, m.id);
|
|
4409
4490
|
})
|
|
@@ -4415,16 +4496,11 @@ function FilterableModelList({ models, currentModel, modelLocked, agentId, onSel
|
|
|
4415
4496
|
]
|
|
4416
4497
|
});
|
|
4417
4498
|
}
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
].join(" ").toLowerCase();
|
|
4424
|
-
}
|
|
4425
|
-
function modelLabel(m) {
|
|
4426
|
-
return `${m.providerDisplay ? `${m.providerDisplay} · ` : ""}${m.displayName} ${m.id}`;
|
|
4427
|
-
}
|
|
4499
|
+
const modelKeys = [
|
|
4500
|
+
(m) => m.displayName,
|
|
4501
|
+
(m) => m.providerDisplay ?? "",
|
|
4502
|
+
(m) => m.id
|
|
4503
|
+
];
|
|
4428
4504
|
/**
|
|
4429
4505
|
* The REST error routes return `{ "error": "message" }`. Pull that out for a
|
|
4430
4506
|
* human-readable status line; fall back to the raw body on anything else.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skydive-cli",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.260",
|
|
4
4
|
"description": "Skydive CLI — manage AI agents from the command line",
|
|
5
5
|
"homepage": "https://skydive.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"diff": "9.0.0",
|
|
34
34
|
"eventsource-parser": "^3.0.8",
|
|
35
35
|
"file-type": "^21.3.4",
|
|
36
|
+
"fuzzysort": "^3.1.0",
|
|
36
37
|
"neverthrow": "^8.2.0",
|
|
37
38
|
"open": "^10.1.0",
|
|
38
39
|
"react": "^19.0.0",
|