@on-par/factory-tui 0.0.0-stage → 0.9.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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -2
  3. package/dist/components/App.d.ts +38 -0
  4. package/dist/components/App.d.ts.map +1 -0
  5. package/dist/components/App.js +383 -0
  6. package/dist/components/App.js.map +1 -0
  7. package/dist/components/ApprovalPrompt.d.ts +9 -0
  8. package/dist/components/ApprovalPrompt.d.ts.map +1 -0
  9. package/dist/components/ApprovalPrompt.js +11 -0
  10. package/dist/components/ApprovalPrompt.js.map +1 -0
  11. package/dist/components/Dashboard.d.ts +16 -0
  12. package/dist/components/Dashboard.d.ts.map +1 -0
  13. package/dist/components/Dashboard.js +20 -0
  14. package/dist/components/Dashboard.js.map +1 -0
  15. package/dist/components/EventFeed.d.ts +7 -0
  16. package/dist/components/EventFeed.d.ts.map +1 -0
  17. package/dist/components/EventFeed.js +21 -0
  18. package/dist/components/EventFeed.js.map +1 -0
  19. package/dist/components/Header.d.ts +8 -0
  20. package/dist/components/Header.d.ts.map +1 -0
  21. package/dist/components/Header.js +13 -0
  22. package/dist/components/Header.js.map +1 -0
  23. package/dist/components/LaneList.d.ts +21 -0
  24. package/dist/components/LaneList.d.ts.map +1 -0
  25. package/dist/components/LaneList.js +48 -0
  26. package/dist/components/LaneList.js.map +1 -0
  27. package/dist/components/LaneRow.d.ts +18 -0
  28. package/dist/components/LaneRow.d.ts.map +1 -0
  29. package/dist/components/LaneRow.js +36 -0
  30. package/dist/components/LaneRow.js.map +1 -0
  31. package/dist/components/LaneView.d.ts +26 -0
  32. package/dist/components/LaneView.d.ts.map +1 -0
  33. package/dist/components/LaneView.js +38 -0
  34. package/dist/components/LaneView.js.map +1 -0
  35. package/dist/components/PhaseRow.d.ts +11 -0
  36. package/dist/components/PhaseRow.d.ts.map +1 -0
  37. package/dist/components/PhaseRow.js +28 -0
  38. package/dist/components/PhaseRow.js.map +1 -0
  39. package/dist/components/RunDetail.d.ts +14 -0
  40. package/dist/components/RunDetail.d.ts.map +1 -0
  41. package/dist/components/RunDetail.js +11 -0
  42. package/dist/components/RunDetail.js.map +1 -0
  43. package/dist/components/SteeringComposer.d.ts +8 -0
  44. package/dist/components/SteeringComposer.d.ts.map +1 -0
  45. package/dist/components/SteeringComposer.js +6 -0
  46. package/dist/components/SteeringComposer.js.map +1 -0
  47. package/dist/components/StopBanner.d.ts +6 -0
  48. package/dist/components/StopBanner.d.ts.map +1 -0
  49. package/dist/components/StopBanner.js +6 -0
  50. package/dist/components/StopBanner.js.map +1 -0
  51. package/dist/dashboard.d.ts +66 -0
  52. package/dist/dashboard.d.ts.map +1 -0
  53. package/dist/dashboard.js +157 -0
  54. package/dist/dashboard.js.map +1 -0
  55. package/dist/fallback.d.ts +8 -0
  56. package/dist/fallback.d.ts.map +1 -0
  57. package/dist/fallback.js +13 -0
  58. package/dist/fallback.js.map +1 -0
  59. package/dist/index.d.ts +45 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +24 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/lane-queue.d.ts +20 -0
  64. package/dist/lane-queue.d.ts.map +1 -0
  65. package/dist/lane-queue.js +20 -0
  66. package/dist/lane-queue.js.map +1 -0
  67. package/dist/run-tui.d.ts +20 -0
  68. package/dist/run-tui.d.ts.map +1 -0
  69. package/dist/run-tui.js +30 -0
  70. package/dist/run-tui.js.map +1 -0
  71. package/dist/state.d.ts +18 -0
  72. package/dist/state.d.ts.map +1 -0
  73. package/dist/state.js +77 -0
  74. package/dist/state.js.map +1 -0
  75. package/dist/tabs/CostsTab.d.ts +13 -0
  76. package/dist/tabs/CostsTab.d.ts.map +1 -0
  77. package/dist/tabs/CostsTab.js +24 -0
  78. package/dist/tabs/CostsTab.js.map +1 -0
  79. package/dist/tabs/HealthTab.d.ts +20 -0
  80. package/dist/tabs/HealthTab.d.ts.map +1 -0
  81. package/dist/tabs/HealthTab.js +18 -0
  82. package/dist/tabs/HealthTab.js.map +1 -0
  83. package/dist/tabs/LogTab.d.ts +10 -0
  84. package/dist/tabs/LogTab.d.ts.map +1 -0
  85. package/dist/tabs/LogTab.js +9 -0
  86. package/dist/tabs/LogTab.js.map +1 -0
  87. package/dist/tabs/QueueTab.d.ts +11 -0
  88. package/dist/tabs/QueueTab.d.ts.map +1 -0
  89. package/dist/tabs/QueueTab.js +37 -0
  90. package/dist/tabs/QueueTab.js.map +1 -0
  91. package/dist/tabs/TabBar.d.ts +7 -0
  92. package/dist/tabs/TabBar.d.ts.map +1 -0
  93. package/dist/tabs/TabBar.js +7 -0
  94. package/dist/tabs/TabBar.js.map +1 -0
  95. package/dist/tabs/health-window.d.ts +15 -0
  96. package/dist/tabs/health-window.d.ts.map +1 -0
  97. package/dist/tabs/health-window.js +50 -0
  98. package/dist/tabs/health-window.js.map +1 -0
  99. package/dist/tabs/log-scroll.d.ts +17 -0
  100. package/dist/tabs/log-scroll.d.ts.map +1 -0
  101. package/dist/tabs/log-scroll.js +32 -0
  102. package/dist/tabs/log-scroll.js.map +1 -0
  103. package/dist/tabs/run-costs.d.ts +40 -0
  104. package/dist/tabs/run-costs.d.ts.map +1 -0
  105. package/dist/tabs/run-costs.js +106 -0
  106. package/dist/tabs/run-costs.js.map +1 -0
  107. package/dist/tabs/types.d.ts +4 -0
  108. package/dist/tabs/types.d.ts.map +1 -0
  109. package/dist/tabs/types.js +9 -0
  110. package/dist/tabs/types.js.map +1 -0
  111. package/dist/text.d.ts +5 -0
  112. package/dist/text.d.ts.map +1 -0
  113. package/dist/text.js +8 -0
  114. package/dist/text.js.map +1 -0
  115. package/package.json +50 -4
@@ -0,0 +1,10 @@
1
+ import type { FactoryEvent } from '@on-par/factory-core';
2
+ import type { JSX } from 'react';
3
+ import { type LogScrollState } from './log-scroll.js';
4
+ export interface LogTabProps {
5
+ events: FactoryEvent[];
6
+ scroll: LogScrollState;
7
+ height: number;
8
+ }
9
+ export declare function LogTab({ events, scroll, height }: LogTabProps): JSX.Element;
10
+ //# sourceMappingURL=LogTab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogTab.d.ts","sourceRoot":"","sources":["../../src/tabs/LogTab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAGjC,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,iBAAiB,CAAC;AAEpE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,OAAO,CAW3E"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { EventFeed } from '../components/EventFeed.js';
4
+ import { visibleSlice } from './log-scroll.js';
5
+ export function LogTab({ events, scroll, height }) {
6
+ const { slice, first, last } = visibleSlice(events, scroll, height);
7
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(EventFeed, { events: slice }), _jsxs(Text, { dimColor: true, children: ["follow: ", scroll.follow ? 'on' : 'off', " \u00B7 \u2191/\u2193 PgUp/PgDn scroll \u00B7 f follow \u00B7 ", first, "-", last, "/", events.length] })] }));
8
+ }
9
+ //# sourceMappingURL=LogTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogTab.js","sourceRoot":"","sources":["../../src/tabs/LogTab.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAGhC,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAuB,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAQpE,MAAM,UAAU,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAe;IAC5D,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAEpE,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,KAAC,SAAS,IAAC,MAAM,EAAE,KAAK,GAAI,EAC5B,MAAC,IAAI,IAAC,QAAQ,+BACH,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,oEAAuC,KAAK,OAAG,IAAI,OAAG,MAAM,CAAC,MAAM,IACnG,IACH,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { QueueSnapshot } from '@on-par/factory-core';
2
+ import type { JSX } from 'react';
3
+ import type { LaneState } from '../dashboard.js';
4
+ export interface QueueTabProps {
5
+ snapshot: QueueSnapshot;
6
+ lanes: LaneState[];
7
+ /** Source name for the heading ("GitHub" / "local file"); omitted when the App has no queue reader. */
8
+ source?: string;
9
+ }
10
+ export declare function QueueTab({ snapshot, lanes, source }: QueueTabProps): JSX.Element;
11
+ //# sourceMappingURL=QueueTab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QueueTab.d.ts","sourceRoot":"","sources":["../../src/tabs/QueueTab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAEjC,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,iBAAiB,CAAC;AAwB7D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,uGAAuG;IACvG,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,aAAa,GAAG,GAAG,CAAC,OAAO,CA4ChF"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { sanitizeTerminalText } from '../text.js';
4
+ const TITLE_MAX_LENGTH = 40;
5
+ function truncate(s, max) {
6
+ const clean = sanitizeTerminalText(s);
7
+ return clean.length > max ? `${clean.slice(0, max - 1)}…` : clean;
8
+ }
9
+ const STATUS_COLOR = {
10
+ running: 'yellow',
11
+ ready: 'green',
12
+ 'waiting-merge': 'yellow',
13
+ merged: 'green',
14
+ failed: 'red',
15
+ parked: 'yellow',
16
+ stopped: undefined,
17
+ queued: undefined,
18
+ 'in-progress': 'yellow',
19
+ };
20
+ export function QueueTab({ snapshot, lanes, source }) {
21
+ const heading = source === undefined ? undefined : _jsxs(Text, { dimColor: true, children: ["queue: ", source] });
22
+ const error = snapshot.error === undefined ? undefined : _jsxs(Text, { color: "red", children: ["(", sanitizeTerminalText(snapshot.error), ")"] });
23
+ if (snapshot.entries.length === 0) {
24
+ return (_jsxs(Box, { flexDirection: "column", children: [heading, error ?? _jsx(Text, { dimColor: true, children: "queue is empty" })] }));
25
+ }
26
+ const laneByIssue = new Map(lanes.map((l) => [l.issue, l]));
27
+ return (_jsxs(Box, { flexDirection: "column", children: [heading, error, snapshot.entries.map((entry, i) => {
28
+ // A live lane (from the event log) knows more than a label does, so it wins; a snapshot
29
+ // status/title from GitHub fills in for issues no lane has touched yet.
30
+ const lane = laneByIssue.get(String(entry.issue));
31
+ const status = lane?.status ?? entry.status ?? 'queued';
32
+ const claimant = lane === undefined && entry.claimant !== undefined ? ` (${sanitizeTerminalText(entry.claimant)})` : '';
33
+ const title = lane?.title ?? entry.title ?? '';
34
+ return (_jsxs(Text, { children: [String(i + 1).padStart(3, ' '), ". ", sanitizeTerminalText(entry.lane), " ", _jsxs(Text, { bold: true, children: ["#", entry.issue] }), ' ', _jsxs(Text, { color: STATUS_COLOR[status], children: [status, claimant] }), ' ', truncate(title, TITLE_MAX_LENGTH)] }, `${entry.lane}-${entry.issue}`));
35
+ }), typeof snapshot.proposedCount === 'number' && snapshot.proposedCount > 0 && (_jsxs(Text, { dimColor: true, children: [snapshot.proposedCount, " proposed issue(s) awaiting: factory triage accept"] }))] }));
36
+ }
37
+ //# sourceMappingURL=QueueTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QueueTab.js","sourceRoot":"","sources":["../../src/tabs/QueueTab.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { JSX } from 'react';
2
+ import { type TabName } from './types.js';
3
+ export interface TabBarProps {
4
+ active: TabName;
5
+ }
6
+ export declare function TabBar({ active }: TabBarProps): JSX.Element;
7
+ //# sourceMappingURL=TabBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TabBar.d.ts","sourceRoot":"","sources":["../../src/tabs/TabBar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAEjC,OAAO,EAAyB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,OAAO,CAoB3D"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { TAB_LABELS, TAB_ORDER } from './types.js';
4
+ export function TabBar({ active }) {
5
+ return (_jsxs(Box, { children: [TAB_ORDER.map((tab, i) => (_jsx(Text, { children: tab === active ? (_jsxs(Text, { bold: true, inverse: true, children: ["[", i + 1, " ", TAB_LABELS[tab], "]"] })) : (_jsxs(Text, { dimColor: true, children: [' ', i + 1, " ", TAB_LABELS[tab], ' '] })) }, tab))), _jsx(Text, { dimColor: true, children: " Tab/1-5 switch \u00B7 q quit" })] }));
6
+ }
7
+ //# sourceMappingURL=TabBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TabBar.js","sourceRoot":"","sources":["../../src/tabs/TabBar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAGhC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAgB,MAAM,YAAY,CAAC;AAMjE,MAAM,UAAU,MAAM,CAAC,EAAE,MAAM,EAAe;IAC5C,OAAO,CACL,MAAC,GAAG,eACD,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CACzB,KAAC,IAAI,cACF,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,CAChB,MAAC,IAAI,IAAC,IAAI,QAAC,OAAO,wBACd,CAAC,GAAG,CAAC,OAAG,UAAU,CAAC,GAAG,CAAC,SACpB,CACR,CAAC,CAAC,CAAC,CACF,MAAC,IAAI,IAAC,QAAQ,mBACX,GAAG,EACH,CAAC,GAAG,CAAC,OAAG,UAAU,CAAC,GAAG,CAAC,EAAE,GAAG,IACxB,CACR,IAVQ,GAAG,CAWP,CACR,CAAC,EACF,KAAC,IAAI,IAAC,QAAQ,oDAAgC,IAC1C,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { CostEntry, FactoryEvent, HealthKpis } from '@on-par/factory-core';
2
+ export type HealthWindowName = 'run' | '24h';
3
+ export declare const HEALTH_WINDOW_LABELS: Record<HealthWindowName, string>;
4
+ export declare const DAY_MS: number;
5
+ export interface HealthHeadlineRow {
6
+ label: string;
7
+ value: string;
8
+ }
9
+ export declare function resolveHealthWindowStart(window: HealthWindowName, runStartedAt: string | undefined, now: number): number | null;
10
+ export declare function filterToWindow(events: FactoryEvent[], costs: CostEntry[], startMs: number | null): {
11
+ events: FactoryEvent[];
12
+ costs: CostEntry[];
13
+ };
14
+ export declare function formatHealthHeadline(kpis: HealthKpis): HealthHeadlineRow[];
15
+ //# sourceMappingURL=health-window.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-window.d.ts","sourceRoot":"","sources":["../../src/tabs/health-window.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEhF,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,KAAK,CAAC;AAE7C,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAA0C,CAAC;AAE7G,eAAO,MAAM,MAAM,QAAsB,CAAC;AAE1C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAKD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,gBAAgB,EACxB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,IAAI,CAKf;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,YAAY,EAAE,EACtB,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,GAAG,IAAI,GACrB;IAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,SAAS,EAAE,CAAA;CAAE,CAIhD;AAMD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,EAAE,CAwB1E"}
@@ -0,0 +1,50 @@
1
+ export const HEALTH_WINDOW_LABELS = { run: 'this run', '24h': 'last 24h' };
2
+ export const DAY_MS = 24 * 60 * 60 * 1000;
3
+ const NO_RUNS = 'unknown (no runs in window)';
4
+ const NO_MERGED = 'unknown (no merged runs in window)';
5
+ export function resolveHealthWindowStart(window, runStartedAt, now) {
6
+ if (window === '24h')
7
+ return now - DAY_MS;
8
+ if (runStartedAt === undefined)
9
+ return null;
10
+ const parsed = Date.parse(runStartedAt);
11
+ return Number.isNaN(parsed) ? null : parsed;
12
+ }
13
+ export function filterToWindow(events, costs, startMs) {
14
+ if (startMs === null)
15
+ return { events: [], costs: [] };
16
+ const inWindow = (ts) => Date.parse(ts) >= startMs;
17
+ return { events: events.filter((e) => inWindow(e.ts)), costs: costs.filter((c) => inWindow(c.ts)) };
18
+ }
19
+ function pct(r) {
20
+ return `${Math.round(r * 100)}%`;
21
+ }
22
+ export function formatHealthHeadline(kpis) {
23
+ const labels = ['Merge rate', 'Rework rate', 'Stuck lanes', 'Median cost/merged PR', 'Throughput'];
24
+ if (kpis.runs === 0)
25
+ return labels.map((label) => ({ label, value: NO_RUNS }));
26
+ let cost;
27
+ if (kpis.merged === 0)
28
+ cost = NO_MERGED;
29
+ else if (kpis.medianCostPerMergedPr === null)
30
+ cost = 'unknown (no cost rows for merged runs)';
31
+ else {
32
+ const coverage = kpis.costCoverage === null ? 'unknown' : pct(kpis.costCoverage);
33
+ cost = `$${kpis.medianCostPerMergedPr.toFixed(2)} (${kpis.costScoredMergedRuns}/${kpis.merged} merged costed · coverage ${coverage})`;
34
+ }
35
+ let throughput;
36
+ if (kpis.merged === 0)
37
+ throughput = NO_MERGED;
38
+ else if (kpis.prsPerHour === null)
39
+ throughput = 'unknown (window too short)';
40
+ else
41
+ throughput = `${kpis.prsPerHour.toFixed(2)} PRs/hour`;
42
+ return [
43
+ { label: labels[0], value: `${pct(kpis.mergeRate)} (${kpis.merged}/${kpis.runs})` },
44
+ { label: labels[1], value: `${pct(kpis.reworkRate)} (${kpis.reworkRuns}/${kpis.runs})` },
45
+ { label: labels[2], value: `${kpis.stuckRuns} of ${kpis.runs} runs` },
46
+ { label: labels[3], value: cost },
47
+ { label: labels[4], value: throughput },
48
+ ];
49
+ }
50
+ //# sourceMappingURL=health-window.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-window.js","sourceRoot":"","sources":["../../src/tabs/health-window.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { FactoryEvent } from '@on-par/factory-core';
2
+ export interface LogScrollState {
3
+ follow: boolean;
4
+ /** Lines scrolled up from the tail. 0 = pinned to the newest event. */
5
+ offset: number;
6
+ }
7
+ export declare function initialLogScroll(): LogScrollState;
8
+ export type LogScrollAction = 'up' | 'down' | 'pageUp' | 'pageDown' | 'toggleFollow';
9
+ export declare function reduceLogScroll(s: LogScrollState, a: LogScrollAction, pageSize: number, total: number): LogScrollState;
10
+ export interface VisibleSlice {
11
+ slice: FactoryEvent[];
12
+ first: number;
13
+ last: number;
14
+ }
15
+ /** Windowed view of `events` for the given scroll state. 1-based first/last for display; total = events.length. */
16
+ export declare function visibleSlice(events: FactoryEvent[], s: LogScrollState, height: number): VisibleSlice;
17
+ //# sourceMappingURL=log-scroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-scroll.d.ts","sourceRoot":"","sources":["../../src/tabs/log-scroll.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,IAAI,cAAc,CAEjD;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,cAAc,CAAC;AAErF,wBAAgB,eAAe,CAC7B,CAAC,EAAE,cAAc,EACjB,CAAC,EAAE,eAAe,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,cAAc,CAiBhB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,mHAAmH;AACnH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,CAUpG"}
@@ -0,0 +1,32 @@
1
+ export function initialLogScroll() {
2
+ return { follow: true, offset: 0 };
3
+ }
4
+ export function reduceLogScroll(s, a, pageSize, total) {
5
+ const maxOffset = Math.max(0, total - pageSize);
6
+ switch (a) {
7
+ case 'up':
8
+ return { follow: false, offset: Math.min(maxOffset, s.offset + 1) };
9
+ case 'pageUp':
10
+ return { follow: false, offset: Math.min(maxOffset, s.offset + pageSize) };
11
+ case 'down':
12
+ return { ...s, offset: Math.max(0, s.offset - 1) };
13
+ case 'pageDown':
14
+ return { ...s, offset: Math.max(0, s.offset - pageSize) };
15
+ case 'toggleFollow': {
16
+ const follow = !s.follow;
17
+ return { follow, offset: follow ? 0 : s.offset };
18
+ }
19
+ }
20
+ }
21
+ /** Windowed view of `events` for the given scroll state. 1-based first/last for display; total = events.length. */
22
+ export function visibleSlice(events, s, height) {
23
+ const total = events.length;
24
+ if (total === 0)
25
+ return { slice: [], first: 0, last: 0 };
26
+ const maxOffset = Math.max(0, total - height);
27
+ const offset = Math.min(s.offset, maxOffset);
28
+ const end = total - offset;
29
+ const start = Math.max(0, end - height);
30
+ return { slice: events.slice(start, end), first: start + 1, last: end };
31
+ }
32
+ //# sourceMappingURL=log-scroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-scroll.js","sourceRoot":"","sources":["../../src/tabs/log-scroll.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,gBAAgB;IAC9B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AACrC,CAAC;AAID,MAAM,UAAU,eAAe,CAC7B,CAAiB,EACjB,CAAkB,EAClB,QAAgB,EAChB,KAAa;IAEb,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,CAAC;IAEhD,QAAQ,CAAC,EAAE,CAAC;QACV,KAAK,IAAI;YACP,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;QACtE,KAAK,QAAQ;YACX,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;QAC7E,KAAK,MAAM;YACT,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;QACrD,KAAK,UAAU;YACb,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;QAC5D,KAAK,cAAc,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;YACzB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACnD,CAAC;IACH,CAAC;AACH,CAAC;AAQD,mHAAmH;AACnH,MAAM,UAAU,YAAY,CAAC,MAAsB,EAAE,CAAiB,EAAE,MAAc;IACpF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;IAC5B,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAEzD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC,CAAC;IAExC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { type CostEntry, type IssueCostRow } from '@on-par/factory-core';
2
+ import type { LaneState } from '../dashboard.js';
3
+ export interface RunIssueCost extends IssueCostRow {
4
+ lane: string;
5
+ lastTs: string;
6
+ merged: boolean;
7
+ }
8
+ export interface LaneCostTotal {
9
+ lane: string;
10
+ cost: number | null;
11
+ unpricedCount: number;
12
+ }
13
+ export interface RunCostsSummary {
14
+ /** Earliest LaneState.startedAt; undefined when no run is in progress. */
15
+ runStartedAt?: string;
16
+ /** This run's issues, most recent cost row first. */
17
+ issues: RunIssueCost[];
18
+ total: {
19
+ inputTokens: number;
20
+ outputTokens: number;
21
+ cost: number | null;
22
+ unpricedCount: number;
23
+ };
24
+ byLane: LaneCostTotal[];
25
+ /** Median cost of merged issues with a known cost; null when there are none. */
26
+ medianMergedCost: number | null;
27
+ mergedCount: number;
28
+ allTime: {
29
+ cost: number | null;
30
+ unpricedCount: number;
31
+ };
32
+ }
33
+ /** Scopes cost rows to the current run (ts >= the earliest lane start) and rolls them up per issue and lane (#1739). */
34
+ export declare function summarizeRunCosts(entries: CostEntry[], lanes: LaneState[]): RunCostsSummary;
35
+ /** The issue the Costs tab highlights: the kept selection, else the running issue, else the most recent (#1739). */
36
+ export declare function resolveCostsSelection(summary: RunCostsSummary, lanes: LaneState[], selectedIssue?: string): string | undefined;
37
+ export declare function formatTokensShort(n: number): string;
38
+ /** Joins segments with " · ", dropping trailing ones for a "+N more" tail so the line fits `width`. */
39
+ export declare function truncateSegments(prefix: string, segments: string[], width: number): string;
40
+ //# sourceMappingURL=run-costs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-costs.d.ts","sourceRoot":"","sources":["../../src/tabs/run-costs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,YAAa,SAAQ,YAAY;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IACjG,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,gFAAgF;IAChF,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CACzD;AASD,wHAAwH;AACxH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,eAAe,CAsE3F;AAED,oHAAoH;AACpH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,SAAS,EAAE,EAClB,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,uGAAuG;AACvG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ1F"}
@@ -0,0 +1,106 @@
1
+ import { aggregateCosts } from '@on-par/factory-core';
2
+ function median(values) {
3
+ if (values.length === 0)
4
+ return null;
5
+ const sorted = [...values].sort((a, b) => a - b);
6
+ const mid = Math.floor(sorted.length / 2);
7
+ return sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
8
+ }
9
+ /** Scopes cost rows to the current run (ts >= the earliest lane start) and rolls them up per issue and lane (#1739). */
10
+ export function summarizeRunCosts(entries, lanes) {
11
+ const all = aggregateCosts(entries).total;
12
+ const allTime = { cost: all.cost, unpricedCount: all.unpricedCount };
13
+ let runStartedAt;
14
+ let startMs = Infinity;
15
+ for (const lane of lanes) {
16
+ const ms = Date.parse(lane.startedAt);
17
+ if (!Number.isNaN(ms) && ms < startMs) {
18
+ startMs = ms;
19
+ runStartedAt = lane.startedAt;
20
+ }
21
+ }
22
+ if (runStartedAt === undefined) {
23
+ return {
24
+ issues: [],
25
+ total: { inputTokens: 0, outputTokens: 0, cost: 0, unpricedCount: 0 },
26
+ byLane: [],
27
+ medianMergedCost: null,
28
+ mergedCount: 0,
29
+ allTime,
30
+ };
31
+ }
32
+ const runEntries = entries.filter((e) => Date.parse(e.ts) >= startMs);
33
+ const lastTsByIssue = new Map();
34
+ for (const e of runEntries) {
35
+ const prev = lastTsByIssue.get(e.issue);
36
+ if (prev === undefined || Date.parse(e.ts) > Date.parse(prev))
37
+ lastTsByIssue.set(e.issue, e.ts);
38
+ }
39
+ const agg = aggregateCosts(runEntries);
40
+ const issues = agg.perIssue.map((row) => {
41
+ const laneState = lanes.find((l) => l.issue === row.issue);
42
+ return {
43
+ ...row,
44
+ lane: laneState?.lane ?? 'default',
45
+ merged: laneState?.status === 'merged',
46
+ lastTs: lastTsByIssue.get(row.issue) ?? runStartedAt,
47
+ };
48
+ });
49
+ issues.sort((a, b) => Date.parse(b.lastTs) - Date.parse(a.lastTs));
50
+ const laneAcc = new Map();
51
+ for (const issue of issues) {
52
+ const acc = laneAcc.get(issue.lane) ?? { sum: 0, priced: 0, unpricedCount: 0 };
53
+ if (issue.cost !== null) {
54
+ acc.sum += issue.cost;
55
+ acc.priced += 1;
56
+ }
57
+ acc.unpricedCount += issue.unpricedCount;
58
+ laneAcc.set(issue.lane, acc);
59
+ }
60
+ const byLane = [...laneAcc].map(([lane, acc]) => ({
61
+ lane,
62
+ cost: acc.priced === 0 && acc.unpricedCount > 0 ? null : acc.sum,
63
+ unpricedCount: acc.unpricedCount,
64
+ }));
65
+ const mergedCosts = issues.flatMap((i) => (i.merged && i.cost !== null ? [i.cost] : []));
66
+ return {
67
+ runStartedAt,
68
+ issues,
69
+ total: agg.total,
70
+ byLane,
71
+ medianMergedCost: median(mergedCosts),
72
+ mergedCount: mergedCosts.length,
73
+ allTime,
74
+ };
75
+ }
76
+ /** The issue the Costs tab highlights: the kept selection, else the running issue, else the most recent (#1739). */
77
+ export function resolveCostsSelection(summary, lanes, selectedIssue) {
78
+ const inRun = (issue) => summary.issues.some((i) => i.issue === issue);
79
+ if (selectedIssue !== undefined && inRun(selectedIssue))
80
+ return selectedIssue;
81
+ const running = lanes.find((l) => l.status === 'running' && inRun(l.issue));
82
+ if (running)
83
+ return running.issue;
84
+ return summary.issues[0]?.issue;
85
+ }
86
+ export function formatTokensShort(n) {
87
+ if (n < 1000)
88
+ return String(Math.round(n));
89
+ const strip = (s) => s.replace(/\.0$/, '');
90
+ if (n < 1e6)
91
+ return `${strip((n / 1e3).toFixed(1))}k`;
92
+ return `${strip((n / 1e6).toFixed(1))}M`;
93
+ }
94
+ /** Joins segments with " · ", dropping trailing ones for a "+N more" tail so the line fits `width`. */
95
+ export function truncateSegments(prefix, segments, width) {
96
+ const full = prefix + segments.join(' · ');
97
+ if (full.length <= width)
98
+ return full;
99
+ for (let keep = segments.length - 1; keep >= 1; keep--) {
100
+ const line = `${prefix}${segments.slice(0, keep).join(' · ')} · +${segments.length - keep} more`;
101
+ if (line.length <= width)
102
+ return line;
103
+ }
104
+ return `${prefix}+${segments.length} more`;
105
+ }
106
+ //# sourceMappingURL=run-costs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-costs.js","sourceRoot":"","sources":["../../src/tabs/run-costs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export type TabName = 'dashboard' | 'queue' | 'costs' | 'log' | 'health';
2
+ export declare const TAB_ORDER: TabName[];
3
+ export declare const TAB_LABELS: Record<TabName, string>;
4
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tabs/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEzE,eAAO,MAAM,SAAS,EAAE,OAAO,EAAqD,CAAC;AAErF,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAM9C,CAAC"}
@@ -0,0 +1,9 @@
1
+ export const TAB_ORDER = ['dashboard', 'queue', 'costs', 'log', 'health'];
2
+ export const TAB_LABELS = {
3
+ dashboard: 'Active',
4
+ queue: 'Queue',
5
+ costs: 'Costs',
6
+ log: 'Log',
7
+ health: 'Health',
8
+ };
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tabs/types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,SAAS,GAAc,CAAC,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAErF,MAAM,CAAC,MAAM,UAAU,GAA4B;IACjD,SAAS,EAAE,QAAQ;IACnB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,QAAQ;CACjB,CAAC"}
package/dist/text.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /** Strip terminal control characters (C0, DEL, C1 — which includes the bare CSI byte) from text that
2
+ * came from GitHub or the event log, so a crafted issue title or label cannot move the cursor, clear
3
+ * the pane, or spoof a row. Ink writes strings verbatim, so this is the only guard (#1362). */
4
+ export declare function sanitizeTerminalText(text: string): string;
5
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;gGAEgG;AAChG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGzD"}
package/dist/text.js ADDED
@@ -0,0 +1,8 @@
1
+ /** Strip terminal control characters (C0, DEL, C1 — which includes the bare CSI byte) from text that
2
+ * came from GitHub or the event log, so a crafted issue title or label cannot move the cursor, clear
3
+ * the pane, or spoof a row. Ink writes strings verbatim, so this is the only guard (#1362). */
4
+ export function sanitizeTerminalText(text) {
5
+ // oxlint-disable-next-line no-control-regex -- stripping terminal control characters on purpose
6
+ return text.replace(/[\x00-\x1f\x7f-\x9f]/g, '');
7
+ }
8
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;gGAEgG;AAChG,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,gGAAgG;IAChG,OAAO,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@on-par/factory-tui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.9.0",
4
+ "description": "Read-only Ink TUI for a live view of the current Software Factory run.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "development": "./src/index.ts",
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "!dist/.tsbuildinfo"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/on-par/software-factory.git",
25
+ "directory": "packages/tui"
26
+ },
27
+ "homepage": "https://github.com/on-par/software-factory#readme",
28
+ "bugs": {
29
+ "url": "https://github.com/on-par/software-factory/issues"
30
+ },
31
+ "engines": {
32
+ "node": ">=24.0.0"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.build.json",
36
+ "typecheck": "tsc --noEmit -p tsconfig.json",
37
+ "test": "vitest run",
38
+ "prepublishOnly": "npm run build",
39
+ "prepack": "node ../../scripts/pack-hygiene-cli.ts prepack"
40
+ },
41
+ "dependencies": {
42
+ "@on-par/factory-core": "0.9.0",
43
+ "ink": "^6.0.0",
44
+ "react": "^19.3.0"
45
+ },
46
+ "license": "MIT",
47
+ "devDependencies": {
48
+ "@types/node": "^26.6.4",
49
+ "@types/react": "^19.3.0",
50
+ "ink-testing-library": "^4.0.0"
51
+ }
52
+ }