bertrand 0.19.0 → 0.20.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/bertrand.js +514 -179
- package/dist/dashboard/assets/{index-ofrZyO9k.js → index-DgvcDkSY.js} +101 -101
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/sw.js +1 -1
- package/dist/run-screen.js +464 -308
- package/package.json +1 -1
package/dist/run-screen.js
CHANGED
|
@@ -18,9 +18,14 @@ var __export = (target, all) => {
|
|
|
18
18
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
19
19
|
import { render } from "@orchetron/storm";
|
|
20
20
|
|
|
21
|
-
// src/tui/screens/
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
21
|
+
// src/tui/screens/startup/index.tsx
|
|
22
|
+
import { useState as useState4 } from "react";
|
|
23
|
+
import { useTui } from "@orchetron/storm";
|
|
24
|
+
|
|
25
|
+
// src/tui/screens/project-picker/index.tsx
|
|
26
|
+
import { useMemo as useMemo2 } from "react";
|
|
27
|
+
import { Box as Box5, Text as Text6 } from "@orchetron/storm";
|
|
28
|
+
import { existsSync as existsSync2 } from "fs";
|
|
24
29
|
|
|
25
30
|
// src/tui/components/app-details.tsx
|
|
26
31
|
import { Box, Text } from "@orchetron/storm";
|
|
@@ -560,16 +565,6 @@ function StatusDot({ status }) {
|
|
|
560
565
|
children: "\u25CF"
|
|
561
566
|
}, undefined, false, undefined, this);
|
|
562
567
|
}
|
|
563
|
-
// src/db/queries/sessions.ts
|
|
564
|
-
import { eq, and, inArray, sql as sql2 } from "drizzle-orm";
|
|
565
|
-
|
|
566
|
-
// src/db/client.ts
|
|
567
|
-
import { Database } from "bun:sqlite";
|
|
568
|
-
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
569
|
-
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
|
570
|
-
import { mkdirSync as mkdirSync2 } from "fs";
|
|
571
|
-
import { dirname } from "path";
|
|
572
|
-
|
|
573
568
|
// src/lib/projects/registry.ts
|
|
574
569
|
import {
|
|
575
570
|
existsSync,
|
|
@@ -580,6 +575,7 @@ import {
|
|
|
580
575
|
statSync,
|
|
581
576
|
writeFileSync
|
|
582
577
|
} from "fs";
|
|
578
|
+
import { randomBytes } from "crypto";
|
|
583
579
|
import { join as join2 } from "path";
|
|
584
580
|
|
|
585
581
|
// src/lib/paths.ts
|
|
@@ -620,6 +616,14 @@ function readRegistry() {
|
|
|
620
616
|
return null;
|
|
621
617
|
}
|
|
622
618
|
}
|
|
619
|
+
function writeRegistry(registry) {
|
|
620
|
+
const path = registryPath();
|
|
621
|
+
mkdirSync(_registryDir, { recursive: true });
|
|
622
|
+
const tmp = `${path}.tmp-${process.pid}-${process.hrtime.bigint()}-${randomBytes(4).toString("hex")}`;
|
|
623
|
+
writeFileSync(tmp, JSON.stringify(registry, null, 2) + `
|
|
624
|
+
`);
|
|
625
|
+
renameSync(tmp, path);
|
|
626
|
+
}
|
|
623
627
|
function recoverFromDisk() {
|
|
624
628
|
const dir = projectsDir();
|
|
625
629
|
if (!existsSync(dir))
|
|
@@ -669,6 +673,52 @@ function listProjects() {
|
|
|
669
673
|
function getActiveProjectSlug() {
|
|
670
674
|
return loadRegistry()?.activeProjectSlug ?? DEFAULT_PROJECT_SLUG;
|
|
671
675
|
}
|
|
676
|
+
function setActiveProjectSlug(slug) {
|
|
677
|
+
const registry = loadRegistry();
|
|
678
|
+
if (!registry) {
|
|
679
|
+
throw new Error(`No registry to update \u2014 create a project first (no ${registryPath()} and no ${projectsDir()}/* to recover from)`);
|
|
680
|
+
}
|
|
681
|
+
if (!registry.projects.some((p) => p.slug === slug)) {
|
|
682
|
+
throw new Error(`Unknown project slug "${slug}"`);
|
|
683
|
+
}
|
|
684
|
+
registry.activeProjectSlug = slug;
|
|
685
|
+
const idx = registry.projects.findIndex((p) => p.slug === slug);
|
|
686
|
+
if (idx !== -1) {
|
|
687
|
+
registry.projects[idx].lastUsedAt = new Date().toISOString();
|
|
688
|
+
}
|
|
689
|
+
writeRegistry(registry);
|
|
690
|
+
}
|
|
691
|
+
function registerProject(opts) {
|
|
692
|
+
const registry = loadRegistry() ?? {
|
|
693
|
+
activeProjectSlug: opts.slug,
|
|
694
|
+
projects: []
|
|
695
|
+
};
|
|
696
|
+
if (registry.projects.some((p) => p.slug === opts.slug)) {
|
|
697
|
+
throw new Error(`Project "${opts.slug}" already exists`);
|
|
698
|
+
}
|
|
699
|
+
const now = new Date().toISOString();
|
|
700
|
+
const entry = {
|
|
701
|
+
slug: opts.slug,
|
|
702
|
+
name: opts.name,
|
|
703
|
+
color: opts.color,
|
|
704
|
+
createdAt: now,
|
|
705
|
+
lastUsedAt: now
|
|
706
|
+
};
|
|
707
|
+
registry.projects.push(entry);
|
|
708
|
+
writeRegistry(registry);
|
|
709
|
+
return entry;
|
|
710
|
+
}
|
|
711
|
+
function removeProject(slug) {
|
|
712
|
+
const registry = loadRegistry();
|
|
713
|
+
if (!registry)
|
|
714
|
+
return;
|
|
715
|
+
registry.projects = registry.projects.filter((p) => p.slug !== slug);
|
|
716
|
+
if (registry.activeProjectSlug === slug) {
|
|
717
|
+
const survivor = [...registry.projects].sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt))[0];
|
|
718
|
+
registry.activeProjectSlug = survivor?.slug ?? DEFAULT_PROJECT_SLUG;
|
|
719
|
+
}
|
|
720
|
+
writeRegistry(registry);
|
|
721
|
+
}
|
|
672
722
|
function isRegistry(value) {
|
|
673
723
|
if (typeof value !== "object" || value === null)
|
|
674
724
|
return false;
|
|
@@ -697,6 +747,13 @@ function projectPaths(slug) {
|
|
|
697
747
|
};
|
|
698
748
|
}
|
|
699
749
|
|
|
750
|
+
// src/db/client.ts
|
|
751
|
+
import { Database } from "bun:sqlite";
|
|
752
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
753
|
+
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
|
754
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
755
|
+
import { dirname } from "path";
|
|
756
|
+
|
|
700
757
|
// src/lib/projects/resolve.ts
|
|
701
758
|
var _cached = null;
|
|
702
759
|
function resolveActiveProject() {
|
|
@@ -714,6 +771,9 @@ function resolveActiveProject() {
|
|
|
714
771
|
_cached = { slug, name, ...projectPaths(slug) };
|
|
715
772
|
return _cached;
|
|
716
773
|
}
|
|
774
|
+
function _resetActiveProjectCache() {
|
|
775
|
+
_cached = null;
|
|
776
|
+
}
|
|
717
777
|
|
|
718
778
|
// src/db/schema.ts
|
|
719
779
|
var exports_schema = {};
|
|
@@ -848,6 +908,7 @@ function openDb(dbPath) {
|
|
|
848
908
|
return cached;
|
|
849
909
|
mkdirSync2(dirname(dbPath), { recursive: true });
|
|
850
910
|
const sqlite = new Database(dbPath);
|
|
911
|
+
sqlite.exec("PRAGMA busy_timeout = 5000");
|
|
851
912
|
sqlite.exec("PRAGMA journal_mode = WAL");
|
|
852
913
|
sqlite.exec("PRAGMA foreign_keys = ON");
|
|
853
914
|
sqlite.exec("PRAGMA synchronous = NORMAL");
|
|
@@ -857,6 +918,10 @@ function openDb(dbPath) {
|
|
|
857
918
|
if (!_migrated.has(dbPath)) {
|
|
858
919
|
try {
|
|
859
920
|
migrate(db, { migrationsFolder: MIGRATIONS_FOLDER });
|
|
921
|
+
if (!hasSessionsTable(sqlite)) {
|
|
922
|
+
sqlite.exec("DROP TABLE IF EXISTS __drizzle_migrations");
|
|
923
|
+
migrate(db, { migrationsFolder: MIGRATIONS_FOLDER });
|
|
924
|
+
}
|
|
860
925
|
} catch (err) {
|
|
861
926
|
sqlite.close();
|
|
862
927
|
throw err;
|
|
@@ -866,6 +931,211 @@ function openDb(dbPath) {
|
|
|
866
931
|
_cache.set(dbPath, db);
|
|
867
932
|
return db;
|
|
868
933
|
}
|
|
934
|
+
function hasSessionsTable(sqlite) {
|
|
935
|
+
const row = sqlite.query("SELECT name FROM sqlite_master WHERE type='table' AND name='sessions'").get();
|
|
936
|
+
return row !== null;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/lib/format.ts
|
|
940
|
+
var SECOND = 1000;
|
|
941
|
+
var MINUTE = 60 * SECOND;
|
|
942
|
+
var HOUR = 60 * MINUTE;
|
|
943
|
+
var DAY = 24 * HOUR;
|
|
944
|
+
function formatDuration(ms) {
|
|
945
|
+
if (ms < MINUTE)
|
|
946
|
+
return `${Math.round(ms / SECOND)}s`;
|
|
947
|
+
const days = Math.floor(ms / DAY);
|
|
948
|
+
const hours = Math.floor(ms % DAY / HOUR);
|
|
949
|
+
const minutes = Math.floor(ms % HOUR / MINUTE);
|
|
950
|
+
if (days > 0)
|
|
951
|
+
return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
952
|
+
if (hours > 0)
|
|
953
|
+
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
954
|
+
return `${minutes}m`;
|
|
955
|
+
}
|
|
956
|
+
function formatAgo(isoOrDate) {
|
|
957
|
+
const date = typeof isoOrDate === "string" ? new Date(isoOrDate) : isoOrDate;
|
|
958
|
+
const ms = Date.now() - date.getTime();
|
|
959
|
+
if (ms < MINUTE)
|
|
960
|
+
return "just now";
|
|
961
|
+
if (ms < HOUR)
|
|
962
|
+
return `${Math.floor(ms / MINUTE)}m ago`;
|
|
963
|
+
if (ms < DAY)
|
|
964
|
+
return `${Math.floor(ms / HOUR)}h ago`;
|
|
965
|
+
if (ms < 2 * DAY)
|
|
966
|
+
return "yesterday";
|
|
967
|
+
if (ms < 7 * DAY)
|
|
968
|
+
return `${Math.floor(ms / DAY)}d ago`;
|
|
969
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/tui/screens/project-picker/index.tsx
|
|
973
|
+
import { jsxDEV as jsxDEV6, Fragment } from "react/jsx-dev-runtime";
|
|
974
|
+
var SLUG_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
975
|
+
function statsFor(slug) {
|
|
976
|
+
const dbFile = projectPaths(slug).db;
|
|
977
|
+
if (!existsSync2(dbFile))
|
|
978
|
+
return { total: 0, active: 0, unreadable: false };
|
|
979
|
+
try {
|
|
980
|
+
const db = getDbForProject(slug);
|
|
981
|
+
const all = db.select({ status: sessions.status }).from(sessions).all();
|
|
982
|
+
return {
|
|
983
|
+
total: all.length,
|
|
984
|
+
active: all.filter((s) => s.status === "active" || s.status === "waiting").length,
|
|
985
|
+
unreadable: false
|
|
986
|
+
};
|
|
987
|
+
} catch {
|
|
988
|
+
return { total: 0, active: 0, unreadable: true };
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function projectRow(entry, isActive) {
|
|
992
|
+
const stats = statsFor(entry.slug);
|
|
993
|
+
const sessionsLabel = stats.unreadable ? "?" : `${stats.active}/${stats.total}`;
|
|
994
|
+
const lastUsed = formatAgo(entry.lastUsedAt);
|
|
995
|
+
return {
|
|
996
|
+
value: entry.slug,
|
|
997
|
+
label: `${entry.slug} ${entry.name} ${sessionsLabel}`,
|
|
998
|
+
meta: lastUsed,
|
|
999
|
+
display: (isCursor) => {
|
|
1000
|
+
const cursorColor = "green";
|
|
1001
|
+
const slugColor = isCursor ? cursorColor : undefined;
|
|
1002
|
+
const nameColor = isCursor ? cursorColor : undefined;
|
|
1003
|
+
return /* @__PURE__ */ jsxDEV6(Fragment, {
|
|
1004
|
+
children: [
|
|
1005
|
+
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
1006
|
+
color: cursorColor,
|
|
1007
|
+
bold: true,
|
|
1008
|
+
children: isCursor ? "\u276F " : " "
|
|
1009
|
+
}, undefined, false, undefined, this),
|
|
1010
|
+
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
1011
|
+
bold: isActive,
|
|
1012
|
+
color: isActive ? "gold" : slugColor,
|
|
1013
|
+
children: isActive ? "\u25CF " : " "
|
|
1014
|
+
}, undefined, false, undefined, this),
|
|
1015
|
+
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
1016
|
+
color: slugColor,
|
|
1017
|
+
bold: isCursor || isActive,
|
|
1018
|
+
children: entry.slug.padEnd(16)
|
|
1019
|
+
}, undefined, false, undefined, this),
|
|
1020
|
+
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
1021
|
+
color: nameColor,
|
|
1022
|
+
dim: !isCursor,
|
|
1023
|
+
children: entry.name.padEnd(20)
|
|
1024
|
+
}, undefined, false, undefined, this),
|
|
1025
|
+
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
1026
|
+
dim: true,
|
|
1027
|
+
children: stats.unreadable ? " (unreadable)" : ` ${sessionsLabel} active/total`
|
|
1028
|
+
}, undefined, false, undefined, this)
|
|
1029
|
+
]
|
|
1030
|
+
}, undefined, true, undefined, this);
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function ProjectPicker({ onSelect }) {
|
|
1035
|
+
const allProjects = useMemo2(() => listProjects(), []);
|
|
1036
|
+
const activeSlug = useMemo2(() => getActiveProjectSlug(), []);
|
|
1037
|
+
const items = useMemo2(() => {
|
|
1038
|
+
return allProjects.slice().sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt)).map((p) => projectRow(p, p.slug === activeSlug));
|
|
1039
|
+
}, [allProjects, activeSlug]);
|
|
1040
|
+
const suggestions = useMemo2(() => allProjects.map((p) => p.slug), [allProjects]);
|
|
1041
|
+
const knownSlugs = useMemo2(() => new Set(allProjects.map((p) => p.slug)), [allProjects]);
|
|
1042
|
+
const select = (selection) => {
|
|
1043
|
+
onSelect(selection);
|
|
1044
|
+
};
|
|
1045
|
+
const handleSubmit = (value) => {
|
|
1046
|
+
const trimmed = value.trim();
|
|
1047
|
+
if (!trimmed)
|
|
1048
|
+
return;
|
|
1049
|
+
if (knownSlugs.has(trimmed)) {
|
|
1050
|
+
select({ type: "select", slug: trimmed });
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (!SLUG_PATTERN.test(trimmed)) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
select({ type: "create", slug: trimmed });
|
|
1057
|
+
};
|
|
1058
|
+
return /* @__PURE__ */ jsxDEV6(Box5, {
|
|
1059
|
+
flexDirection: "column",
|
|
1060
|
+
paddingY: 1,
|
|
1061
|
+
gap: 1,
|
|
1062
|
+
children: [
|
|
1063
|
+
/* @__PURE__ */ jsxDEV6(Box5, {
|
|
1064
|
+
marginX: 1,
|
|
1065
|
+
children: /* @__PURE__ */ jsxDEV6(Logo, {}, undefined, false, undefined, this)
|
|
1066
|
+
}, undefined, false, undefined, this),
|
|
1067
|
+
/* @__PURE__ */ jsxDEV6(Box5, {
|
|
1068
|
+
flexDirection: "column",
|
|
1069
|
+
marginX: 2,
|
|
1070
|
+
gap: 1,
|
|
1071
|
+
children: [
|
|
1072
|
+
/* @__PURE__ */ jsxDEV6(AppDetails, {}, undefined, false, undefined, this),
|
|
1073
|
+
/* @__PURE__ */ jsxDEV6(Box5, {
|
|
1074
|
+
flexDirection: "column",
|
|
1075
|
+
gap: 1,
|
|
1076
|
+
children: [
|
|
1077
|
+
/* @__PURE__ */ jsxDEV6(Box5, {
|
|
1078
|
+
flexDirection: "row",
|
|
1079
|
+
gap: 1,
|
|
1080
|
+
children: [
|
|
1081
|
+
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
1082
|
+
bold: true,
|
|
1083
|
+
children: "Projects"
|
|
1084
|
+
}, undefined, false, undefined, this),
|
|
1085
|
+
allProjects.length > 0 ? /* @__PURE__ */ jsxDEV6(Fragment, {
|
|
1086
|
+
children: [
|
|
1087
|
+
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
1088
|
+
dim: true,
|
|
1089
|
+
children: "\xB7"
|
|
1090
|
+
}, undefined, false, undefined, this),
|
|
1091
|
+
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
1092
|
+
dim: true,
|
|
1093
|
+
children: [
|
|
1094
|
+
allProjects.length,
|
|
1095
|
+
" total",
|
|
1096
|
+
activeSlug ? ` \xB7 active: ${activeSlug}` : ""
|
|
1097
|
+
]
|
|
1098
|
+
}, undefined, true, undefined, this)
|
|
1099
|
+
]
|
|
1100
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV6(Text6, {
|
|
1101
|
+
dim: true,
|
|
1102
|
+
children: "\xB7 none \u2014 type a slug to create your first project"
|
|
1103
|
+
}, undefined, false, undefined, this)
|
|
1104
|
+
]
|
|
1105
|
+
}, undefined, true, undefined, this),
|
|
1106
|
+
/* @__PURE__ */ jsxDEV6(Picker, {
|
|
1107
|
+
mode: "single",
|
|
1108
|
+
items,
|
|
1109
|
+
isFocused: true,
|
|
1110
|
+
maxVisible: 16,
|
|
1111
|
+
suggest: suggestions,
|
|
1112
|
+
placeholder: "Filter or type a new slug to create\u2026",
|
|
1113
|
+
emptyHint: "No projects. Type a slug to create one.",
|
|
1114
|
+
onSubmit: handleSubmit,
|
|
1115
|
+
onKey: (e) => {
|
|
1116
|
+
if (e.key === "c" && e.ctrl) {
|
|
1117
|
+
select({ type: "quit" });
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}, undefined, false, undefined, this),
|
|
1121
|
+
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
1122
|
+
dim: true,
|
|
1123
|
+
children: "\u2191\u2193 navigate \xB7 enter select \xB7 type to filter or create new \xB7 ctrl+c quit"
|
|
1124
|
+
}, undefined, false, undefined, this)
|
|
1125
|
+
]
|
|
1126
|
+
}, undefined, true, undefined, this)
|
|
1127
|
+
]
|
|
1128
|
+
}, undefined, true, undefined, this)
|
|
1129
|
+
]
|
|
1130
|
+
}, undefined, true, undefined, this);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// src/tui/screens/launch/index.tsx
|
|
1134
|
+
import { useMemo as useMemo3, useState as useState3 } from "react";
|
|
1135
|
+
import { Box as Box6, Text as Text7 } from "@orchetron/storm";
|
|
1136
|
+
|
|
1137
|
+
// src/db/queries/sessions.ts
|
|
1138
|
+
import { eq, and, inArray, sql as sql2 } from "drizzle-orm";
|
|
869
1139
|
|
|
870
1140
|
// src/lib/id.ts
|
|
871
1141
|
import { nanoid } from "nanoid";
|
|
@@ -916,39 +1186,6 @@ function unarchiveSession(id) {
|
|
|
916
1186
|
return { ok: true, session: updated };
|
|
917
1187
|
}
|
|
918
1188
|
|
|
919
|
-
// src/lib/format.ts
|
|
920
|
-
var SECOND = 1000;
|
|
921
|
-
var MINUTE = 60 * SECOND;
|
|
922
|
-
var HOUR = 60 * MINUTE;
|
|
923
|
-
var DAY = 24 * HOUR;
|
|
924
|
-
function formatDuration(ms) {
|
|
925
|
-
if (ms < MINUTE)
|
|
926
|
-
return `${Math.round(ms / SECOND)}s`;
|
|
927
|
-
const days = Math.floor(ms / DAY);
|
|
928
|
-
const hours = Math.floor(ms % DAY / HOUR);
|
|
929
|
-
const minutes = Math.floor(ms % HOUR / MINUTE);
|
|
930
|
-
if (days > 0)
|
|
931
|
-
return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
932
|
-
if (hours > 0)
|
|
933
|
-
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
934
|
-
return `${minutes}m`;
|
|
935
|
-
}
|
|
936
|
-
function formatAgo(isoOrDate) {
|
|
937
|
-
const date = typeof isoOrDate === "string" ? new Date(isoOrDate) : isoOrDate;
|
|
938
|
-
const ms = Date.now() - date.getTime();
|
|
939
|
-
if (ms < MINUTE)
|
|
940
|
-
return "just now";
|
|
941
|
-
if (ms < HOUR)
|
|
942
|
-
return `${Math.floor(ms / MINUTE)}m ago`;
|
|
943
|
-
if (ms < DAY)
|
|
944
|
-
return `${Math.floor(ms / HOUR)}h ago`;
|
|
945
|
-
if (ms < 2 * DAY)
|
|
946
|
-
return "yesterday";
|
|
947
|
-
if (ms < 7 * DAY)
|
|
948
|
-
return `${Math.floor(ms / DAY)}d ago`;
|
|
949
|
-
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
950
|
-
}
|
|
951
|
-
|
|
952
1189
|
// src/lib/parse-session-name.ts
|
|
953
1190
|
function parseSessionName(input) {
|
|
954
1191
|
const trimmed = input.trim().replace(/^\/+|\/+$/g, "");
|
|
@@ -970,7 +1207,7 @@ function parseSessionName(input) {
|
|
|
970
1207
|
}
|
|
971
1208
|
|
|
972
1209
|
// src/tui/screens/launch/index.tsx
|
|
973
|
-
import { jsxDEV as
|
|
1210
|
+
import { jsxDEV as jsxDEV7, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
974
1211
|
var STATUS_COLOR = {
|
|
975
1212
|
paused: "gold",
|
|
976
1213
|
waiting: "red",
|
|
@@ -1004,14 +1241,14 @@ function sessionRow(s) {
|
|
|
1004
1241
|
const textColor = isCursor ? cursorColor : color;
|
|
1005
1242
|
const slugColor = isCursor ? cursorColor : undefined;
|
|
1006
1243
|
const dimText = !isCursor && (disabled || isArchived);
|
|
1007
|
-
return /* @__PURE__ */
|
|
1244
|
+
return /* @__PURE__ */ jsxDEV7(Fragment2, {
|
|
1008
1245
|
children: [
|
|
1009
|
-
/* @__PURE__ */
|
|
1246
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1010
1247
|
color: cursorColor,
|
|
1011
1248
|
bold: true,
|
|
1012
1249
|
children: isCursor ? "\u276F " : " "
|
|
1013
1250
|
}, undefined, false, undefined, this),
|
|
1014
|
-
/* @__PURE__ */
|
|
1251
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1015
1252
|
color: dotColor,
|
|
1016
1253
|
bold: isCursor,
|
|
1017
1254
|
children: [
|
|
@@ -1019,17 +1256,17 @@ function sessionRow(s) {
|
|
|
1019
1256
|
" "
|
|
1020
1257
|
]
|
|
1021
1258
|
}, undefined, true, undefined, this),
|
|
1022
|
-
/* @__PURE__ */
|
|
1259
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1023
1260
|
color: textColor,
|
|
1024
1261
|
bold: isCursor,
|
|
1025
1262
|
dim: dimText,
|
|
1026
1263
|
children: status.padEnd(8)
|
|
1027
1264
|
}, undefined, false, undefined, this),
|
|
1028
|
-
/* @__PURE__ */
|
|
1265
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1029
1266
|
color: slugColor,
|
|
1030
1267
|
children: " "
|
|
1031
1268
|
}, undefined, false, undefined, this),
|
|
1032
|
-
/* @__PURE__ */
|
|
1269
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1033
1270
|
color: slugColor,
|
|
1034
1271
|
bold: isCursor,
|
|
1035
1272
|
dim: dimText,
|
|
@@ -1048,13 +1285,12 @@ function categoryHeader(categoryPath) {
|
|
|
1048
1285
|
};
|
|
1049
1286
|
}
|
|
1050
1287
|
function Launch({ onSelect }) {
|
|
1051
|
-
const { exit } = useTui();
|
|
1052
1288
|
const [error, setError] = useState3(null);
|
|
1053
1289
|
const [notice, setNotice] = useState3(null);
|
|
1054
1290
|
const [showArchived, setShowArchived] = useState3(false);
|
|
1055
1291
|
const [refreshKey, setRefreshKey] = useState3(0);
|
|
1056
|
-
const allSessions =
|
|
1057
|
-
const visibleSessions =
|
|
1292
|
+
const allSessions = useMemo3(() => getAllSessions({ excludeArchived: !showArchived }), [showArchived, refreshKey]);
|
|
1293
|
+
const visibleSessions = useMemo3(() => {
|
|
1058
1294
|
return allSessions.filter((s) => {
|
|
1059
1295
|
const st = s.session.status;
|
|
1060
1296
|
if (st === "paused" || st === "waiting")
|
|
@@ -1072,7 +1308,7 @@ function Launch({ onSelect }) {
|
|
|
1072
1308
|
return recencyKey(b).localeCompare(recencyKey(a));
|
|
1073
1309
|
});
|
|
1074
1310
|
}, [allSessions, showArchived]);
|
|
1075
|
-
const items =
|
|
1311
|
+
const items = useMemo3(() => {
|
|
1076
1312
|
const rows = [];
|
|
1077
1313
|
let lastCategory = null;
|
|
1078
1314
|
for (const s of visibleSessions) {
|
|
@@ -1084,7 +1320,7 @@ function Launch({ onSelect }) {
|
|
|
1084
1320
|
}
|
|
1085
1321
|
return rows;
|
|
1086
1322
|
}, [visibleSessions]);
|
|
1087
|
-
const suggestions =
|
|
1323
|
+
const suggestions = useMemo3(() => {
|
|
1088
1324
|
const categories2 = new Set;
|
|
1089
1325
|
const names = [];
|
|
1090
1326
|
for (const s of allSessions) {
|
|
@@ -1093,7 +1329,7 @@ function Launch({ onSelect }) {
|
|
|
1093
1329
|
}
|
|
1094
1330
|
return [...categories2, ...names];
|
|
1095
1331
|
}, [allSessions]);
|
|
1096
|
-
const sessionByValue =
|
|
1332
|
+
const sessionByValue = useMemo3(() => {
|
|
1097
1333
|
const map = new Map;
|
|
1098
1334
|
for (const s of allSessions) {
|
|
1099
1335
|
map.set(`${s.categoryPath}/${s.session.slug}`, s);
|
|
@@ -1127,7 +1363,6 @@ function Launch({ onSelect }) {
|
|
|
1127
1363
|
};
|
|
1128
1364
|
const select = (selection) => {
|
|
1129
1365
|
onSelect(selection);
|
|
1130
|
-
exit();
|
|
1131
1366
|
};
|
|
1132
1367
|
const handleSubmit = (value) => {
|
|
1133
1368
|
const existing = sessionByValue.get(value);
|
|
@@ -1146,7 +1381,7 @@ function Launch({ onSelect }) {
|
|
|
1146
1381
|
setError(e instanceof Error ? e.message : "Invalid name");
|
|
1147
1382
|
}
|
|
1148
1383
|
};
|
|
1149
|
-
const counts =
|
|
1384
|
+
const counts = useMemo3(() => {
|
|
1150
1385
|
let paused = 0;
|
|
1151
1386
|
let waiting = 0;
|
|
1152
1387
|
let archived = 0;
|
|
@@ -1160,56 +1395,56 @@ function Launch({ onSelect }) {
|
|
|
1160
1395
|
}
|
|
1161
1396
|
return { paused, waiting, archived };
|
|
1162
1397
|
}, [visibleSessions]);
|
|
1163
|
-
return /* @__PURE__ */
|
|
1398
|
+
return /* @__PURE__ */ jsxDEV7(Box6, {
|
|
1164
1399
|
flexDirection: "column",
|
|
1165
1400
|
paddingY: 1,
|
|
1166
1401
|
gap: 1,
|
|
1167
1402
|
children: [
|
|
1168
|
-
/* @__PURE__ */
|
|
1403
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1169
1404
|
marginX: 1,
|
|
1170
|
-
children: /* @__PURE__ */
|
|
1405
|
+
children: /* @__PURE__ */ jsxDEV7(Logo, {}, undefined, false, undefined, this)
|
|
1171
1406
|
}, undefined, false, undefined, this),
|
|
1172
|
-
/* @__PURE__ */
|
|
1407
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1173
1408
|
flexDirection: "column",
|
|
1174
1409
|
marginX: 2,
|
|
1175
1410
|
gap: 1,
|
|
1176
1411
|
children: [
|
|
1177
|
-
/* @__PURE__ */
|
|
1178
|
-
/* @__PURE__ */
|
|
1412
|
+
/* @__PURE__ */ jsxDEV7(AppDetails, {}, undefined, false, undefined, this),
|
|
1413
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1179
1414
|
flexDirection: "column",
|
|
1180
1415
|
gap: 1,
|
|
1181
1416
|
children: [
|
|
1182
|
-
/* @__PURE__ */
|
|
1417
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1183
1418
|
flexDirection: "row",
|
|
1184
1419
|
gap: 1,
|
|
1185
1420
|
children: [
|
|
1186
|
-
/* @__PURE__ */
|
|
1421
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1187
1422
|
bold: true,
|
|
1188
1423
|
children: "Sessions"
|
|
1189
1424
|
}, undefined, false, undefined, this),
|
|
1190
|
-
visibleSessions.length === 0 ? /* @__PURE__ */
|
|
1425
|
+
visibleSessions.length === 0 ? /* @__PURE__ */ jsxDEV7(Text7, {
|
|
1191
1426
|
dim: true,
|
|
1192
1427
|
children: "\xB7 none \u2014 type category/slug to create"
|
|
1193
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */
|
|
1428
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV7(Fragment2, {
|
|
1194
1429
|
children: [
|
|
1195
|
-
/* @__PURE__ */
|
|
1430
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1196
1431
|
dim: true,
|
|
1197
1432
|
children: "\xB7"
|
|
1198
1433
|
}, undefined, false, undefined, this),
|
|
1199
|
-
/* @__PURE__ */
|
|
1434
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1200
1435
|
color: "gold",
|
|
1201
1436
|
children: [
|
|
1202
1437
|
counts.paused,
|
|
1203
1438
|
" paused"
|
|
1204
1439
|
]
|
|
1205
1440
|
}, undefined, true, undefined, this),
|
|
1206
|
-
counts.waiting > 0 && /* @__PURE__ */
|
|
1441
|
+
counts.waiting > 0 && /* @__PURE__ */ jsxDEV7(Fragment2, {
|
|
1207
1442
|
children: [
|
|
1208
|
-
/* @__PURE__ */
|
|
1443
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1209
1444
|
dim: true,
|
|
1210
1445
|
children: "\xB7"
|
|
1211
1446
|
}, undefined, false, undefined, this),
|
|
1212
|
-
/* @__PURE__ */
|
|
1447
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1213
1448
|
color: "red",
|
|
1214
1449
|
dim: true,
|
|
1215
1450
|
children: [
|
|
@@ -1219,13 +1454,13 @@ function Launch({ onSelect }) {
|
|
|
1219
1454
|
}, undefined, true, undefined, this)
|
|
1220
1455
|
]
|
|
1221
1456
|
}, undefined, true, undefined, this),
|
|
1222
|
-
showArchived && counts.archived > 0 && /* @__PURE__ */
|
|
1457
|
+
showArchived && counts.archived > 0 && /* @__PURE__ */ jsxDEV7(Fragment2, {
|
|
1223
1458
|
children: [
|
|
1224
|
-
/* @__PURE__ */
|
|
1459
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1225
1460
|
dim: true,
|
|
1226
1461
|
children: "\xB7"
|
|
1227
1462
|
}, undefined, false, undefined, this),
|
|
1228
|
-
/* @__PURE__ */
|
|
1463
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1229
1464
|
color: "purple",
|
|
1230
1465
|
dim: true,
|
|
1231
1466
|
children: [
|
|
@@ -1239,7 +1474,7 @@ function Launch({ onSelect }) {
|
|
|
1239
1474
|
}, undefined, true, undefined, this)
|
|
1240
1475
|
]
|
|
1241
1476
|
}, undefined, true, undefined, this),
|
|
1242
|
-
/* @__PURE__ */
|
|
1477
|
+
/* @__PURE__ */ jsxDEV7(Picker, {
|
|
1243
1478
|
mode: "single",
|
|
1244
1479
|
items,
|
|
1245
1480
|
isFocused: true,
|
|
@@ -1260,15 +1495,15 @@ function Launch({ onSelect }) {
|
|
|
1260
1495
|
}
|
|
1261
1496
|
}
|
|
1262
1497
|
}, undefined, false, undefined, this),
|
|
1263
|
-
notice && /* @__PURE__ */
|
|
1498
|
+
notice && /* @__PURE__ */ jsxDEV7(Text7, {
|
|
1264
1499
|
color: "green",
|
|
1265
1500
|
children: notice
|
|
1266
1501
|
}, undefined, false, undefined, this),
|
|
1267
|
-
error && /* @__PURE__ */
|
|
1502
|
+
error && /* @__PURE__ */ jsxDEV7(Text7, {
|
|
1268
1503
|
color: "red",
|
|
1269
1504
|
children: error
|
|
1270
1505
|
}, undefined, false, undefined, this),
|
|
1271
|
-
/* @__PURE__ */
|
|
1506
|
+
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
1272
1507
|
dim: true,
|
|
1273
1508
|
children: [
|
|
1274
1509
|
"\u2191\u2193 navigate \xB7 \u2190\u2192 skip category \xB7 enter continue/create \xB7 ctrl+a",
|
|
@@ -1288,9 +1523,104 @@ function Launch({ onSelect }) {
|
|
|
1288
1523
|
}, undefined, true, undefined, this);
|
|
1289
1524
|
}
|
|
1290
1525
|
|
|
1526
|
+
// src/lib/projects/create.ts
|
|
1527
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
1528
|
+
|
|
1529
|
+
// src/db/migrate.ts
|
|
1530
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
1531
|
+
import { drizzle as drizzle2 } from "drizzle-orm/bun-sqlite";
|
|
1532
|
+
import { migrate as migrate2 } from "drizzle-orm/bun-sqlite/migrator";
|
|
1533
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
1534
|
+
import { dirname as dirname2 } from "path";
|
|
1535
|
+
var MIGRATIONS_FOLDER2 = import.meta.dir + "/migrations";
|
|
1536
|
+
function runMigrations(dbPath) {
|
|
1537
|
+
const target = dbPath ?? resolveActiveProject().db;
|
|
1538
|
+
mkdirSync3(dirname2(target), { recursive: true });
|
|
1539
|
+
const sqlite = new Database2(target);
|
|
1540
|
+
sqlite.exec("PRAGMA busy_timeout = 5000");
|
|
1541
|
+
sqlite.exec("PRAGMA journal_mode = WAL");
|
|
1542
|
+
sqlite.exec("PRAGMA foreign_keys = ON");
|
|
1543
|
+
const db = drizzle2(sqlite);
|
|
1544
|
+
migrate2(db, { migrationsFolder: MIGRATIONS_FOLDER2 });
|
|
1545
|
+
sqlite.close();
|
|
1546
|
+
}
|
|
1547
|
+
if (false) {}
|
|
1548
|
+
|
|
1549
|
+
// src/lib/projects/create.ts
|
|
1550
|
+
function createProject(opts) {
|
|
1551
|
+
registerProject({ slug: opts.slug, name: opts.name ?? opts.slug });
|
|
1552
|
+
try {
|
|
1553
|
+
const paths2 = projectPaths(opts.slug);
|
|
1554
|
+
mkdirSync4(paths2.root, { recursive: true });
|
|
1555
|
+
runMigrations(paths2.db);
|
|
1556
|
+
} catch (err) {
|
|
1557
|
+
try {
|
|
1558
|
+
removeProject(opts.slug);
|
|
1559
|
+
} catch {}
|
|
1560
|
+
throw err;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// src/tui/screens/startup/index.tsx
|
|
1565
|
+
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
1566
|
+
function Startup({
|
|
1567
|
+
skipProjectPicker,
|
|
1568
|
+
initialProjectSlug,
|
|
1569
|
+
onSelect
|
|
1570
|
+
}) {
|
|
1571
|
+
const { exit } = useTui();
|
|
1572
|
+
const [activeSlug, setActiveSlug] = useState4(skipProjectPicker ? initialProjectSlug : null);
|
|
1573
|
+
const finish = (s) => {
|
|
1574
|
+
onSelect(s);
|
|
1575
|
+
exit();
|
|
1576
|
+
};
|
|
1577
|
+
const handleProjectSelect = (s) => {
|
|
1578
|
+
switch (s.type) {
|
|
1579
|
+
case "quit":
|
|
1580
|
+
finish({ type: "quit" });
|
|
1581
|
+
return;
|
|
1582
|
+
case "create":
|
|
1583
|
+
createProject({ slug: s.slug });
|
|
1584
|
+
setActiveProjectSlug(s.slug);
|
|
1585
|
+
_resetActiveProjectCache();
|
|
1586
|
+
setActiveSlug(s.slug);
|
|
1587
|
+
return;
|
|
1588
|
+
case "select":
|
|
1589
|
+
setActiveProjectSlug(s.slug);
|
|
1590
|
+
_resetActiveProjectCache();
|
|
1591
|
+
setActiveSlug(s.slug);
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
const handleLaunchSelect = (s) => {
|
|
1596
|
+
if (s.type === "quit" || activeSlug === null) {
|
|
1597
|
+
finish({ type: "quit" });
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (s.type === "create") {
|
|
1601
|
+
finish({
|
|
1602
|
+
type: "create",
|
|
1603
|
+
projectSlug: activeSlug,
|
|
1604
|
+
categoryPath: s.categoryPath,
|
|
1605
|
+
slug: s.slug
|
|
1606
|
+
});
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
finish({ type: "pick", projectSlug: activeSlug, sessionId: s.sessionId });
|
|
1610
|
+
};
|
|
1611
|
+
if (activeSlug === null) {
|
|
1612
|
+
return /* @__PURE__ */ jsxDEV8(ProjectPicker, {
|
|
1613
|
+
onSelect: handleProjectSelect
|
|
1614
|
+
}, undefined, false, undefined, this);
|
|
1615
|
+
}
|
|
1616
|
+
return /* @__PURE__ */ jsxDEV8(Launch, {
|
|
1617
|
+
onSelect: handleLaunchSelect
|
|
1618
|
+
}, undefined, false, undefined, this);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1291
1621
|
// src/tui/screens/Exit.tsx
|
|
1292
|
-
import { useState as
|
|
1293
|
-
import { Box as
|
|
1622
|
+
import { useState as useState5 } from "react";
|
|
1623
|
+
import { Box as Box7, Text as Text8, useInput as useInput3, useTui as useTui2 } from "@orchetron/storm";
|
|
1294
1624
|
|
|
1295
1625
|
// src/db/queries/conversations.ts
|
|
1296
1626
|
import { eq as eq2, and as and2, desc, sql as sql3 } from "drizzle-orm";
|
|
@@ -1299,7 +1629,7 @@ function getConversationsBySession(sessionId) {
|
|
|
1299
1629
|
}
|
|
1300
1630
|
|
|
1301
1631
|
// src/tui/screens/Exit.tsx
|
|
1302
|
-
import { jsxDEV as
|
|
1632
|
+
import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
|
|
1303
1633
|
var OPTIONS = [
|
|
1304
1634
|
{ action: "save", label: "Save", hint: "Keep session paused for later" },
|
|
1305
1635
|
{
|
|
@@ -1320,7 +1650,7 @@ var OPTIONS = [
|
|
|
1320
1650
|
];
|
|
1321
1651
|
function Exit({ sessionId, onAction }) {
|
|
1322
1652
|
const { exit } = useTui2();
|
|
1323
|
-
const [cursor, setCursor] =
|
|
1653
|
+
const [cursor, setCursor] = useState5(0);
|
|
1324
1654
|
const session = getSession(sessionId);
|
|
1325
1655
|
const conversations2 = session ? getConversationsBySession(session.id) : [];
|
|
1326
1656
|
useInput3((e) => {
|
|
@@ -1340,37 +1670,37 @@ function Exit({ sessionId, onAction }) {
|
|
|
1340
1670
|
}
|
|
1341
1671
|
});
|
|
1342
1672
|
if (!session) {
|
|
1343
|
-
return /* @__PURE__ */
|
|
1673
|
+
return /* @__PURE__ */ jsxDEV9(Text8, {
|
|
1344
1674
|
color: "red",
|
|
1345
1675
|
children: "Session not found"
|
|
1346
1676
|
}, undefined, false, undefined, this);
|
|
1347
1677
|
}
|
|
1348
1678
|
const duration = session.endedAt && session.startedAt ? formatDuration(new Date(session.endedAt).getTime() - new Date(session.startedAt).getTime()) : null;
|
|
1349
|
-
return /* @__PURE__ */
|
|
1679
|
+
return /* @__PURE__ */ jsxDEV9(Box7, {
|
|
1350
1680
|
flexDirection: "column",
|
|
1351
1681
|
padding: 1,
|
|
1352
1682
|
gap: 1,
|
|
1353
1683
|
children: [
|
|
1354
|
-
/* @__PURE__ */
|
|
1684
|
+
/* @__PURE__ */ jsxDEV9(Text8, {
|
|
1355
1685
|
bold: true,
|
|
1356
1686
|
color: "#82AAFF",
|
|
1357
1687
|
children: "Session ended"
|
|
1358
1688
|
}, undefined, false, undefined, this),
|
|
1359
|
-
/* @__PURE__ */
|
|
1689
|
+
/* @__PURE__ */ jsxDEV9(Box7, {
|
|
1360
1690
|
flexDirection: "column",
|
|
1361
1691
|
children: [
|
|
1362
|
-
/* @__PURE__ */
|
|
1692
|
+
/* @__PURE__ */ jsxDEV9(Box7, {
|
|
1363
1693
|
flexDirection: "row",
|
|
1364
1694
|
gap: 1,
|
|
1365
1695
|
children: [
|
|
1366
|
-
/* @__PURE__ */
|
|
1696
|
+
/* @__PURE__ */ jsxDEV9(StatusDot, {
|
|
1367
1697
|
status: session.status
|
|
1368
1698
|
}, undefined, false, undefined, this),
|
|
1369
|
-
/* @__PURE__ */
|
|
1699
|
+
/* @__PURE__ */ jsxDEV9(Text8, {
|
|
1370
1700
|
bold: true,
|
|
1371
1701
|
children: session.name
|
|
1372
1702
|
}, undefined, false, undefined, this),
|
|
1373
|
-
duration && /* @__PURE__ */
|
|
1703
|
+
duration && /* @__PURE__ */ jsxDEV9(Text8, {
|
|
1374
1704
|
dim: true,
|
|
1375
1705
|
children: [
|
|
1376
1706
|
"(",
|
|
@@ -1380,7 +1710,7 @@ function Exit({ sessionId, onAction }) {
|
|
|
1380
1710
|
}, undefined, true, undefined, this)
|
|
1381
1711
|
]
|
|
1382
1712
|
}, undefined, true, undefined, this),
|
|
1383
|
-
/* @__PURE__ */
|
|
1713
|
+
/* @__PURE__ */ jsxDEV9(Text8, {
|
|
1384
1714
|
dim: true,
|
|
1385
1715
|
children: [
|
|
1386
1716
|
conversations2.length,
|
|
@@ -1390,28 +1720,28 @@ function Exit({ sessionId, onAction }) {
|
|
|
1390
1720
|
}, undefined, true, undefined, this)
|
|
1391
1721
|
]
|
|
1392
1722
|
}, undefined, true, undefined, this),
|
|
1393
|
-
/* @__PURE__ */
|
|
1723
|
+
/* @__PURE__ */ jsxDEV9(Box7, {
|
|
1394
1724
|
flexDirection: "column",
|
|
1395
|
-
children: OPTIONS.map((opt, i) => /* @__PURE__ */
|
|
1725
|
+
children: OPTIONS.map((opt, i) => /* @__PURE__ */ jsxDEV9(Box7, {
|
|
1396
1726
|
flexDirection: "row",
|
|
1397
1727
|
gap: 1,
|
|
1398
1728
|
children: [
|
|
1399
|
-
/* @__PURE__ */
|
|
1729
|
+
/* @__PURE__ */ jsxDEV9(Text8, {
|
|
1400
1730
|
children: i === cursor ? "\u276F" : " "
|
|
1401
1731
|
}, undefined, false, undefined, this),
|
|
1402
|
-
/* @__PURE__ */
|
|
1732
|
+
/* @__PURE__ */ jsxDEV9(Text8, {
|
|
1403
1733
|
bold: i === cursor,
|
|
1404
1734
|
children: opt.label
|
|
1405
1735
|
}, undefined, false, undefined, this),
|
|
1406
|
-
/* @__PURE__ */
|
|
1736
|
+
/* @__PURE__ */ jsxDEV9(Text8, {
|
|
1407
1737
|
dim: true,
|
|
1408
1738
|
children: opt.hint
|
|
1409
1739
|
}, undefined, false, undefined, this)
|
|
1410
1740
|
]
|
|
1411
1741
|
}, opt.action, true, undefined, this))
|
|
1412
1742
|
}, undefined, false, undefined, this),
|
|
1413
|
-
/* @__PURE__ */
|
|
1414
|
-
children: /* @__PURE__ */
|
|
1743
|
+
/* @__PURE__ */ jsxDEV9(Box7, {
|
|
1744
|
+
children: /* @__PURE__ */ jsxDEV9(Text8, {
|
|
1415
1745
|
dim: true,
|
|
1416
1746
|
children: "\u2191\u2193 navigate \xB7 enter select \xB7 q save & quit"
|
|
1417
1747
|
}, undefined, false, undefined, this)
|
|
@@ -1421,12 +1751,12 @@ function Exit({ sessionId, onAction }) {
|
|
|
1421
1751
|
}
|
|
1422
1752
|
|
|
1423
1753
|
// src/tui/screens/Resume.tsx
|
|
1424
|
-
import { useState as
|
|
1425
|
-
import { Box as
|
|
1426
|
-
import { jsxDEV as
|
|
1754
|
+
import { useState as useState6 } from "react";
|
|
1755
|
+
import { Box as Box8, Text as Text9, useInput as useInput4, useTui as useTui3 } from "@orchetron/storm";
|
|
1756
|
+
import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
|
|
1427
1757
|
function Resume({ sessionId, onSelect }) {
|
|
1428
1758
|
const { exit } = useTui3();
|
|
1429
|
-
const [cursor, setCursor] =
|
|
1759
|
+
const [cursor, setCursor] = useState6(0);
|
|
1430
1760
|
const session = getSession(sessionId);
|
|
1431
1761
|
const conversations2 = getConversationsBySession(sessionId);
|
|
1432
1762
|
const totalOptions = conversations2.length + 1;
|
|
@@ -1453,17 +1783,17 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1453
1783
|
}
|
|
1454
1784
|
});
|
|
1455
1785
|
if (!session) {
|
|
1456
|
-
return /* @__PURE__ */
|
|
1786
|
+
return /* @__PURE__ */ jsxDEV10(Text9, {
|
|
1457
1787
|
color: "red",
|
|
1458
1788
|
children: "Session not found"
|
|
1459
1789
|
}, undefined, false, undefined, this);
|
|
1460
1790
|
}
|
|
1461
|
-
return /* @__PURE__ */
|
|
1791
|
+
return /* @__PURE__ */ jsxDEV10(Box8, {
|
|
1462
1792
|
flexDirection: "column",
|
|
1463
1793
|
padding: 1,
|
|
1464
1794
|
gap: 1,
|
|
1465
1795
|
children: [
|
|
1466
|
-
/* @__PURE__ */
|
|
1796
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1467
1797
|
bold: true,
|
|
1468
1798
|
color: "#82AAFF",
|
|
1469
1799
|
children: [
|
|
@@ -1471,17 +1801,17 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1471
1801
|
session.name
|
|
1472
1802
|
]
|
|
1473
1803
|
}, undefined, true, undefined, this),
|
|
1474
|
-
/* @__PURE__ */
|
|
1804
|
+
/* @__PURE__ */ jsxDEV10(Box8, {
|
|
1475
1805
|
flexDirection: "column",
|
|
1476
1806
|
children: [
|
|
1477
|
-
/* @__PURE__ */
|
|
1807
|
+
/* @__PURE__ */ jsxDEV10(Box8, {
|
|
1478
1808
|
flexDirection: "row",
|
|
1479
1809
|
gap: 1,
|
|
1480
1810
|
children: [
|
|
1481
|
-
/* @__PURE__ */
|
|
1811
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1482
1812
|
children: cursor === 0 ? "\u276F" : " "
|
|
1483
1813
|
}, undefined, false, undefined, this),
|
|
1484
|
-
/* @__PURE__ */
|
|
1814
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1485
1815
|
bold: cursor === 0,
|
|
1486
1816
|
color: "#34D399",
|
|
1487
1817
|
children: "+ New conversation"
|
|
@@ -1493,42 +1823,42 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1493
1823
|
const isSelected = cursor === idx;
|
|
1494
1824
|
const duration = conv.endedAt ? formatDuration(new Date(conv.endedAt).getTime() - new Date(conv.startedAt).getTime()) : "active";
|
|
1495
1825
|
const ago = formatAgo(conv.startedAt);
|
|
1496
|
-
return /* @__PURE__ */
|
|
1826
|
+
return /* @__PURE__ */ jsxDEV10(Box8, {
|
|
1497
1827
|
flexDirection: "row",
|
|
1498
1828
|
gap: 1,
|
|
1499
1829
|
children: [
|
|
1500
|
-
/* @__PURE__ */
|
|
1830
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1501
1831
|
children: isSelected ? "\u276F" : " "
|
|
1502
1832
|
}, undefined, false, undefined, this),
|
|
1503
|
-
/* @__PURE__ */
|
|
1833
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1504
1834
|
bold: isSelected,
|
|
1505
1835
|
dim: conv.discarded,
|
|
1506
1836
|
children: conv.id.slice(0, 8)
|
|
1507
1837
|
}, undefined, false, undefined, this),
|
|
1508
|
-
/* @__PURE__ */
|
|
1838
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1509
1839
|
dim: true,
|
|
1510
1840
|
children: ago
|
|
1511
1841
|
}, undefined, false, undefined, this),
|
|
1512
|
-
/* @__PURE__ */
|
|
1842
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1513
1843
|
dim: true,
|
|
1514
1844
|
children: "\xB7"
|
|
1515
1845
|
}, undefined, false, undefined, this),
|
|
1516
|
-
/* @__PURE__ */
|
|
1846
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1517
1847
|
dim: true,
|
|
1518
1848
|
children: [
|
|
1519
1849
|
conv.eventCount,
|
|
1520
1850
|
" events"
|
|
1521
1851
|
]
|
|
1522
1852
|
}, undefined, true, undefined, this),
|
|
1523
|
-
/* @__PURE__ */
|
|
1853
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1524
1854
|
dim: true,
|
|
1525
1855
|
children: "\xB7"
|
|
1526
1856
|
}, undefined, false, undefined, this),
|
|
1527
|
-
/* @__PURE__ */
|
|
1857
|
+
/* @__PURE__ */ jsxDEV10(Text9, {
|
|
1528
1858
|
dim: true,
|
|
1529
1859
|
children: duration
|
|
1530
1860
|
}, undefined, false, undefined, this),
|
|
1531
|
-
conv.discarded && /* @__PURE__ */
|
|
1861
|
+
conv.discarded && /* @__PURE__ */ jsxDEV10(Text9, {
|
|
1532
1862
|
color: "red",
|
|
1533
1863
|
dim: true,
|
|
1534
1864
|
children: "(discarded)"
|
|
@@ -1538,8 +1868,8 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1538
1868
|
})
|
|
1539
1869
|
]
|
|
1540
1870
|
}, undefined, true, undefined, this),
|
|
1541
|
-
/* @__PURE__ */
|
|
1542
|
-
children: /* @__PURE__ */
|
|
1871
|
+
/* @__PURE__ */ jsxDEV10(Box8, {
|
|
1872
|
+
children: /* @__PURE__ */ jsxDEV10(Text9, {
|
|
1543
1873
|
dim: true,
|
|
1544
1874
|
children: "\u2191\u2193 navigate \xB7 enter select \xB7 q back"
|
|
1545
1875
|
}, undefined, false, undefined, this)
|
|
@@ -1548,174 +1878,8 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1548
1878
|
}, undefined, true, undefined, this);
|
|
1549
1879
|
}
|
|
1550
1880
|
|
|
1551
|
-
// src/tui/screens/project-picker/index.tsx
|
|
1552
|
-
import { useMemo as useMemo3 } from "react";
|
|
1553
|
-
import { Box as Box8, Text as Text9, useTui as useTui4 } from "@orchetron/storm";
|
|
1554
|
-
import { existsSync as existsSync2 } from "fs";
|
|
1555
|
-
import { jsxDEV as jsxDEV9, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
1556
|
-
var SLUG_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
1557
|
-
function statsFor(slug) {
|
|
1558
|
-
const dbFile = projectPaths(slug).db;
|
|
1559
|
-
if (!existsSync2(dbFile))
|
|
1560
|
-
return { total: 0, active: 0, unreadable: false };
|
|
1561
|
-
try {
|
|
1562
|
-
const db = getDbForProject(slug);
|
|
1563
|
-
const all = db.select({ status: sessions.status }).from(sessions).all();
|
|
1564
|
-
return {
|
|
1565
|
-
total: all.length,
|
|
1566
|
-
active: all.filter((s) => s.status === "active" || s.status === "waiting").length,
|
|
1567
|
-
unreadable: false
|
|
1568
|
-
};
|
|
1569
|
-
} catch {
|
|
1570
|
-
return { total: 0, active: 0, unreadable: true };
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
function projectRow(entry, isActive) {
|
|
1574
|
-
const stats = statsFor(entry.slug);
|
|
1575
|
-
const sessionsLabel = stats.unreadable ? "?" : `${stats.active}/${stats.total}`;
|
|
1576
|
-
const lastUsed = formatAgo(entry.lastUsedAt);
|
|
1577
|
-
return {
|
|
1578
|
-
value: entry.slug,
|
|
1579
|
-
label: `${entry.slug} ${entry.name} ${sessionsLabel}`,
|
|
1580
|
-
meta: lastUsed,
|
|
1581
|
-
display: (isCursor) => {
|
|
1582
|
-
const cursorColor = "green";
|
|
1583
|
-
const slugColor = isCursor ? cursorColor : undefined;
|
|
1584
|
-
const nameColor = isCursor ? cursorColor : undefined;
|
|
1585
|
-
return /* @__PURE__ */ jsxDEV9(Fragment2, {
|
|
1586
|
-
children: [
|
|
1587
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
1588
|
-
color: cursorColor,
|
|
1589
|
-
bold: true,
|
|
1590
|
-
children: isCursor ? "\u276F " : " "
|
|
1591
|
-
}, undefined, false, undefined, this),
|
|
1592
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
1593
|
-
bold: isActive,
|
|
1594
|
-
color: isActive ? "gold" : slugColor,
|
|
1595
|
-
children: isActive ? "\u25CF " : " "
|
|
1596
|
-
}, undefined, false, undefined, this),
|
|
1597
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
1598
|
-
color: slugColor,
|
|
1599
|
-
bold: isCursor || isActive,
|
|
1600
|
-
children: entry.slug.padEnd(16)
|
|
1601
|
-
}, undefined, false, undefined, this),
|
|
1602
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
1603
|
-
color: nameColor,
|
|
1604
|
-
dim: !isCursor,
|
|
1605
|
-
children: entry.name.padEnd(20)
|
|
1606
|
-
}, undefined, false, undefined, this),
|
|
1607
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
1608
|
-
dim: true,
|
|
1609
|
-
children: stats.unreadable ? " (unreadable)" : ` ${sessionsLabel} active/total`
|
|
1610
|
-
}, undefined, false, undefined, this)
|
|
1611
|
-
]
|
|
1612
|
-
}, undefined, true, undefined, this);
|
|
1613
|
-
}
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
function ProjectPicker({ onSelect }) {
|
|
1617
|
-
const { exit } = useTui4();
|
|
1618
|
-
const allProjects = useMemo3(() => listProjects(), []);
|
|
1619
|
-
const activeSlug = useMemo3(() => getActiveProjectSlug(), []);
|
|
1620
|
-
const items = useMemo3(() => {
|
|
1621
|
-
return allProjects.slice().sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt)).map((p) => projectRow(p, p.slug === activeSlug));
|
|
1622
|
-
}, [allProjects, activeSlug]);
|
|
1623
|
-
const suggestions = useMemo3(() => allProjects.map((p) => p.slug), [allProjects]);
|
|
1624
|
-
const knownSlugs = useMemo3(() => new Set(allProjects.map((p) => p.slug)), [allProjects]);
|
|
1625
|
-
const select = (selection) => {
|
|
1626
|
-
onSelect(selection);
|
|
1627
|
-
exit();
|
|
1628
|
-
};
|
|
1629
|
-
const handleSubmit = (value) => {
|
|
1630
|
-
const trimmed = value.trim();
|
|
1631
|
-
if (!trimmed)
|
|
1632
|
-
return;
|
|
1633
|
-
if (knownSlugs.has(trimmed)) {
|
|
1634
|
-
select({ type: "select", slug: trimmed });
|
|
1635
|
-
return;
|
|
1636
|
-
}
|
|
1637
|
-
if (!SLUG_PATTERN.test(trimmed)) {
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
select({ type: "create", slug: trimmed });
|
|
1641
|
-
};
|
|
1642
|
-
return /* @__PURE__ */ jsxDEV9(Box8, {
|
|
1643
|
-
flexDirection: "column",
|
|
1644
|
-
paddingY: 1,
|
|
1645
|
-
gap: 1,
|
|
1646
|
-
children: [
|
|
1647
|
-
/* @__PURE__ */ jsxDEV9(Box8, {
|
|
1648
|
-
marginX: 1,
|
|
1649
|
-
children: /* @__PURE__ */ jsxDEV9(Logo, {}, undefined, false, undefined, this)
|
|
1650
|
-
}, undefined, false, undefined, this),
|
|
1651
|
-
/* @__PURE__ */ jsxDEV9(Box8, {
|
|
1652
|
-
flexDirection: "column",
|
|
1653
|
-
marginX: 2,
|
|
1654
|
-
gap: 1,
|
|
1655
|
-
children: [
|
|
1656
|
-
/* @__PURE__ */ jsxDEV9(AppDetails, {}, undefined, false, undefined, this),
|
|
1657
|
-
/* @__PURE__ */ jsxDEV9(Box8, {
|
|
1658
|
-
flexDirection: "column",
|
|
1659
|
-
gap: 1,
|
|
1660
|
-
children: [
|
|
1661
|
-
/* @__PURE__ */ jsxDEV9(Box8, {
|
|
1662
|
-
flexDirection: "row",
|
|
1663
|
-
gap: 1,
|
|
1664
|
-
children: [
|
|
1665
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
1666
|
-
bold: true,
|
|
1667
|
-
children: "Projects"
|
|
1668
|
-
}, undefined, false, undefined, this),
|
|
1669
|
-
allProjects.length > 0 ? /* @__PURE__ */ jsxDEV9(Fragment2, {
|
|
1670
|
-
children: [
|
|
1671
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
1672
|
-
dim: true,
|
|
1673
|
-
children: "\xB7"
|
|
1674
|
-
}, undefined, false, undefined, this),
|
|
1675
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
1676
|
-
dim: true,
|
|
1677
|
-
children: [
|
|
1678
|
-
allProjects.length,
|
|
1679
|
-
" total",
|
|
1680
|
-
activeSlug ? ` \xB7 active: ${activeSlug}` : ""
|
|
1681
|
-
]
|
|
1682
|
-
}, undefined, true, undefined, this)
|
|
1683
|
-
]
|
|
1684
|
-
}, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV9(Text9, {
|
|
1685
|
-
dim: true,
|
|
1686
|
-
children: "\xB7 none \u2014 type a slug to create your first project"
|
|
1687
|
-
}, undefined, false, undefined, this)
|
|
1688
|
-
]
|
|
1689
|
-
}, undefined, true, undefined, this),
|
|
1690
|
-
/* @__PURE__ */ jsxDEV9(Picker, {
|
|
1691
|
-
mode: "single",
|
|
1692
|
-
items,
|
|
1693
|
-
isFocused: true,
|
|
1694
|
-
maxVisible: 16,
|
|
1695
|
-
suggest: suggestions,
|
|
1696
|
-
placeholder: "Filter or type a new slug to create\u2026",
|
|
1697
|
-
emptyHint: "No projects. Type a slug to create one.",
|
|
1698
|
-
onSubmit: handleSubmit,
|
|
1699
|
-
onKey: (e) => {
|
|
1700
|
-
if (e.key === "c" && e.ctrl) {
|
|
1701
|
-
select({ type: "quit" });
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
}, undefined, false, undefined, this),
|
|
1705
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
1706
|
-
dim: true,
|
|
1707
|
-
children: "\u2191\u2193 navigate \xB7 enter select \xB7 type to filter or create new \xB7 ctrl+c quit"
|
|
1708
|
-
}, undefined, false, undefined, this)
|
|
1709
|
-
]
|
|
1710
|
-
}, undefined, true, undefined, this)
|
|
1711
|
-
]
|
|
1712
|
-
}, undefined, true, undefined, this)
|
|
1713
|
-
]
|
|
1714
|
-
}, undefined, true, undefined, this);
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
1881
|
// src/tui/run-screen.tsx
|
|
1718
|
-
import { jsxDEV as
|
|
1882
|
+
import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
|
|
1719
1883
|
var [, , screen, outputPath, ...args] = process.argv;
|
|
1720
1884
|
if (!screen || !outputPath) {
|
|
1721
1885
|
console.error("Usage: run-screen <screen> <outputPath> [args...]");
|
|
@@ -1723,21 +1887,13 @@ if (!screen || !outputPath) {
|
|
|
1723
1887
|
}
|
|
1724
1888
|
var result;
|
|
1725
1889
|
switch (screen) {
|
|
1726
|
-
case "
|
|
1727
|
-
|
|
1728
|
-
const
|
|
1729
|
-
onSelect: (s) => {
|
|
1730
|
-
selection = s;
|
|
1731
|
-
}
|
|
1732
|
-
}, undefined, false, undefined, this), { alternateScreen: true, patchConsole: true });
|
|
1733
|
-
await app.waitUntilExit();
|
|
1734
|
-
app.unmount();
|
|
1735
|
-
result = selection;
|
|
1736
|
-
break;
|
|
1737
|
-
}
|
|
1738
|
-
case "project-picker": {
|
|
1890
|
+
case "startup": {
|
|
1891
|
+
const skipProjectPicker = args[0] === "true";
|
|
1892
|
+
const initialProjectSlug = args[1] ?? "";
|
|
1739
1893
|
let selection = { type: "quit" };
|
|
1740
|
-
const app = render(/* @__PURE__ */
|
|
1894
|
+
const app = render(/* @__PURE__ */ jsxDEV11(Startup, {
|
|
1895
|
+
skipProjectPicker,
|
|
1896
|
+
initialProjectSlug,
|
|
1741
1897
|
onSelect: (s) => {
|
|
1742
1898
|
selection = s;
|
|
1743
1899
|
}
|
|
@@ -1754,7 +1910,7 @@ switch (screen) {
|
|
|
1754
1910
|
process.exit(1);
|
|
1755
1911
|
}
|
|
1756
1912
|
let action = "save";
|
|
1757
|
-
const app = render(/* @__PURE__ */
|
|
1913
|
+
const app = render(/* @__PURE__ */ jsxDEV11(Exit, {
|
|
1758
1914
|
sessionId,
|
|
1759
1915
|
onAction: (a) => {
|
|
1760
1916
|
action = a;
|
|
@@ -1772,7 +1928,7 @@ switch (screen) {
|
|
|
1772
1928
|
process.exit(1);
|
|
1773
1929
|
}
|
|
1774
1930
|
let selection = { type: "back" };
|
|
1775
|
-
const app = render(/* @__PURE__ */
|
|
1931
|
+
const app = render(/* @__PURE__ */ jsxDEV11(Resume, {
|
|
1776
1932
|
sessionId,
|
|
1777
1933
|
onSelect: (s) => {
|
|
1778
1934
|
selection = s;
|