bertrand 0.20.0 → 0.20.1

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.
@@ -18,14 +18,9 @@ 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/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";
21
+ // src/tui/screens/launch/index.tsx
22
+ import { useMemo as useMemo2, useState as useState3 } from "react";
23
+ import { Box as Box5, Text as Text6, useTui } from "@orchetron/storm";
29
24
 
30
25
  // src/tui/components/app-details.tsx
31
26
  import { Box, Text } from "@orchetron/storm";
@@ -565,6 +560,16 @@ function StatusDot({ status }) {
565
560
  children: "\u25CF"
566
561
  }, undefined, false, undefined, this);
567
562
  }
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
+
568
573
  // src/lib/projects/registry.ts
569
574
  import {
570
575
  existsSync,
@@ -575,7 +580,6 @@ import {
575
580
  statSync,
576
581
  writeFileSync
577
582
  } from "fs";
578
- import { randomBytes } from "crypto";
579
583
  import { join as join2 } from "path";
580
584
 
581
585
  // src/lib/paths.ts
@@ -616,14 +620,6 @@ function readRegistry() {
616
620
  return null;
617
621
  }
618
622
  }
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
- }
627
623
  function recoverFromDisk() {
628
624
  const dir = projectsDir();
629
625
  if (!existsSync(dir))
@@ -673,52 +669,6 @@ function listProjects() {
673
669
  function getActiveProjectSlug() {
674
670
  return loadRegistry()?.activeProjectSlug ?? DEFAULT_PROJECT_SLUG;
675
671
  }
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
- }
722
672
  function isRegistry(value) {
723
673
  if (typeof value !== "object" || value === null)
724
674
  return false;
@@ -747,13 +697,6 @@ function projectPaths(slug) {
747
697
  };
748
698
  }
749
699
 
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
-
757
700
  // src/lib/projects/resolve.ts
758
701
  var _cached = null;
759
702
  function resolveActiveProject() {
@@ -771,9 +714,6 @@ function resolveActiveProject() {
771
714
  _cached = { slug, name, ...projectPaths(slug) };
772
715
  return _cached;
773
716
  }
774
- function _resetActiveProjectCache() {
775
- _cached = null;
776
- }
777
717
 
778
718
  // src/db/schema.ts
779
719
  var exports_schema = {};
@@ -936,207 +876,6 @@ function hasSessionsTable(sqlite) {
936
876
  return row !== null;
937
877
  }
938
878
 
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";
1139
-
1140
879
  // src/lib/id.ts
1141
880
  import { nanoid } from "nanoid";
1142
881
 
@@ -1186,6 +925,39 @@ function unarchiveSession(id) {
1186
925
  return { ok: true, session: updated };
1187
926
  }
1188
927
 
928
+ // src/lib/format.ts
929
+ var SECOND = 1000;
930
+ var MINUTE = 60 * SECOND;
931
+ var HOUR = 60 * MINUTE;
932
+ var DAY = 24 * HOUR;
933
+ function formatDuration(ms) {
934
+ if (ms < MINUTE)
935
+ return `${Math.round(ms / SECOND)}s`;
936
+ const days = Math.floor(ms / DAY);
937
+ const hours = Math.floor(ms % DAY / HOUR);
938
+ const minutes = Math.floor(ms % HOUR / MINUTE);
939
+ if (days > 0)
940
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
941
+ if (hours > 0)
942
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
943
+ return `${minutes}m`;
944
+ }
945
+ function formatAgo(isoOrDate) {
946
+ const date = typeof isoOrDate === "string" ? new Date(isoOrDate) : isoOrDate;
947
+ const ms = Date.now() - date.getTime();
948
+ if (ms < MINUTE)
949
+ return "just now";
950
+ if (ms < HOUR)
951
+ return `${Math.floor(ms / MINUTE)}m ago`;
952
+ if (ms < DAY)
953
+ return `${Math.floor(ms / HOUR)}h ago`;
954
+ if (ms < 2 * DAY)
955
+ return "yesterday";
956
+ if (ms < 7 * DAY)
957
+ return `${Math.floor(ms / DAY)}d ago`;
958
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
959
+ }
960
+
1189
961
  // src/lib/parse-session-name.ts
1190
962
  function parseSessionName(input) {
1191
963
  const trimmed = input.trim().replace(/^\/+|\/+$/g, "");
@@ -1207,7 +979,7 @@ function parseSessionName(input) {
1207
979
  }
1208
980
 
1209
981
  // src/tui/screens/launch/index.tsx
1210
- import { jsxDEV as jsxDEV7, Fragment as Fragment2 } from "react/jsx-dev-runtime";
982
+ import { jsxDEV as jsxDEV6, Fragment } from "react/jsx-dev-runtime";
1211
983
  var STATUS_COLOR = {
1212
984
  paused: "gold",
1213
985
  waiting: "red",
@@ -1241,14 +1013,14 @@ function sessionRow(s) {
1241
1013
  const textColor = isCursor ? cursorColor : color;
1242
1014
  const slugColor = isCursor ? cursorColor : undefined;
1243
1015
  const dimText = !isCursor && (disabled || isArchived);
1244
- return /* @__PURE__ */ jsxDEV7(Fragment2, {
1016
+ return /* @__PURE__ */ jsxDEV6(Fragment, {
1245
1017
  children: [
1246
- /* @__PURE__ */ jsxDEV7(Text7, {
1018
+ /* @__PURE__ */ jsxDEV6(Text6, {
1247
1019
  color: cursorColor,
1248
1020
  bold: true,
1249
1021
  children: isCursor ? "\u276F " : " "
1250
1022
  }, undefined, false, undefined, this),
1251
- /* @__PURE__ */ jsxDEV7(Text7, {
1023
+ /* @__PURE__ */ jsxDEV6(Text6, {
1252
1024
  color: dotColor,
1253
1025
  bold: isCursor,
1254
1026
  children: [
@@ -1256,17 +1028,17 @@ function sessionRow(s) {
1256
1028
  " "
1257
1029
  ]
1258
1030
  }, undefined, true, undefined, this),
1259
- /* @__PURE__ */ jsxDEV7(Text7, {
1031
+ /* @__PURE__ */ jsxDEV6(Text6, {
1260
1032
  color: textColor,
1261
1033
  bold: isCursor,
1262
1034
  dim: dimText,
1263
1035
  children: status.padEnd(8)
1264
1036
  }, undefined, false, undefined, this),
1265
- /* @__PURE__ */ jsxDEV7(Text7, {
1037
+ /* @__PURE__ */ jsxDEV6(Text6, {
1266
1038
  color: slugColor,
1267
1039
  children: " "
1268
1040
  }, undefined, false, undefined, this),
1269
- /* @__PURE__ */ jsxDEV7(Text7, {
1041
+ /* @__PURE__ */ jsxDEV6(Text6, {
1270
1042
  color: slugColor,
1271
1043
  bold: isCursor,
1272
1044
  dim: dimText,
@@ -1285,12 +1057,13 @@ function categoryHeader(categoryPath) {
1285
1057
  };
1286
1058
  }
1287
1059
  function Launch({ onSelect }) {
1060
+ const { exit } = useTui();
1288
1061
  const [error, setError] = useState3(null);
1289
1062
  const [notice, setNotice] = useState3(null);
1290
1063
  const [showArchived, setShowArchived] = useState3(false);
1291
1064
  const [refreshKey, setRefreshKey] = useState3(0);
1292
- const allSessions = useMemo3(() => getAllSessions({ excludeArchived: !showArchived }), [showArchived, refreshKey]);
1293
- const visibleSessions = useMemo3(() => {
1065
+ const allSessions = useMemo2(() => getAllSessions({ excludeArchived: !showArchived }), [showArchived, refreshKey]);
1066
+ const visibleSessions = useMemo2(() => {
1294
1067
  return allSessions.filter((s) => {
1295
1068
  const st = s.session.status;
1296
1069
  if (st === "paused" || st === "waiting")
@@ -1308,7 +1081,7 @@ function Launch({ onSelect }) {
1308
1081
  return recencyKey(b).localeCompare(recencyKey(a));
1309
1082
  });
1310
1083
  }, [allSessions, showArchived]);
1311
- const items = useMemo3(() => {
1084
+ const items = useMemo2(() => {
1312
1085
  const rows = [];
1313
1086
  let lastCategory = null;
1314
1087
  for (const s of visibleSessions) {
@@ -1320,7 +1093,7 @@ function Launch({ onSelect }) {
1320
1093
  }
1321
1094
  return rows;
1322
1095
  }, [visibleSessions]);
1323
- const suggestions = useMemo3(() => {
1096
+ const suggestions = useMemo2(() => {
1324
1097
  const categories2 = new Set;
1325
1098
  const names = [];
1326
1099
  for (const s of allSessions) {
@@ -1329,7 +1102,7 @@ function Launch({ onSelect }) {
1329
1102
  }
1330
1103
  return [...categories2, ...names];
1331
1104
  }, [allSessions]);
1332
- const sessionByValue = useMemo3(() => {
1105
+ const sessionByValue = useMemo2(() => {
1333
1106
  const map = new Map;
1334
1107
  for (const s of allSessions) {
1335
1108
  map.set(`${s.categoryPath}/${s.session.slug}`, s);
@@ -1363,6 +1136,7 @@ function Launch({ onSelect }) {
1363
1136
  };
1364
1137
  const select = (selection) => {
1365
1138
  onSelect(selection);
1139
+ exit();
1366
1140
  };
1367
1141
  const handleSubmit = (value) => {
1368
1142
  const existing = sessionByValue.get(value);
@@ -1381,7 +1155,7 @@ function Launch({ onSelect }) {
1381
1155
  setError(e instanceof Error ? e.message : "Invalid name");
1382
1156
  }
1383
1157
  };
1384
- const counts = useMemo3(() => {
1158
+ const counts = useMemo2(() => {
1385
1159
  let paused = 0;
1386
1160
  let waiting = 0;
1387
1161
  let archived = 0;
@@ -1395,56 +1169,56 @@ function Launch({ onSelect }) {
1395
1169
  }
1396
1170
  return { paused, waiting, archived };
1397
1171
  }, [visibleSessions]);
1398
- return /* @__PURE__ */ jsxDEV7(Box6, {
1172
+ return /* @__PURE__ */ jsxDEV6(Box5, {
1399
1173
  flexDirection: "column",
1400
1174
  paddingY: 1,
1401
1175
  gap: 1,
1402
1176
  children: [
1403
- /* @__PURE__ */ jsxDEV7(Box6, {
1177
+ /* @__PURE__ */ jsxDEV6(Box5, {
1404
1178
  marginX: 1,
1405
- children: /* @__PURE__ */ jsxDEV7(Logo, {}, undefined, false, undefined, this)
1179
+ children: /* @__PURE__ */ jsxDEV6(Logo, {}, undefined, false, undefined, this)
1406
1180
  }, undefined, false, undefined, this),
1407
- /* @__PURE__ */ jsxDEV7(Box6, {
1181
+ /* @__PURE__ */ jsxDEV6(Box5, {
1408
1182
  flexDirection: "column",
1409
1183
  marginX: 2,
1410
1184
  gap: 1,
1411
1185
  children: [
1412
- /* @__PURE__ */ jsxDEV7(AppDetails, {}, undefined, false, undefined, this),
1413
- /* @__PURE__ */ jsxDEV7(Box6, {
1186
+ /* @__PURE__ */ jsxDEV6(AppDetails, {}, undefined, false, undefined, this),
1187
+ /* @__PURE__ */ jsxDEV6(Box5, {
1414
1188
  flexDirection: "column",
1415
1189
  gap: 1,
1416
1190
  children: [
1417
- /* @__PURE__ */ jsxDEV7(Box6, {
1191
+ /* @__PURE__ */ jsxDEV6(Box5, {
1418
1192
  flexDirection: "row",
1419
1193
  gap: 1,
1420
1194
  children: [
1421
- /* @__PURE__ */ jsxDEV7(Text7, {
1195
+ /* @__PURE__ */ jsxDEV6(Text6, {
1422
1196
  bold: true,
1423
1197
  children: "Sessions"
1424
1198
  }, undefined, false, undefined, this),
1425
- visibleSessions.length === 0 ? /* @__PURE__ */ jsxDEV7(Text7, {
1199
+ visibleSessions.length === 0 ? /* @__PURE__ */ jsxDEV6(Text6, {
1426
1200
  dim: true,
1427
1201
  children: "\xB7 none \u2014 type category/slug to create"
1428
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV7(Fragment2, {
1202
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6(Fragment, {
1429
1203
  children: [
1430
- /* @__PURE__ */ jsxDEV7(Text7, {
1204
+ /* @__PURE__ */ jsxDEV6(Text6, {
1431
1205
  dim: true,
1432
1206
  children: "\xB7"
1433
1207
  }, undefined, false, undefined, this),
1434
- /* @__PURE__ */ jsxDEV7(Text7, {
1208
+ /* @__PURE__ */ jsxDEV6(Text6, {
1435
1209
  color: "gold",
1436
1210
  children: [
1437
1211
  counts.paused,
1438
1212
  " paused"
1439
1213
  ]
1440
1214
  }, undefined, true, undefined, this),
1441
- counts.waiting > 0 && /* @__PURE__ */ jsxDEV7(Fragment2, {
1215
+ counts.waiting > 0 && /* @__PURE__ */ jsxDEV6(Fragment, {
1442
1216
  children: [
1443
- /* @__PURE__ */ jsxDEV7(Text7, {
1217
+ /* @__PURE__ */ jsxDEV6(Text6, {
1444
1218
  dim: true,
1445
1219
  children: "\xB7"
1446
1220
  }, undefined, false, undefined, this),
1447
- /* @__PURE__ */ jsxDEV7(Text7, {
1221
+ /* @__PURE__ */ jsxDEV6(Text6, {
1448
1222
  color: "red",
1449
1223
  dim: true,
1450
1224
  children: [
@@ -1454,13 +1228,13 @@ function Launch({ onSelect }) {
1454
1228
  }, undefined, true, undefined, this)
1455
1229
  ]
1456
1230
  }, undefined, true, undefined, this),
1457
- showArchived && counts.archived > 0 && /* @__PURE__ */ jsxDEV7(Fragment2, {
1231
+ showArchived && counts.archived > 0 && /* @__PURE__ */ jsxDEV6(Fragment, {
1458
1232
  children: [
1459
- /* @__PURE__ */ jsxDEV7(Text7, {
1233
+ /* @__PURE__ */ jsxDEV6(Text6, {
1460
1234
  dim: true,
1461
1235
  children: "\xB7"
1462
1236
  }, undefined, false, undefined, this),
1463
- /* @__PURE__ */ jsxDEV7(Text7, {
1237
+ /* @__PURE__ */ jsxDEV6(Text6, {
1464
1238
  color: "purple",
1465
1239
  dim: true,
1466
1240
  children: [
@@ -1474,7 +1248,7 @@ function Launch({ onSelect }) {
1474
1248
  }, undefined, true, undefined, this)
1475
1249
  ]
1476
1250
  }, undefined, true, undefined, this),
1477
- /* @__PURE__ */ jsxDEV7(Picker, {
1251
+ /* @__PURE__ */ jsxDEV6(Picker, {
1478
1252
  mode: "single",
1479
1253
  items,
1480
1254
  isFocused: true,
@@ -1495,15 +1269,15 @@ function Launch({ onSelect }) {
1495
1269
  }
1496
1270
  }
1497
1271
  }, undefined, false, undefined, this),
1498
- notice && /* @__PURE__ */ jsxDEV7(Text7, {
1272
+ notice && /* @__PURE__ */ jsxDEV6(Text6, {
1499
1273
  color: "green",
1500
1274
  children: notice
1501
1275
  }, undefined, false, undefined, this),
1502
- error && /* @__PURE__ */ jsxDEV7(Text7, {
1276
+ error && /* @__PURE__ */ jsxDEV6(Text6, {
1503
1277
  color: "red",
1504
1278
  children: error
1505
1279
  }, undefined, false, undefined, this),
1506
- /* @__PURE__ */ jsxDEV7(Text7, {
1280
+ /* @__PURE__ */ jsxDEV6(Text6, {
1507
1281
  dim: true,
1508
1282
  children: [
1509
1283
  "\u2191\u2193 navigate \xB7 \u2190\u2192 skip category \xB7 enter continue/create \xB7 ctrl+a",
@@ -1523,104 +1297,9 @@ function Launch({ onSelect }) {
1523
1297
  }, undefined, true, undefined, this);
1524
1298
  }
1525
1299
 
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
-
1621
1300
  // src/tui/screens/Exit.tsx
1622
- import { useState as useState5 } from "react";
1623
- import { Box as Box7, Text as Text8, useInput as useInput3, useTui as useTui2 } from "@orchetron/storm";
1301
+ import { useState as useState4 } from "react";
1302
+ import { Box as Box6, Text as Text7, useInput as useInput3, useTui as useTui2 } from "@orchetron/storm";
1624
1303
 
1625
1304
  // src/db/queries/conversations.ts
1626
1305
  import { eq as eq2, and as and2, desc, sql as sql3 } from "drizzle-orm";
@@ -1629,7 +1308,7 @@ function getConversationsBySession(sessionId) {
1629
1308
  }
1630
1309
 
1631
1310
  // src/tui/screens/Exit.tsx
1632
- import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
1311
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
1633
1312
  var OPTIONS = [
1634
1313
  { action: "save", label: "Save", hint: "Keep session paused for later" },
1635
1314
  {
@@ -1650,7 +1329,7 @@ var OPTIONS = [
1650
1329
  ];
1651
1330
  function Exit({ sessionId, onAction }) {
1652
1331
  const { exit } = useTui2();
1653
- const [cursor, setCursor] = useState5(0);
1332
+ const [cursor, setCursor] = useState4(0);
1654
1333
  const session = getSession(sessionId);
1655
1334
  const conversations2 = session ? getConversationsBySession(session.id) : [];
1656
1335
  useInput3((e) => {
@@ -1670,37 +1349,37 @@ function Exit({ sessionId, onAction }) {
1670
1349
  }
1671
1350
  });
1672
1351
  if (!session) {
1673
- return /* @__PURE__ */ jsxDEV9(Text8, {
1352
+ return /* @__PURE__ */ jsxDEV7(Text7, {
1674
1353
  color: "red",
1675
1354
  children: "Session not found"
1676
1355
  }, undefined, false, undefined, this);
1677
1356
  }
1678
1357
  const duration = session.endedAt && session.startedAt ? formatDuration(new Date(session.endedAt).getTime() - new Date(session.startedAt).getTime()) : null;
1679
- return /* @__PURE__ */ jsxDEV9(Box7, {
1358
+ return /* @__PURE__ */ jsxDEV7(Box6, {
1680
1359
  flexDirection: "column",
1681
1360
  padding: 1,
1682
1361
  gap: 1,
1683
1362
  children: [
1684
- /* @__PURE__ */ jsxDEV9(Text8, {
1363
+ /* @__PURE__ */ jsxDEV7(Text7, {
1685
1364
  bold: true,
1686
1365
  color: "#82AAFF",
1687
1366
  children: "Session ended"
1688
1367
  }, undefined, false, undefined, this),
1689
- /* @__PURE__ */ jsxDEV9(Box7, {
1368
+ /* @__PURE__ */ jsxDEV7(Box6, {
1690
1369
  flexDirection: "column",
1691
1370
  children: [
1692
- /* @__PURE__ */ jsxDEV9(Box7, {
1371
+ /* @__PURE__ */ jsxDEV7(Box6, {
1693
1372
  flexDirection: "row",
1694
1373
  gap: 1,
1695
1374
  children: [
1696
- /* @__PURE__ */ jsxDEV9(StatusDot, {
1375
+ /* @__PURE__ */ jsxDEV7(StatusDot, {
1697
1376
  status: session.status
1698
1377
  }, undefined, false, undefined, this),
1699
- /* @__PURE__ */ jsxDEV9(Text8, {
1378
+ /* @__PURE__ */ jsxDEV7(Text7, {
1700
1379
  bold: true,
1701
1380
  children: session.name
1702
1381
  }, undefined, false, undefined, this),
1703
- duration && /* @__PURE__ */ jsxDEV9(Text8, {
1382
+ duration && /* @__PURE__ */ jsxDEV7(Text7, {
1704
1383
  dim: true,
1705
1384
  children: [
1706
1385
  "(",
@@ -1710,7 +1389,7 @@ function Exit({ sessionId, onAction }) {
1710
1389
  }, undefined, true, undefined, this)
1711
1390
  ]
1712
1391
  }, undefined, true, undefined, this),
1713
- /* @__PURE__ */ jsxDEV9(Text8, {
1392
+ /* @__PURE__ */ jsxDEV7(Text7, {
1714
1393
  dim: true,
1715
1394
  children: [
1716
1395
  conversations2.length,
@@ -1720,28 +1399,28 @@ function Exit({ sessionId, onAction }) {
1720
1399
  }, undefined, true, undefined, this)
1721
1400
  ]
1722
1401
  }, undefined, true, undefined, this),
1723
- /* @__PURE__ */ jsxDEV9(Box7, {
1402
+ /* @__PURE__ */ jsxDEV7(Box6, {
1724
1403
  flexDirection: "column",
1725
- children: OPTIONS.map((opt, i) => /* @__PURE__ */ jsxDEV9(Box7, {
1404
+ children: OPTIONS.map((opt, i) => /* @__PURE__ */ jsxDEV7(Box6, {
1726
1405
  flexDirection: "row",
1727
1406
  gap: 1,
1728
1407
  children: [
1729
- /* @__PURE__ */ jsxDEV9(Text8, {
1408
+ /* @__PURE__ */ jsxDEV7(Text7, {
1730
1409
  children: i === cursor ? "\u276F" : " "
1731
1410
  }, undefined, false, undefined, this),
1732
- /* @__PURE__ */ jsxDEV9(Text8, {
1411
+ /* @__PURE__ */ jsxDEV7(Text7, {
1733
1412
  bold: i === cursor,
1734
1413
  children: opt.label
1735
1414
  }, undefined, false, undefined, this),
1736
- /* @__PURE__ */ jsxDEV9(Text8, {
1415
+ /* @__PURE__ */ jsxDEV7(Text7, {
1737
1416
  dim: true,
1738
1417
  children: opt.hint
1739
1418
  }, undefined, false, undefined, this)
1740
1419
  ]
1741
1420
  }, opt.action, true, undefined, this))
1742
1421
  }, undefined, false, undefined, this),
1743
- /* @__PURE__ */ jsxDEV9(Box7, {
1744
- children: /* @__PURE__ */ jsxDEV9(Text8, {
1422
+ /* @__PURE__ */ jsxDEV7(Box6, {
1423
+ children: /* @__PURE__ */ jsxDEV7(Text7, {
1745
1424
  dim: true,
1746
1425
  children: "\u2191\u2193 navigate \xB7 enter select \xB7 q save & quit"
1747
1426
  }, undefined, false, undefined, this)
@@ -1751,12 +1430,12 @@ function Exit({ sessionId, onAction }) {
1751
1430
  }
1752
1431
 
1753
1432
  // src/tui/screens/Resume.tsx
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";
1433
+ import { useState as useState5 } from "react";
1434
+ import { Box as Box7, Text as Text8, useInput as useInput4, useTui as useTui3 } from "@orchetron/storm";
1435
+ import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
1757
1436
  function Resume({ sessionId, onSelect }) {
1758
1437
  const { exit } = useTui3();
1759
- const [cursor, setCursor] = useState6(0);
1438
+ const [cursor, setCursor] = useState5(0);
1760
1439
  const session = getSession(sessionId);
1761
1440
  const conversations2 = getConversationsBySession(sessionId);
1762
1441
  const totalOptions = conversations2.length + 1;
@@ -1783,17 +1462,17 @@ function Resume({ sessionId, onSelect }) {
1783
1462
  }
1784
1463
  });
1785
1464
  if (!session) {
1786
- return /* @__PURE__ */ jsxDEV10(Text9, {
1465
+ return /* @__PURE__ */ jsxDEV8(Text8, {
1787
1466
  color: "red",
1788
1467
  children: "Session not found"
1789
1468
  }, undefined, false, undefined, this);
1790
1469
  }
1791
- return /* @__PURE__ */ jsxDEV10(Box8, {
1470
+ return /* @__PURE__ */ jsxDEV8(Box7, {
1792
1471
  flexDirection: "column",
1793
1472
  padding: 1,
1794
1473
  gap: 1,
1795
1474
  children: [
1796
- /* @__PURE__ */ jsxDEV10(Text9, {
1475
+ /* @__PURE__ */ jsxDEV8(Text8, {
1797
1476
  bold: true,
1798
1477
  color: "#82AAFF",
1799
1478
  children: [
@@ -1801,17 +1480,17 @@ function Resume({ sessionId, onSelect }) {
1801
1480
  session.name
1802
1481
  ]
1803
1482
  }, undefined, true, undefined, this),
1804
- /* @__PURE__ */ jsxDEV10(Box8, {
1483
+ /* @__PURE__ */ jsxDEV8(Box7, {
1805
1484
  flexDirection: "column",
1806
1485
  children: [
1807
- /* @__PURE__ */ jsxDEV10(Box8, {
1486
+ /* @__PURE__ */ jsxDEV8(Box7, {
1808
1487
  flexDirection: "row",
1809
1488
  gap: 1,
1810
1489
  children: [
1811
- /* @__PURE__ */ jsxDEV10(Text9, {
1490
+ /* @__PURE__ */ jsxDEV8(Text8, {
1812
1491
  children: cursor === 0 ? "\u276F" : " "
1813
1492
  }, undefined, false, undefined, this),
1814
- /* @__PURE__ */ jsxDEV10(Text9, {
1493
+ /* @__PURE__ */ jsxDEV8(Text8, {
1815
1494
  bold: cursor === 0,
1816
1495
  color: "#34D399",
1817
1496
  children: "+ New conversation"
@@ -1823,42 +1502,42 @@ function Resume({ sessionId, onSelect }) {
1823
1502
  const isSelected = cursor === idx;
1824
1503
  const duration = conv.endedAt ? formatDuration(new Date(conv.endedAt).getTime() - new Date(conv.startedAt).getTime()) : "active";
1825
1504
  const ago = formatAgo(conv.startedAt);
1826
- return /* @__PURE__ */ jsxDEV10(Box8, {
1505
+ return /* @__PURE__ */ jsxDEV8(Box7, {
1827
1506
  flexDirection: "row",
1828
1507
  gap: 1,
1829
1508
  children: [
1830
- /* @__PURE__ */ jsxDEV10(Text9, {
1509
+ /* @__PURE__ */ jsxDEV8(Text8, {
1831
1510
  children: isSelected ? "\u276F" : " "
1832
1511
  }, undefined, false, undefined, this),
1833
- /* @__PURE__ */ jsxDEV10(Text9, {
1512
+ /* @__PURE__ */ jsxDEV8(Text8, {
1834
1513
  bold: isSelected,
1835
1514
  dim: conv.discarded,
1836
1515
  children: conv.id.slice(0, 8)
1837
1516
  }, undefined, false, undefined, this),
1838
- /* @__PURE__ */ jsxDEV10(Text9, {
1517
+ /* @__PURE__ */ jsxDEV8(Text8, {
1839
1518
  dim: true,
1840
1519
  children: ago
1841
1520
  }, undefined, false, undefined, this),
1842
- /* @__PURE__ */ jsxDEV10(Text9, {
1521
+ /* @__PURE__ */ jsxDEV8(Text8, {
1843
1522
  dim: true,
1844
1523
  children: "\xB7"
1845
1524
  }, undefined, false, undefined, this),
1846
- /* @__PURE__ */ jsxDEV10(Text9, {
1525
+ /* @__PURE__ */ jsxDEV8(Text8, {
1847
1526
  dim: true,
1848
1527
  children: [
1849
1528
  conv.eventCount,
1850
1529
  " events"
1851
1530
  ]
1852
1531
  }, undefined, true, undefined, this),
1853
- /* @__PURE__ */ jsxDEV10(Text9, {
1532
+ /* @__PURE__ */ jsxDEV8(Text8, {
1854
1533
  dim: true,
1855
1534
  children: "\xB7"
1856
1535
  }, undefined, false, undefined, this),
1857
- /* @__PURE__ */ jsxDEV10(Text9, {
1536
+ /* @__PURE__ */ jsxDEV8(Text8, {
1858
1537
  dim: true,
1859
1538
  children: duration
1860
1539
  }, undefined, false, undefined, this),
1861
- conv.discarded && /* @__PURE__ */ jsxDEV10(Text9, {
1540
+ conv.discarded && /* @__PURE__ */ jsxDEV8(Text8, {
1862
1541
  color: "red",
1863
1542
  dim: true,
1864
1543
  children: "(discarded)"
@@ -1868,8 +1547,8 @@ function Resume({ sessionId, onSelect }) {
1868
1547
  })
1869
1548
  ]
1870
1549
  }, undefined, true, undefined, this),
1871
- /* @__PURE__ */ jsxDEV10(Box8, {
1872
- children: /* @__PURE__ */ jsxDEV10(Text9, {
1550
+ /* @__PURE__ */ jsxDEV8(Box7, {
1551
+ children: /* @__PURE__ */ jsxDEV8(Text8, {
1873
1552
  dim: true,
1874
1553
  children: "\u2191\u2193 navigate \xB7 enter select \xB7 q back"
1875
1554
  }, undefined, false, undefined, this)
@@ -1878,8 +1557,174 @@ function Resume({ sessionId, onSelect }) {
1878
1557
  }, undefined, true, undefined, this);
1879
1558
  }
1880
1559
 
1560
+ // src/tui/screens/project-picker/index.tsx
1561
+ import { useMemo as useMemo3 } from "react";
1562
+ import { Box as Box8, Text as Text9, useTui as useTui4 } from "@orchetron/storm";
1563
+ import { existsSync as existsSync2 } from "fs";
1564
+ import { jsxDEV as jsxDEV9, Fragment as Fragment2 } from "react/jsx-dev-runtime";
1565
+ var SLUG_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
1566
+ function statsFor(slug) {
1567
+ const dbFile = projectPaths(slug).db;
1568
+ if (!existsSync2(dbFile))
1569
+ return { total: 0, active: 0, unreadable: false };
1570
+ try {
1571
+ const db = getDbForProject(slug);
1572
+ const all = db.select({ status: sessions.status }).from(sessions).all();
1573
+ return {
1574
+ total: all.length,
1575
+ active: all.filter((s) => s.status === "active" || s.status === "waiting").length,
1576
+ unreadable: false
1577
+ };
1578
+ } catch {
1579
+ return { total: 0, active: 0, unreadable: true };
1580
+ }
1581
+ }
1582
+ function projectRow(entry, isActive) {
1583
+ const stats = statsFor(entry.slug);
1584
+ const sessionsLabel = stats.unreadable ? "?" : `${stats.active}/${stats.total}`;
1585
+ const lastUsed = formatAgo(entry.lastUsedAt);
1586
+ return {
1587
+ value: entry.slug,
1588
+ label: `${entry.slug} ${entry.name} ${sessionsLabel}`,
1589
+ meta: lastUsed,
1590
+ display: (isCursor) => {
1591
+ const cursorColor = "green";
1592
+ const slugColor = isCursor ? cursorColor : undefined;
1593
+ const nameColor = isCursor ? cursorColor : undefined;
1594
+ return /* @__PURE__ */ jsxDEV9(Fragment2, {
1595
+ children: [
1596
+ /* @__PURE__ */ jsxDEV9(Text9, {
1597
+ color: cursorColor,
1598
+ bold: true,
1599
+ children: isCursor ? "\u276F " : " "
1600
+ }, undefined, false, undefined, this),
1601
+ /* @__PURE__ */ jsxDEV9(Text9, {
1602
+ bold: isActive,
1603
+ color: isActive ? "gold" : slugColor,
1604
+ children: isActive ? "\u25CF " : " "
1605
+ }, undefined, false, undefined, this),
1606
+ /* @__PURE__ */ jsxDEV9(Text9, {
1607
+ color: slugColor,
1608
+ bold: isCursor || isActive,
1609
+ children: entry.slug.padEnd(16)
1610
+ }, undefined, false, undefined, this),
1611
+ /* @__PURE__ */ jsxDEV9(Text9, {
1612
+ color: nameColor,
1613
+ dim: !isCursor,
1614
+ children: entry.name.padEnd(20)
1615
+ }, undefined, false, undefined, this),
1616
+ /* @__PURE__ */ jsxDEV9(Text9, {
1617
+ dim: true,
1618
+ children: stats.unreadable ? " (unreadable)" : ` ${sessionsLabel} active/total`
1619
+ }, undefined, false, undefined, this)
1620
+ ]
1621
+ }, undefined, true, undefined, this);
1622
+ }
1623
+ };
1624
+ }
1625
+ function ProjectPicker({ onSelect }) {
1626
+ const { exit } = useTui4();
1627
+ const allProjects = useMemo3(() => listProjects(), []);
1628
+ const activeSlug = useMemo3(() => getActiveProjectSlug(), []);
1629
+ const items = useMemo3(() => {
1630
+ return allProjects.slice().sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt)).map((p) => projectRow(p, p.slug === activeSlug));
1631
+ }, [allProjects, activeSlug]);
1632
+ const suggestions = useMemo3(() => allProjects.map((p) => p.slug), [allProjects]);
1633
+ const knownSlugs = useMemo3(() => new Set(allProjects.map((p) => p.slug)), [allProjects]);
1634
+ const select = (selection) => {
1635
+ onSelect(selection);
1636
+ exit();
1637
+ };
1638
+ const handleSubmit = (value) => {
1639
+ const trimmed = value.trim();
1640
+ if (!trimmed)
1641
+ return;
1642
+ if (knownSlugs.has(trimmed)) {
1643
+ select({ type: "select", slug: trimmed });
1644
+ return;
1645
+ }
1646
+ if (!SLUG_PATTERN.test(trimmed)) {
1647
+ return;
1648
+ }
1649
+ select({ type: "create", slug: trimmed });
1650
+ };
1651
+ return /* @__PURE__ */ jsxDEV9(Box8, {
1652
+ flexDirection: "column",
1653
+ paddingY: 1,
1654
+ gap: 1,
1655
+ children: [
1656
+ /* @__PURE__ */ jsxDEV9(Box8, {
1657
+ marginX: 1,
1658
+ children: /* @__PURE__ */ jsxDEV9(Logo, {}, undefined, false, undefined, this)
1659
+ }, undefined, false, undefined, this),
1660
+ /* @__PURE__ */ jsxDEV9(Box8, {
1661
+ flexDirection: "column",
1662
+ marginX: 2,
1663
+ gap: 1,
1664
+ children: [
1665
+ /* @__PURE__ */ jsxDEV9(AppDetails, {}, undefined, false, undefined, this),
1666
+ /* @__PURE__ */ jsxDEV9(Box8, {
1667
+ flexDirection: "column",
1668
+ gap: 1,
1669
+ children: [
1670
+ /* @__PURE__ */ jsxDEV9(Box8, {
1671
+ flexDirection: "row",
1672
+ gap: 1,
1673
+ children: [
1674
+ /* @__PURE__ */ jsxDEV9(Text9, {
1675
+ bold: true,
1676
+ children: "Projects"
1677
+ }, undefined, false, undefined, this),
1678
+ allProjects.length > 0 ? /* @__PURE__ */ jsxDEV9(Fragment2, {
1679
+ children: [
1680
+ /* @__PURE__ */ jsxDEV9(Text9, {
1681
+ dim: true,
1682
+ children: "\xB7"
1683
+ }, undefined, false, undefined, this),
1684
+ /* @__PURE__ */ jsxDEV9(Text9, {
1685
+ dim: true,
1686
+ children: [
1687
+ allProjects.length,
1688
+ " total",
1689
+ activeSlug ? ` \xB7 active: ${activeSlug}` : ""
1690
+ ]
1691
+ }, undefined, true, undefined, this)
1692
+ ]
1693
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV9(Text9, {
1694
+ dim: true,
1695
+ children: "\xB7 none \u2014 type a slug to create your first project"
1696
+ }, undefined, false, undefined, this)
1697
+ ]
1698
+ }, undefined, true, undefined, this),
1699
+ /* @__PURE__ */ jsxDEV9(Picker, {
1700
+ mode: "single",
1701
+ items,
1702
+ isFocused: true,
1703
+ maxVisible: 16,
1704
+ suggest: suggestions,
1705
+ placeholder: "Filter or type a new slug to create\u2026",
1706
+ emptyHint: "No projects. Type a slug to create one.",
1707
+ onSubmit: handleSubmit,
1708
+ onKey: (e) => {
1709
+ if (e.key === "c" && e.ctrl) {
1710
+ select({ type: "quit" });
1711
+ }
1712
+ }
1713
+ }, undefined, false, undefined, this),
1714
+ /* @__PURE__ */ jsxDEV9(Text9, {
1715
+ dim: true,
1716
+ children: "\u2191\u2193 navigate \xB7 enter select \xB7 type to filter or create new \xB7 ctrl+c quit"
1717
+ }, undefined, false, undefined, this)
1718
+ ]
1719
+ }, undefined, true, undefined, this)
1720
+ ]
1721
+ }, undefined, true, undefined, this)
1722
+ ]
1723
+ }, undefined, true, undefined, this);
1724
+ }
1725
+
1881
1726
  // src/tui/run-screen.tsx
1882
- import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
1727
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
1883
1728
  var [, , screen, outputPath, ...args] = process.argv;
1884
1729
  if (!screen || !outputPath) {
1885
1730
  console.error("Usage: run-screen <screen> <outputPath> [args...]");
@@ -1887,13 +1732,21 @@ if (!screen || !outputPath) {
1887
1732
  }
1888
1733
  var result;
1889
1734
  switch (screen) {
1890
- case "startup": {
1891
- const skipProjectPicker = args[0] === "true";
1892
- const initialProjectSlug = args[1] ?? "";
1735
+ case "launch": {
1736
+ let selection = { type: "quit" };
1737
+ const app = render(/* @__PURE__ */ jsxDEV10(Launch, {
1738
+ onSelect: (s) => {
1739
+ selection = s;
1740
+ }
1741
+ }, undefined, false, undefined, this), { alternateScreen: true, patchConsole: true });
1742
+ await app.waitUntilExit();
1743
+ app.unmount();
1744
+ result = selection;
1745
+ break;
1746
+ }
1747
+ case "project-picker": {
1893
1748
  let selection = { type: "quit" };
1894
- const app = render(/* @__PURE__ */ jsxDEV11(Startup, {
1895
- skipProjectPicker,
1896
- initialProjectSlug,
1749
+ const app = render(/* @__PURE__ */ jsxDEV10(ProjectPicker, {
1897
1750
  onSelect: (s) => {
1898
1751
  selection = s;
1899
1752
  }
@@ -1910,7 +1763,7 @@ switch (screen) {
1910
1763
  process.exit(1);
1911
1764
  }
1912
1765
  let action = "save";
1913
- const app = render(/* @__PURE__ */ jsxDEV11(Exit, {
1766
+ const app = render(/* @__PURE__ */ jsxDEV10(Exit, {
1914
1767
  sessionId,
1915
1768
  onAction: (a) => {
1916
1769
  action = a;
@@ -1928,7 +1781,7 @@ switch (screen) {
1928
1781
  process.exit(1);
1929
1782
  }
1930
1783
  let selection = { type: "back" };
1931
- const app = render(/* @__PURE__ */ jsxDEV11(Resume, {
1784
+ const app = render(/* @__PURE__ */ jsxDEV10(Resume, {
1932
1785
  sessionId,
1933
1786
  onSelect: (s) => {
1934
1787
  selection = s;