bertrand 0.20.1 → 0.22.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/README.md +3 -2
- package/dist/bertrand.js +155 -48
- package/dist/dashboard/assets/{index-mg96b7q-.js → index-SB5vH7Ec.js} +2 -2
- package/dist/dashboard/index.html +1 -1
- package/dist/migrations/0004_dapper_apocalypse.sql +1 -0
- package/dist/migrations/meta/0004_snapshot.json +784 -0
- package/dist/migrations/meta/_journal.json +7 -0
- package/dist/run-screen.js +180 -67
- package/package.json +1 -1
package/dist/run-screen.js
CHANGED
|
@@ -15,7 +15,7 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// src/tui/run-screen.tsx
|
|
18
|
-
import { writeFileSync as writeFileSync2 } from "fs";
|
|
18
|
+
import { writeFileSync as writeFileSync2, appendFileSync } from "fs";
|
|
19
19
|
import { render } from "@orchetron/storm";
|
|
20
20
|
|
|
21
21
|
// src/tui/screens/launch/index.tsx
|
|
@@ -590,6 +590,7 @@ var paths = {
|
|
|
590
590
|
root: join(homedir(), BERTRAND_DIR),
|
|
591
591
|
hooks: join(homedir(), BERTRAND_DIR, "hooks"),
|
|
592
592
|
sessions: join(homedir(), BERTRAND_DIR, "sessions"),
|
|
593
|
+
runtime: join(homedir(), BERTRAND_DIR, "run"),
|
|
593
594
|
db: join(homedir(), BERTRAND_DIR, "bertrand.db"),
|
|
594
595
|
syncEnv: join(homedir(), BERTRAND_DIR, "sync.env")
|
|
595
596
|
};
|
|
@@ -759,6 +760,7 @@ var sessions = sqliteTable("sessions", {
|
|
|
759
760
|
enum: ["active", "waiting", "paused", "archived"]
|
|
760
761
|
}).notNull().default("paused"),
|
|
761
762
|
summary: text("summary"),
|
|
763
|
+
rating: integer("rating"),
|
|
762
764
|
pid: integer("pid"),
|
|
763
765
|
startedAt: text("started_at").notNull().default(sql`(datetime('now'))`),
|
|
764
766
|
endedAt: text("ended_at"),
|
|
@@ -898,6 +900,9 @@ function getAllSessions(opts) {
|
|
|
898
900
|
function updateSessionStatus(id, status) {
|
|
899
901
|
return getDb().update(sessions).set({ status, updatedAt: sql2`(datetime('now'))` }).where(eq(sessions.id, id)).returning().get();
|
|
900
902
|
}
|
|
903
|
+
function setSessionRating(id, rating) {
|
|
904
|
+
return getDb().update(sessions).set({ rating, updatedAt: sql2`(datetime('now'))` }).where(eq(sessions.id, id)).returning().get();
|
|
905
|
+
}
|
|
901
906
|
|
|
902
907
|
// src/lib/session-archive.ts
|
|
903
908
|
var ACTIVE_STATUSES = ["active", "waiting"];
|
|
@@ -959,6 +964,7 @@ function formatAgo(isoOrDate) {
|
|
|
959
964
|
}
|
|
960
965
|
|
|
961
966
|
// src/lib/parse-session-name.ts
|
|
967
|
+
var SEGMENT_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
962
968
|
function parseSessionName(input) {
|
|
963
969
|
const trimmed = input.trim().replace(/^\/+|\/+$/g, "");
|
|
964
970
|
if (!trimmed) {
|
|
@@ -969,12 +975,12 @@ function parseSessionName(input) {
|
|
|
969
975
|
throw new Error(`Session name must include at least one category: "category/session" (got "${trimmed}")`);
|
|
970
976
|
}
|
|
971
977
|
for (const segment of segments) {
|
|
972
|
-
if (
|
|
978
|
+
if (!SEGMENT_PATTERN.test(segment)) {
|
|
973
979
|
throw new Error(`Invalid segment "${segment}": must start with alphanumeric and contain only letters, digits, dots, underscores, or dashes`);
|
|
974
980
|
}
|
|
975
981
|
}
|
|
976
|
-
const
|
|
977
|
-
const
|
|
982
|
+
const categoryPath = segments[0];
|
|
983
|
+
const slug = segments.slice(1).join("/");
|
|
978
984
|
return { categoryPath, slug };
|
|
979
985
|
}
|
|
980
986
|
|
|
@@ -1324,7 +1330,7 @@ var OPTIONS = [
|
|
|
1324
1330
|
{
|
|
1325
1331
|
action: "resume",
|
|
1326
1332
|
label: "Resume",
|
|
1327
|
-
hint: "
|
|
1333
|
+
hint: "Continue a conversation or start a new one"
|
|
1328
1334
|
}
|
|
1329
1335
|
];
|
|
1330
1336
|
function Exit({ sessionId, onAction }) {
|
|
@@ -1332,10 +1338,19 @@ function Exit({ sessionId, onAction }) {
|
|
|
1332
1338
|
const [cursor, setCursor] = useState4(0);
|
|
1333
1339
|
const session = getSession(sessionId);
|
|
1334
1340
|
const conversations2 = session ? getConversationsBySession(session.id) : [];
|
|
1341
|
+
const [rating, setRating] = useState4(session?.rating ?? null);
|
|
1342
|
+
const persistRating = (next) => {
|
|
1343
|
+
setRating(next);
|
|
1344
|
+
setSessionRating(sessionId, next);
|
|
1345
|
+
};
|
|
1335
1346
|
useInput3((e) => {
|
|
1336
1347
|
if (e.key === "c" && e.ctrl)
|
|
1337
1348
|
exit();
|
|
1338
|
-
if (e.key
|
|
1349
|
+
if (e.key >= "1" && e.key <= "5") {
|
|
1350
|
+
persistRating(Number(e.key));
|
|
1351
|
+
} else if (e.key === "0" || e.key === "backspace") {
|
|
1352
|
+
persistRating(null);
|
|
1353
|
+
} else if (e.key === "up" || e.key === "k") {
|
|
1339
1354
|
setCursor((c) => Math.max(0, c - 1));
|
|
1340
1355
|
} else if (e.key === "down" || e.key === "j") {
|
|
1341
1356
|
setCursor((c) => Math.min(OPTIONS.length - 1, c + 1));
|
|
@@ -1399,6 +1414,29 @@ function Exit({ sessionId, onAction }) {
|
|
|
1399
1414
|
}, undefined, true, undefined, this)
|
|
1400
1415
|
]
|
|
1401
1416
|
}, undefined, true, undefined, this),
|
|
1417
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1418
|
+
flexDirection: "column",
|
|
1419
|
+
children: [
|
|
1420
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1421
|
+
dim: true,
|
|
1422
|
+
children: "How effective was this session?"
|
|
1423
|
+
}, undefined, false, undefined, this),
|
|
1424
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1425
|
+
flexDirection: "row",
|
|
1426
|
+
gap: 1,
|
|
1427
|
+
children: [
|
|
1428
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1429
|
+
color: "#FFD580",
|
|
1430
|
+
children: [1, 2, 3, 4, 5].map((n) => rating !== null && n <= rating ? "\u2605" : "\u2606").join(" ")
|
|
1431
|
+
}, undefined, false, undefined, this),
|
|
1432
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1433
|
+
dim: true,
|
|
1434
|
+
children: rating !== null ? `${rating} star${rating === 1 ? "" : "s"}` : "unrated"
|
|
1435
|
+
}, undefined, false, undefined, this)
|
|
1436
|
+
]
|
|
1437
|
+
}, undefined, true, undefined, this)
|
|
1438
|
+
]
|
|
1439
|
+
}, undefined, true, undefined, this),
|
|
1402
1440
|
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1403
1441
|
flexDirection: "column",
|
|
1404
1442
|
children: OPTIONS.map((opt, i) => /* @__PURE__ */ jsxDEV7(Box6, {
|
|
@@ -1422,7 +1460,7 @@ function Exit({ sessionId, onAction }) {
|
|
|
1422
1460
|
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1423
1461
|
children: /* @__PURE__ */ jsxDEV7(Text7, {
|
|
1424
1462
|
dim: true,
|
|
1425
|
-
children: "\u2191\u2193 navigate \xB7 enter select \xB7 q save & quit"
|
|
1463
|
+
children: "1-5 rate \xB7 0/backspace clear \xB7 \u2191\u2193 navigate \xB7 enter select \xB7 q save & quit"
|
|
1426
1464
|
}, undefined, false, undefined, this)
|
|
1427
1465
|
}, undefined, false, undefined, this)
|
|
1428
1466
|
]
|
|
@@ -1730,71 +1768,146 @@ if (!screen || !outputPath) {
|
|
|
1730
1768
|
console.error("Usage: run-screen <screen> <outputPath> [args...]");
|
|
1731
1769
|
process.exit(1);
|
|
1732
1770
|
}
|
|
1771
|
+
var RESULT_PATH = outputPath;
|
|
1772
|
+
var DEBUG_PATH = process.env.BERTRAND_DEBUG_TUI || null;
|
|
1773
|
+
var START_HR = process.hrtime.bigint();
|
|
1774
|
+
function phase(name, detail) {
|
|
1775
|
+
if (!DEBUG_PATH)
|
|
1776
|
+
return;
|
|
1777
|
+
const elapsedMs = Number(process.hrtime.bigint() - START_HR) / 1e6;
|
|
1778
|
+
const line = `${elapsedMs.toFixed(2).padStart(8)}ms ${name}${detail ? " " + detail : ""}
|
|
1779
|
+
`;
|
|
1780
|
+
try {
|
|
1781
|
+
appendFileSync(DEBUG_PATH, line);
|
|
1782
|
+
} catch {}
|
|
1783
|
+
}
|
|
1784
|
+
phase("spawn", `screen=${screen} pid=${process.pid} isTTY=${process.stdout.isTTY ?? "?"} term=${process.env.TERM_PROGRAM || process.env.TERM || "?"}`);
|
|
1733
1785
|
var result;
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1786
|
+
var resultWritten = false;
|
|
1787
|
+
function writeResult() {
|
|
1788
|
+
if (resultWritten)
|
|
1789
|
+
return;
|
|
1790
|
+
resultWritten = true;
|
|
1791
|
+
try {
|
|
1792
|
+
writeFileSync2(RESULT_PATH, JSON.stringify(result));
|
|
1793
|
+
} catch {}
|
|
1794
|
+
}
|
|
1795
|
+
process.on("SIGINT", writeResult);
|
|
1796
|
+
process.on("SIGTERM", writeResult);
|
|
1797
|
+
process.on("SIGHUP", writeResult);
|
|
1798
|
+
process.on("uncaughtException", writeResult);
|
|
1799
|
+
process.on("unhandledRejection", writeResult);
|
|
1800
|
+
var renderCount = 0;
|
|
1801
|
+
var onRender = (m) => {
|
|
1802
|
+
renderCount++;
|
|
1803
|
+
phase(`paint #${renderCount}`, `cells=${m.cellsChanged} took=${m.renderTime.toFixed(2)}ms`);
|
|
1804
|
+
};
|
|
1805
|
+
try {
|
|
1806
|
+
switch (screen) {
|
|
1807
|
+
case "launch": {
|
|
1808
|
+
let selection = { type: "quit" };
|
|
1809
|
+
result = selection;
|
|
1810
|
+
phase("render() pre");
|
|
1811
|
+
const app = render(/* @__PURE__ */ jsxDEV10(Launch, {
|
|
1812
|
+
onSelect: (s) => {
|
|
1813
|
+
selection = s;
|
|
1814
|
+
result = selection;
|
|
1815
|
+
}
|
|
1816
|
+
}, undefined, false, undefined, this), { alternateScreen: true, patchConsole: true, onRender });
|
|
1817
|
+
phase("render() post");
|
|
1818
|
+
app.screen.invalidate();
|
|
1819
|
+
app.requestRepaint();
|
|
1820
|
+
phase("post-mount invalidate+requestRepaint");
|
|
1821
|
+
await app.waitUntilExit();
|
|
1822
|
+
phase("waitUntilExit returned");
|
|
1823
|
+
app.unmount();
|
|
1824
|
+
phase("unmount done");
|
|
1825
|
+
result = selection;
|
|
1826
|
+
break;
|
|
1764
1827
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1828
|
+
case "project-picker": {
|
|
1829
|
+
let selection = { type: "quit" };
|
|
1830
|
+
result = selection;
|
|
1831
|
+
phase("render() pre");
|
|
1832
|
+
const app = render(/* @__PURE__ */ jsxDEV10(ProjectPicker, {
|
|
1833
|
+
onSelect: (s) => {
|
|
1834
|
+
selection = s;
|
|
1835
|
+
result = selection;
|
|
1836
|
+
}
|
|
1837
|
+
}, undefined, false, undefined, this), { alternateScreen: true, patchConsole: true, onRender });
|
|
1838
|
+
phase("render() post");
|
|
1839
|
+
app.screen.invalidate();
|
|
1840
|
+
app.requestRepaint();
|
|
1841
|
+
phase("post-mount invalidate+requestRepaint");
|
|
1842
|
+
await app.waitUntilExit();
|
|
1843
|
+
phase("waitUntilExit returned");
|
|
1844
|
+
app.unmount();
|
|
1845
|
+
phase("unmount done");
|
|
1846
|
+
result = selection;
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
case "exit": {
|
|
1850
|
+
const sessionId = args[0];
|
|
1851
|
+
if (!sessionId) {
|
|
1852
|
+
console.error("exit requires sessionId");
|
|
1853
|
+
process.exit(1);
|
|
1770
1854
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1855
|
+
let action = "save";
|
|
1856
|
+
result = action;
|
|
1857
|
+
phase("render() pre");
|
|
1858
|
+
const app = render(/* @__PURE__ */ jsxDEV10(Exit, {
|
|
1859
|
+
sessionId,
|
|
1860
|
+
onAction: (a) => {
|
|
1861
|
+
action = a;
|
|
1862
|
+
result = action;
|
|
1863
|
+
}
|
|
1864
|
+
}, undefined, false, undefined, this), { alternateScreen: true, patchConsole: true, onRender });
|
|
1865
|
+
phase("render() post");
|
|
1866
|
+
app.screen.invalidate();
|
|
1867
|
+
app.requestRepaint();
|
|
1868
|
+
phase("post-mount invalidate+requestRepaint");
|
|
1869
|
+
await app.waitUntilExit();
|
|
1870
|
+
phase("waitUntilExit returned");
|
|
1871
|
+
app.unmount();
|
|
1872
|
+
phase("unmount done");
|
|
1873
|
+
result = action;
|
|
1874
|
+
break;
|
|
1782
1875
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
sessionId
|
|
1786
|
-
|
|
1787
|
-
|
|
1876
|
+
case "resume": {
|
|
1877
|
+
const sessionId = args[0];
|
|
1878
|
+
if (!sessionId) {
|
|
1879
|
+
console.error("resume requires sessionId");
|
|
1880
|
+
process.exit(1);
|
|
1788
1881
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1882
|
+
let selection = { type: "back" };
|
|
1883
|
+
result = selection;
|
|
1884
|
+
phase("render() pre");
|
|
1885
|
+
const app = render(/* @__PURE__ */ jsxDEV10(Resume, {
|
|
1886
|
+
sessionId,
|
|
1887
|
+
onSelect: (s) => {
|
|
1888
|
+
selection = s;
|
|
1889
|
+
result = selection;
|
|
1890
|
+
}
|
|
1891
|
+
}, undefined, false, undefined, this), { alternateScreen: true, patchConsole: true, onRender });
|
|
1892
|
+
phase("render() post");
|
|
1893
|
+
app.screen.invalidate();
|
|
1894
|
+
app.requestRepaint();
|
|
1895
|
+
phase("post-mount invalidate+requestRepaint");
|
|
1896
|
+
await app.waitUntilExit();
|
|
1897
|
+
phase("waitUntilExit returned");
|
|
1898
|
+
app.unmount();
|
|
1899
|
+
phase("unmount done");
|
|
1900
|
+
result = selection;
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
default:
|
|
1904
|
+
console.error(`Unknown screen: ${screen}`);
|
|
1905
|
+
process.exit(1);
|
|
1794
1906
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1907
|
+
writeResult();
|
|
1908
|
+
phase("result written");
|
|
1909
|
+
} finally {
|
|
1910
|
+
writeResult();
|
|
1911
|
+
phase("finally complete");
|
|
1798
1912
|
}
|
|
1799
|
-
writeFileSync2(outputPath, JSON.stringify(result));
|
|
1800
1913
|
process.exit(0);
|