praana 0.5.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 (204) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/bin/praana.js +17 -0
  4. package/bin/pran.js +17 -0
  5. package/dist/app-banner.d.ts +11 -0
  6. package/dist/app-banner.js +161 -0
  7. package/dist/app-controller.d.ts +44 -0
  8. package/dist/app-controller.js +143 -0
  9. package/dist/app-identity.d.ts +18 -0
  10. package/dist/app-identity.js +52 -0
  11. package/dist/auto-compact.d.ts +16 -0
  12. package/dist/auto-compact.js +101 -0
  13. package/dist/cli-args.d.ts +14 -0
  14. package/dist/cli-args.js +69 -0
  15. package/dist/compile-classic.d.ts +21 -0
  16. package/dist/compile-classic.js +106 -0
  17. package/dist/compiler.d.ts +75 -0
  18. package/dist/compiler.js +406 -0
  19. package/dist/config.d.ts +3 -0
  20. package/dist/config.js +433 -0
  21. package/dist/context-engine/activity-log.d.ts +9 -0
  22. package/dist/context-engine/activity-log.js +109 -0
  23. package/dist/context-engine/artifact-store.d.ts +32 -0
  24. package/dist/context-engine/artifact-store.js +272 -0
  25. package/dist/context-engine/bm25.d.ts +3 -0
  26. package/dist/context-engine/bm25.js +32 -0
  27. package/dist/context-engine/checkpoint.d.ts +34 -0
  28. package/dist/context-engine/checkpoint.js +430 -0
  29. package/dist/context-engine/classify.d.ts +3 -0
  30. package/dist/context-engine/classify.js +60 -0
  31. package/dist/context-engine/db.d.ts +73 -0
  32. package/dist/context-engine/db.js +505 -0
  33. package/dist/context-engine/distiller.d.ts +30 -0
  34. package/dist/context-engine/distiller.js +67 -0
  35. package/dist/context-engine/engine-compiler.d.ts +23 -0
  36. package/dist/context-engine/engine-compiler.js +297 -0
  37. package/dist/context-engine/error-tracker.d.ts +21 -0
  38. package/dist/context-engine/error-tracker.js +74 -0
  39. package/dist/context-engine/event-lineage.d.ts +26 -0
  40. package/dist/context-engine/event-lineage.js +120 -0
  41. package/dist/context-engine/extraction.d.ts +26 -0
  42. package/dist/context-engine/extraction.js +83 -0
  43. package/dist/context-engine/index.d.ts +82 -0
  44. package/dist/context-engine/index.js +238 -0
  45. package/dist/context-engine/scoring.d.ts +13 -0
  46. package/dist/context-engine/scoring.js +47 -0
  47. package/dist/context-engine/state-snapshot.d.ts +8 -0
  48. package/dist/context-engine/state-snapshot.js +50 -0
  49. package/dist/context-engine/summarize.d.ts +6 -0
  50. package/dist/context-engine/summarize.js +32 -0
  51. package/dist/context-engine/telemetry.d.ts +25 -0
  52. package/dist/context-engine/telemetry.js +64 -0
  53. package/dist/context-engine/turn-digest.d.ts +50 -0
  54. package/dist/context-engine/turn-digest.js +250 -0
  55. package/dist/context-engine/turn-ledger.d.ts +18 -0
  56. package/dist/context-engine/turn-ledger.js +184 -0
  57. package/dist/context-engine/turn-recorder.d.ts +24 -0
  58. package/dist/context-engine/turn-recorder.js +88 -0
  59. package/dist/context-engine/types.d.ts +201 -0
  60. package/dist/context-engine/types.js +4 -0
  61. package/dist/context-pressure.d.ts +19 -0
  62. package/dist/context-pressure.js +36 -0
  63. package/dist/distillers/generic.d.ts +14 -0
  64. package/dist/distillers/generic.js +93 -0
  65. package/dist/distillers/git-diff.d.ts +8 -0
  66. package/dist/distillers/git-diff.js +119 -0
  67. package/dist/distillers/index.d.ts +2 -0
  68. package/dist/distillers/index.js +16 -0
  69. package/dist/distillers/npm-test.d.ts +8 -0
  70. package/dist/distillers/npm-test.js +50 -0
  71. package/dist/distillers/rg-results.d.ts +8 -0
  72. package/dist/distillers/rg-results.js +28 -0
  73. package/dist/distillers/tsc-errors.d.ts +8 -0
  74. package/dist/distillers/tsc-errors.js +52 -0
  75. package/dist/event-log.d.ts +56 -0
  76. package/dist/event-log.js +214 -0
  77. package/dist/llm.d.ts +29 -0
  78. package/dist/llm.js +155 -0
  79. package/dist/logger.d.ts +94 -0
  80. package/dist/logger.js +287 -0
  81. package/dist/main.d.ts +1 -0
  82. package/dist/main.js +54 -0
  83. package/dist/memory/confidence.d.ts +7 -0
  84. package/dist/memory/confidence.js +37 -0
  85. package/dist/memory/consolidation.d.ts +26 -0
  86. package/dist/memory/consolidation.js +166 -0
  87. package/dist/memory/db.d.ts +40 -0
  88. package/dist/memory/db.js +283 -0
  89. package/dist/memory/dedup.d.ts +6 -0
  90. package/dist/memory/dedup.js +50 -0
  91. package/dist/memory/embedder-factory.d.ts +3 -0
  92. package/dist/memory/embedder-factory.js +81 -0
  93. package/dist/memory/embeddings.d.ts +15 -0
  94. package/dist/memory/embeddings.js +67 -0
  95. package/dist/memory/index.d.ts +9 -0
  96. package/dist/memory/index.js +11 -0
  97. package/dist/memory/ollama-summarizer.d.ts +19 -0
  98. package/dist/memory/ollama-summarizer.js +72 -0
  99. package/dist/memory/openai-summarizer.d.ts +21 -0
  100. package/dist/memory/openai-summarizer.js +51 -0
  101. package/dist/memory/store.d.ts +61 -0
  102. package/dist/memory/store.js +502 -0
  103. package/dist/memory/summarizer-factory.d.ts +3 -0
  104. package/dist/memory/summarizer-factory.js +69 -0
  105. package/dist/memory/summarizer.d.ts +4 -0
  106. package/dist/memory/summarizer.js +112 -0
  107. package/dist/memory/types.d.ts +87 -0
  108. package/dist/memory/types.js +17 -0
  109. package/dist/model-context.d.ts +15 -0
  110. package/dist/model-context.js +212 -0
  111. package/dist/project-detector.d.ts +37 -0
  112. package/dist/project-detector.js +604 -0
  113. package/dist/render.d.ts +15 -0
  114. package/dist/render.js +46 -0
  115. package/dist/session.d.ts +118 -0
  116. package/dist/session.js +809 -0
  117. package/dist/skills/index.d.ts +69 -0
  118. package/dist/skills/index.js +885 -0
  119. package/dist/skills/types.d.ts +93 -0
  120. package/dist/skills/types.js +8 -0
  121. package/dist/slash-commands.d.ts +14 -0
  122. package/dist/slash-commands.js +301 -0
  123. package/dist/state-graph.d.ts +38 -0
  124. package/dist/state-graph.js +255 -0
  125. package/dist/status-bar.d.ts +54 -0
  126. package/dist/status-bar.js +184 -0
  127. package/dist/thinking-display.d.ts +21 -0
  128. package/dist/thinking-display.js +37 -0
  129. package/dist/tool-summary.d.ts +4 -0
  130. package/dist/tool-summary.js +67 -0
  131. package/dist/tools/index.d.ts +925 -0
  132. package/dist/tools/index.js +86 -0
  133. package/dist/tools/knowledge.d.ts +140 -0
  134. package/dist/tools/knowledge.js +260 -0
  135. package/dist/tools/memory.d.ts +39 -0
  136. package/dist/tools/memory.js +300 -0
  137. package/dist/tools/search-code.d.ts +134 -0
  138. package/dist/tools/search-code.js +390 -0
  139. package/dist/tools/system.d.ts +16 -0
  140. package/dist/tools/system.js +499 -0
  141. package/dist/tools/tool-def.d.ts +6 -0
  142. package/dist/tools/tool-def.js +3 -0
  143. package/dist/turn-control.d.ts +51 -0
  144. package/dist/turn-control.js +210 -0
  145. package/dist/turn.d.ts +20 -0
  146. package/dist/turn.js +624 -0
  147. package/dist/types.d.ts +233 -0
  148. package/dist/types.js +4 -0
  149. package/dist/ui/readline-ui.d.ts +2 -0
  150. package/dist/ui/readline-ui.js +176 -0
  151. package/dist/ui/tui/app.d.ts +13 -0
  152. package/dist/ui/tui/app.js +270 -0
  153. package/dist/ui/tui/busy-indicator.d.ts +2 -0
  154. package/dist/ui/tui/busy-indicator.js +13 -0
  155. package/dist/ui/tui/components/gutter-rule.d.ts +5 -0
  156. package/dist/ui/tui/components/gutter-rule.js +9 -0
  157. package/dist/ui/tui/components/inline-tool-row.d.ts +10 -0
  158. package/dist/ui/tui/components/inline-tool-row.js +8 -0
  159. package/dist/ui/tui/components/prompt-input.d.ts +20 -0
  160. package/dist/ui/tui/components/prompt-input.js +120 -0
  161. package/dist/ui/tui/components/system-line.d.ts +5 -0
  162. package/dist/ui/tui/components/system-line.js +6 -0
  163. package/dist/ui/tui/components/thinking-block.d.ts +11 -0
  164. package/dist/ui/tui/components/thinking-block.js +31 -0
  165. package/dist/ui/tui/components/toast-line.d.ts +4 -0
  166. package/dist/ui/tui/components/toast-line.js +8 -0
  167. package/dist/ui/tui/components/tool-result-line.d.ts +5 -0
  168. package/dist/ui/tui/components/tool-result-line.js +6 -0
  169. package/dist/ui/tui/components/turn-footer.d.ts +5 -0
  170. package/dist/ui/tui/components/turn-footer.js +7 -0
  171. package/dist/ui/tui/components/user-block.d.ts +6 -0
  172. package/dist/ui/tui/components/user-block.js +6 -0
  173. package/dist/ui/tui/logo-banner.d.ts +5 -0
  174. package/dist/ui/tui/logo-banner.js +8 -0
  175. package/dist/ui/tui/markdown-render.d.ts +16 -0
  176. package/dist/ui/tui/markdown-render.js +218 -0
  177. package/dist/ui/tui/palette.d.ts +12 -0
  178. package/dist/ui/tui/palette.js +13 -0
  179. package/dist/ui/tui/reasoning-summary.d.ts +12 -0
  180. package/dist/ui/tui/reasoning-summary.js +27 -0
  181. package/dist/ui/tui/reducer.d.ts +92 -0
  182. package/dist/ui/tui/reducer.js +260 -0
  183. package/dist/ui/tui/run.d.ts +3 -0
  184. package/dist/ui/tui/run.js +40 -0
  185. package/dist/ui/tui/sink.d.ts +4 -0
  186. package/dist/ui/tui/sink.js +89 -0
  187. package/dist/ui/tui/status-bar-view.d.ts +5 -0
  188. package/dist/ui/tui/status-bar-view.js +44 -0
  189. package/dist/ui/tui/terminal-height.d.ts +12 -0
  190. package/dist/ui/tui/terminal-height.js +20 -0
  191. package/dist/ui/tui/terminal-width.d.ts +2 -0
  192. package/dist/ui/tui/terminal-width.js +5 -0
  193. package/dist/ui/tui/tool-display.d.ts +23 -0
  194. package/dist/ui/tui/tool-display.js +217 -0
  195. package/dist/ui/tui/transcript-line.d.ts +12 -0
  196. package/dist/ui/tui/transcript-line.js +43 -0
  197. package/dist/ui/tui/transcript-replay.d.ts +12 -0
  198. package/dist/ui/tui/transcript-replay.js +117 -0
  199. package/dist/ui-events.d.ts +39 -0
  200. package/dist/ui-events.js +33 -0
  201. package/dist/ui.d.ts +77 -0
  202. package/dist/ui.js +179 -0
  203. package/package.json +73 -0
  204. package/praana.config.example.toml +231 -0
@@ -0,0 +1,270 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useReducer, useState, useMemo, useCallback, useEffect, useRef } from "react";
3
+ import { Box, Text, useApp, useInput } from "ink";
4
+ import { PromptInput } from "./components/prompt-input.js";
5
+ import { TurnAbortedError } from "../../turn-control.js";
6
+ import { createInitialTranscriptState, transcriptReducer, } from "./reducer.js";
7
+ import { createTuiTurnSink } from "./sink.js";
8
+ import { PALETTE } from "./palette.js";
9
+ import { TranscriptLine } from "./transcript-line.js";
10
+ import { BusyIndicator } from "./busy-indicator.js";
11
+ import { LogoBanner } from "./logo-banner.js";
12
+ import { StatusBarView } from "./status-bar-view.js";
13
+ import { ToastLine } from "./components/toast-line.js";
14
+ export function TuiApp({ controller, initialStatus, recentLines, transcriptBootstrap = [], bootSummary, markdownRendering = true, syntaxHighlighting = true, syntaxTheme = "solarized-dark", }) {
15
+ const { exit } = useApp();
16
+ const [input, setInput] = useState("");
17
+ const [status, setStatus] = useState(initialStatus);
18
+ const [toast, setToast] = useState("");
19
+ const [showBusy, setShowBusy] = useState(false);
20
+ const [scrollOffset, setScrollOffset] = useState(0);
21
+ const atBottomRef = useRef(true);
22
+ const SCROLL_WINDOW = 30;
23
+ const toastTimerRef = useRef(null);
24
+ const inputHistoryRef = useRef([]);
25
+ const historyIndexRef = useRef(-1);
26
+ const showToast = useCallback((message) => {
27
+ if (toastTimerRef.current)
28
+ clearTimeout(toastTimerRef.current);
29
+ setToast(message);
30
+ toastTimerRef.current = setTimeout(() => {
31
+ setToast("");
32
+ toastTimerRef.current = null;
33
+ }, 4000);
34
+ }, []);
35
+ const bootLenRef = useRef(0);
36
+ const [transcript, dispatch] = useReducer(transcriptReducer, undefined, () => {
37
+ const bootstrap = [];
38
+ let nextId = 1;
39
+ const add = (role, text) => {
40
+ bootstrap.push({ id: `boot-${nextId++}`, role, text, group: 0 });
41
+ };
42
+ if (recentLines.length > 0) {
43
+ for (const line of recentLines)
44
+ add("system", line);
45
+ }
46
+ if (transcriptBootstrap.length > 0) {
47
+ return {
48
+ ...createInitialTranscriptState(),
49
+ completed: transcriptBootstrap,
50
+ nextId: transcriptBootstrap.length + 1,
51
+ groupCounter: transcriptBootstrap.reduce((max, e) => Math.max(max, e.group), 0),
52
+ };
53
+ }
54
+ bootLenRef.current = bootstrap.length;
55
+ return {
56
+ ...createInitialTranscriptState(),
57
+ completed: bootstrap,
58
+ nextId,
59
+ };
60
+ });
61
+ const sink = useMemo(() => createTuiTurnSink(dispatch), []);
62
+ const refreshStatus = useCallback(() => {
63
+ setStatus(controller.getStatusBarInput());
64
+ }, [controller]);
65
+ const showLogo = transcriptBootstrap.length === 0 && transcript.completed.length <= bootLenRef.current;
66
+ /* ── Scrolling window (entry-based — preserves terminal scrollback) ── */
67
+ const totalCompleted = transcript.completed.length;
68
+ const showScrolling = totalCompleted > SCROLL_WINDOW;
69
+ const visibleSlice = useMemo(() => {
70
+ if (!showScrolling) {
71
+ return { entries: transcript.completed, startIndex: 0 };
72
+ }
73
+ const end = Math.max(0, totalCompleted - scrollOffset);
74
+ const start = Math.max(0, end - SCROLL_WINDOW);
75
+ return { entries: transcript.completed.slice(start, end), startIndex: start };
76
+ }, [transcript.completed, scrollOffset, showScrolling, totalCompleted]);
77
+ const visibleEntries = visibleSlice.entries;
78
+ const liveAssistantEmpty = transcript.live?.role === "assistant" && !transcript.live.text.trim();
79
+ useEffect(() => {
80
+ if (totalCompleted > 0 && atBottomRef.current) {
81
+ setScrollOffset(0);
82
+ }
83
+ }, [totalCompleted]);
84
+ useEffect(() => {
85
+ if (!transcript.busy) {
86
+ setShowBusy(false);
87
+ return;
88
+ }
89
+ const waitingForOutput = !transcript.live ||
90
+ (transcript.live.role === "assistant" && !transcript.live.text.trim());
91
+ if (!waitingForOutput) {
92
+ setShowBusy(false);
93
+ return;
94
+ }
95
+ const t = setTimeout(() => setShowBusy(true), 450);
96
+ return () => {
97
+ clearTimeout(t);
98
+ setShowBusy(false);
99
+ };
100
+ }, [transcript.busy, transcript.live]);
101
+ const scrollUp = useCallback((amount) => {
102
+ setScrollOffset((prev) => {
103
+ const maxOffset = Math.max(0, totalCompleted - SCROLL_WINDOW);
104
+ const next = Math.min(prev + amount, maxOffset);
105
+ atBottomRef.current = next === 0;
106
+ return next;
107
+ });
108
+ }, [totalCompleted]);
109
+ const scrollDown = useCallback((amount) => {
110
+ setScrollOffset((prev) => {
111
+ const next = Math.max(0, prev - amount);
112
+ atBottomRef.current = next === 0;
113
+ return next;
114
+ });
115
+ }, []);
116
+ const scrollToBottom = useCallback(() => {
117
+ setScrollOffset(0);
118
+ atBottomRef.current = true;
119
+ }, []);
120
+ const scrollToTop = useCallback(() => {
121
+ const maxOffset = Math.max(0, totalCompleted - SCROLL_WINDOW);
122
+ setScrollOffset(maxOffset);
123
+ atBottomRef.current = false;
124
+ }, [totalCompleted]);
125
+ /* Busy ref avoids recreating handleSubmit on every transcript change */
126
+ const busyRef = useRef(transcript.busy);
127
+ busyRef.current = transcript.busy;
128
+ const handleSubmit = useCallback(async (value) => {
129
+ const trimmed = value.trim();
130
+ setInput("");
131
+ if (!trimmed || busyRef.current)
132
+ return;
133
+ if (trimmed.startsWith("/")) {
134
+ const result = await controller.executeSlashCommand(trimmed);
135
+ if (result.lines.length > 0) {
136
+ if (result.display === "toast") {
137
+ showToast(result.lines.join(" "));
138
+ }
139
+ else {
140
+ dispatch({ type: "system_lines", lines: result.lines });
141
+ }
142
+ }
143
+ if (result.action === "refresh_status")
144
+ refreshStatus();
145
+ if (result.action === "exit")
146
+ exit();
147
+ return;
148
+ }
149
+ inputHistoryRef.current = [
150
+ trimmed,
151
+ ...inputHistoryRef.current.filter((h) => h !== trimmed),
152
+ ].slice(0, 50);
153
+ historyIndexRef.current = -1;
154
+ dispatch({ type: "user_message", text: trimmed });
155
+ dispatch({ type: "set_busy", busy: true });
156
+ const turnStartedAt = Date.now();
157
+ try {
158
+ await controller.runUserTurn(trimmed, sink);
159
+ sink.flushText?.();
160
+ dispatch({ type: "assistant_complete" });
161
+ const bar = controller.getStatusBarInput();
162
+ dispatch({
163
+ type: "turn_footer",
164
+ model: bar.model,
165
+ durationMs: Date.now() - turnStartedAt,
166
+ stats: sink.consumeTurnStats?.() ?? undefined,
167
+ });
168
+ }
169
+ catch (err) {
170
+ if (err instanceof TurnAbortedError) {
171
+ showToast("Turn interrupted");
172
+ const bar = controller.getStatusBarInput();
173
+ dispatch({
174
+ type: "turn_footer",
175
+ model: bar.model,
176
+ durationMs: Date.now() - turnStartedAt,
177
+ stats: sink.consumeTurnStats?.() ?? undefined,
178
+ });
179
+ }
180
+ else {
181
+ const message = err.message;
182
+ controller.session.getLogger().error("Turn failed", {
183
+ code: "TURN_FAILED",
184
+ cause: err,
185
+ });
186
+ dispatch({ type: "error", message });
187
+ }
188
+ }
189
+ finally {
190
+ dispatch({ type: "set_busy", busy: false });
191
+ refreshStatus();
192
+ }
193
+ }, [controller, sink, refreshStatus, exit, showToast]);
194
+ const handleNavigationKey = useCallback((key, input) => {
195
+ if (transcript.busy && key.escape) {
196
+ controller.abortTurn();
197
+ return true;
198
+ }
199
+ return false;
200
+ }, [transcript.busy, controller, showToast]);
201
+ useInput((input, key) => {
202
+ if (transcript.busy && key.escape)
203
+ return;
204
+ if (!showScrolling)
205
+ return;
206
+ if (key.pageUp) {
207
+ scrollUp(Math.floor(SCROLL_WINDOW * 0.4));
208
+ }
209
+ else if (key.pageDown) {
210
+ scrollDown(Math.floor(SCROLL_WINDOW * 0.4));
211
+ }
212
+ else if (input === "" && key.upArrow) {
213
+ scrollUp(1);
214
+ }
215
+ else if (input === "" && key.downArrow) {
216
+ scrollDown(1);
217
+ }
218
+ else if (key.home) {
219
+ scrollToTop();
220
+ }
221
+ else if (key.end) {
222
+ scrollToBottom();
223
+ }
224
+ });
225
+ useEffect(() => {
226
+ const onSigint = () => {
227
+ controller.handleUserInterrupt(() => {
228
+ showToast("Use /exit to save and quit.");
229
+ });
230
+ };
231
+ process.on("SIGINT", onSigint);
232
+ return () => {
233
+ process.removeListener("SIGINT", onSigint);
234
+ };
235
+ }, [controller, showToast]);
236
+ useEffect(() => {
237
+ return () => {
238
+ if (toastTimerRef.current)
239
+ clearTimeout(toastTimerRef.current);
240
+ };
241
+ }, []);
242
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", padding: 1, gap: 1, children: [showLogo && _jsx(LogoBanner, { bootSummary: bootSummary }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [showScrolling && scrollOffset > 0 && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: PALETTE.muted, dimColor: true, children: "\u2191 showing older messages (PgUp/PgDn to scroll, End for latest)" }) })), visibleEntries.map((entry, index) => (_jsx(TranscriptLine, { entry: entry, prevRole: index > 0
243
+ ? visibleEntries[index - 1]?.role
244
+ : visibleSlice.startIndex > 0
245
+ ? transcript.completed[visibleSlice.startIndex - 1]?.role
246
+ : undefined, showThinking: status.thinking, markdownRendering: markdownRendering, syntaxHighlighting: syntaxHighlighting, syntaxTheme: syntaxTheme }, entry.id))), transcript.live ? (_jsxs(_Fragment, { children: [_jsx(TranscriptLine, { entry: transcript.live, prevRole: visibleEntries.length > 0
247
+ ? visibleEntries[visibleEntries.length - 1]?.role
248
+ : undefined, live: true, showThinking: status.thinking, markdownRendering: markdownRendering, syntaxHighlighting: syntaxHighlighting, syntaxTheme: syntaxTheme }), transcript.busy && liveAssistantEmpty && showBusy ? (_jsx(BusyIndicator, {})) : null] })) : transcript.busy && showBusy ? (_jsx(BusyIndicator, {})) : null] }), toast ? _jsx(ToastLine, { message: toast }) : null, _jsxs(Box, { padding: 1, gap: 1, children: [_jsx(Text, { color: PALETTE.assistant, children: "\u276F " }), _jsx(PromptInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: transcript.busy ? "running…" : "message or /command", onEmptyShortcut: (key) => {
249
+ if (key !== "t" || transcript.busy)
250
+ return false;
251
+ controller.showThinking = !controller.showThinking;
252
+ refreshStatus();
253
+ showToast(controller.showThinking ? "Thinking enabled." : "Thinking disabled.");
254
+ return true;
255
+ }, onNavigationKey: handleNavigationKey, onHistoryPrev: () => {
256
+ const history = inputHistoryRef.current;
257
+ if (history.length === 0)
258
+ return null;
259
+ const nextIndex = Math.min(historyIndexRef.current + 1, history.length - 1);
260
+ historyIndexRef.current = nextIndex;
261
+ return history[nextIndex] ?? null;
262
+ }, onHistoryNext: () => {
263
+ if (historyIndexRef.current <= 0) {
264
+ historyIndexRef.current = -1;
265
+ return "";
266
+ }
267
+ historyIndexRef.current -= 1;
268
+ return inputHistoryRef.current[historyIndexRef.current] ?? "";
269
+ } })] }), _jsx(StatusBarView, { status: status })] }));
270
+ }
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function BusyIndicator(): React.JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Box, Text } from "ink";
4
+ import { PALETTE } from "./palette.js";
5
+ export function BusyIndicator() {
6
+ const [frame, setFrame] = useState(0);
7
+ useEffect(() => {
8
+ const t = setInterval(() => setFrame((f) => (f + 1) % 4), 400);
9
+ return () => clearInterval(t);
10
+ }, []);
11
+ const dots = [". ", ".. ", "...", " .."][frame] ?? "...";
12
+ return (_jsx(Box, { children: _jsx(Text, { color: PALETTE.thinking, dimColor: true, children: dots }) }));
13
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ /** Full-width horizontal rule between turns. */
3
+ export declare function GutterRule({ marginTop }: {
4
+ marginTop?: boolean;
5
+ }): React.JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { PALETTE } from "../palette.js";
4
+ import { getTerminalWidth } from "../terminal-width.js";
5
+ /** Full-width horizontal rule between turns. */
6
+ export function GutterRule({ marginTop }) {
7
+ const width = Math.max(20, getTerminalWidth() - 2);
8
+ return (_jsx(Box, { marginTop: marginTop ? 1 : 0, children: _jsx(Text, { color: PALETTE.gutter, children: "─".repeat(width) }) }));
9
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ export interface InlineToolRowProps {
3
+ icon: string;
4
+ label: string;
5
+ pending?: string;
6
+ complete?: boolean;
7
+ isError?: boolean;
8
+ marginTop: boolean;
9
+ }
10
+ export declare function InlineToolRow({ icon, label, pending, complete, isError, marginTop, }: InlineToolRowProps): React.JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { PALETTE } from "../palette.js";
4
+ const ICON_WIDTH = 2;
5
+ export function InlineToolRow({ icon, label, pending, complete = true, isError = false, marginTop, }) {
6
+ const color = isError ? PALETTE.error : complete ? PALETTE.muted : PALETTE.tool;
7
+ return (_jsx(Box, { flexDirection: "column", marginTop: marginTop ? 1 : 0, paddingLeft: 1, children: complete ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: ICON_WIDTH, children: _jsx(Text, { color: isError ? PALETTE.error : PALETTE.tool, children: icon }) }), _jsx(Text, { wrap: "wrap", color: color, dimColor: !isError, children: label })] })) : (_jsxs(Text, { wrap: "wrap", color: PALETTE.muted, dimColor: true, children: ["~ ", pending ?? "Running…"] })) }));
8
+ }
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { type Key } from "ink";
3
+ export interface PromptInputProps {
4
+ value: string;
5
+ onChange: (value: string) => void;
6
+ onSubmit: (value: string) => void;
7
+ placeholder?: string;
8
+ focus?: boolean;
9
+ /** Called for single-char keys when the input is empty. Return true to consume. */
10
+ onEmptyShortcut?: (key: string) => boolean;
11
+ /** Scroll / escape keys — PromptInput owns stdin so these must be routed here. */
12
+ onNavigationKey?: (key: Key, input: string) => boolean;
13
+ onHistoryPrev?: () => string | null;
14
+ onHistoryNext?: () => string | null;
15
+ }
16
+ /**
17
+ * Prompt line input — ink-text-input fork with empty-input shortcut support.
18
+ * Ink TextInput captures all keystrokes; shortcuts like `t` must be handled here.
19
+ */
20
+ export declare function PromptInput({ value: originalValue, placeholder, focus, onChange, onSubmit, onEmptyShortcut, onNavigationKey, onHistoryPrev, onHistoryNext, }: PromptInputProps): React.JSX.Element;
@@ -0,0 +1,120 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Text, useInput } from "ink";
4
+ import chalk from "chalk";
5
+ /**
6
+ * Prompt line input — ink-text-input fork with empty-input shortcut support.
7
+ * Ink TextInput captures all keystrokes; shortcuts like `t` must be handled here.
8
+ */
9
+ export function PromptInput({ value: originalValue, placeholder = "", focus = true, onChange, onSubmit, onEmptyShortcut, onNavigationKey, onHistoryPrev, onHistoryNext, }) {
10
+ const [state, setState] = useState({
11
+ cursorOffset: originalValue.length,
12
+ cursorWidth: 0,
13
+ });
14
+ const { cursorOffset, cursorWidth } = state;
15
+ useEffect(() => {
16
+ setState((previousState) => {
17
+ if (!focus)
18
+ return previousState;
19
+ const newValue = originalValue;
20
+ if (previousState.cursorOffset > newValue.length - 1) {
21
+ return { cursorOffset: newValue.length, cursorWidth: 0 };
22
+ }
23
+ return previousState;
24
+ });
25
+ }, [originalValue, focus]);
26
+ const cursorActualWidth = 0;
27
+ const value = originalValue;
28
+ let renderedValue = value;
29
+ let renderedPlaceholder = placeholder ? chalk.grey(placeholder) : undefined;
30
+ if (focus) {
31
+ renderedPlaceholder =
32
+ placeholder.length > 0
33
+ ? chalk.inverse(placeholder[0]) + chalk.grey(placeholder.slice(1))
34
+ : chalk.inverse(" ");
35
+ renderedValue = value.length > 0 ? "" : chalk.inverse(" ");
36
+ let i = 0;
37
+ for (const char of value) {
38
+ renderedValue +=
39
+ i >= cursorOffset - cursorActualWidth && i <= cursorOffset
40
+ ? chalk.inverse(char)
41
+ : char;
42
+ i++;
43
+ }
44
+ if (value.length > 0 && cursorOffset === value.length) {
45
+ renderedValue += chalk.inverse(" ");
46
+ }
47
+ }
48
+ useInput((input, key) => {
49
+ if (onNavigationKey?.(key, input)) {
50
+ return;
51
+ }
52
+ if (key.upArrow && onHistoryPrev) {
53
+ const prev = onHistoryPrev();
54
+ if (prev !== null) {
55
+ onChange(prev);
56
+ setState({ cursorOffset: prev.length, cursorWidth: 0 });
57
+ return;
58
+ }
59
+ }
60
+ if (key.downArrow && onHistoryNext) {
61
+ const next = onHistoryNext() ?? "";
62
+ onChange(next);
63
+ setState({ cursorOffset: next.length, cursorWidth: 0 });
64
+ return;
65
+ }
66
+ if (key.upArrow ||
67
+ key.downArrow ||
68
+ (key.ctrl && input === "c") ||
69
+ key.tab ||
70
+ (key.shift && key.tab)) {
71
+ return;
72
+ }
73
+ if (key.return) {
74
+ onSubmit?.(originalValue);
75
+ return;
76
+ }
77
+ if (!key.ctrl &&
78
+ !key.meta &&
79
+ input.length === 1 &&
80
+ originalValue.length === 0 &&
81
+ onEmptyShortcut?.(input)) {
82
+ return;
83
+ }
84
+ let nextCursorOffset = cursorOffset;
85
+ let nextValue = originalValue;
86
+ let nextCursorWidth = 0;
87
+ if (key.leftArrow) {
88
+ nextCursorOffset--;
89
+ }
90
+ else if (key.rightArrow) {
91
+ nextCursorOffset++;
92
+ }
93
+ else if (key.backspace || key.delete) {
94
+ if (cursorOffset > 0) {
95
+ nextValue =
96
+ originalValue.slice(0, cursorOffset - 1) +
97
+ originalValue.slice(cursorOffset);
98
+ nextCursorOffset--;
99
+ }
100
+ }
101
+ else if (input) {
102
+ nextValue =
103
+ originalValue.slice(0, cursorOffset) +
104
+ input +
105
+ originalValue.slice(cursorOffset);
106
+ nextCursorOffset += input.length;
107
+ if (input.length > 1)
108
+ nextCursorWidth = input.length;
109
+ }
110
+ nextCursorOffset = Math.max(0, Math.min(nextCursorOffset, nextValue.length));
111
+ setState({ cursorOffset: nextCursorOffset, cursorWidth: nextCursorWidth });
112
+ if (nextValue !== originalValue)
113
+ onChange(nextValue);
114
+ }, { isActive: focus });
115
+ return (_jsx(Text, { children: placeholder
116
+ ? value.length > 0
117
+ ? renderedValue
118
+ : renderedPlaceholder
119
+ : renderedValue }));
120
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export declare function SystemLine({ text, marginTop }: {
3
+ text: string;
4
+ marginTop: boolean;
5
+ }): React.JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { PALETTE } from "../palette.js";
4
+ export function SystemLine({ text, marginTop }) {
5
+ return (_jsx(Box, { marginTop: marginTop ? 1 : 0, paddingLeft: 1, children: _jsx(Text, { wrap: "wrap", color: PALETTE.system, dimColor: true, children: text }) }));
6
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ export interface ThinkingBlockProps {
3
+ text: string;
4
+ live?: boolean;
5
+ showBody: boolean;
6
+ durationMs?: number;
7
+ markdownRendering?: boolean;
8
+ syntaxHighlighting?: boolean;
9
+ syntaxTheme?: string;
10
+ }
11
+ export declare function ThinkingBlock({ text, live, showBody, durationMs, }: ThinkingBlockProps): React.JSX.Element | null;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { PALETTE } from "../palette.js";
4
+ import { formatThinkingDuration, reasoningSummary, } from "../reasoning-summary.js";
5
+ function thinkingTextProps(extra) {
6
+ return {
7
+ color: PALETTE.thinking,
8
+ dimColor: true,
9
+ italic: true,
10
+ ...extra,
11
+ };
12
+ }
13
+ function ThinkingBody({ bodyText }) {
14
+ return (_jsx(Text, { wrap: "wrap", ...thinkingTextProps(), children: bodyText }));
15
+ }
16
+ export function ThinkingBlock({ text, live = false, showBody, durationMs, }) {
17
+ const plain = text.trim();
18
+ const duration = durationMs !== undefined ? formatThinkingDuration(durationMs) : undefined;
19
+ if (!showBody) {
20
+ return (_jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, paddingLeft: 1, children: _jsx(Text, { ...thinkingTextProps({ wrap: "wrap" }), children: live ? "Thinking…" : duration ? `Thought · ${duration}` : "Thought" }) }));
21
+ }
22
+ if (!plain && live) {
23
+ return (_jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, paddingLeft: 1, children: _jsx(Text, { ...thinkingTextProps({ wrap: "wrap" }), children: "Thinking\u2026" }) }));
24
+ }
25
+ if (!plain)
26
+ return null;
27
+ const { title, body } = reasoningSummary(plain);
28
+ const bodyText = body || plain;
29
+ const hasBody = Boolean(bodyText.trim());
30
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, paddingLeft: 1, children: [title ? (_jsxs(Text, { ...thinkingTextProps({ wrap: "wrap" }), children: [live ? "Thinking" : "Thought", ": ", title, !live && duration ? ` · ${duration}` : live ? " …" : ""] })) : !live && duration ? (_jsxs(Text, { ...thinkingTextProps({ wrap: "wrap" }), children: ["Thought \u00B7 ", duration] })) : null, hasBody ? (_jsx(Box, { paddingLeft: title ? 2 : 0, marginTop: title ? 1 : 0, children: _jsx(ThinkingBody, { bodyText: bodyText }) })) : null] }));
31
+ }
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ export declare function ToastLine({ message }: {
3
+ message: string;
4
+ }): React.JSX.Element | null;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { PALETTE } from "../palette.js";
4
+ export function ToastLine({ message }) {
5
+ if (!message)
6
+ return null;
7
+ return (_jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: PALETTE.tool, dimColor: true, children: message }) }));
8
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export declare function ToolResultLine({ summary, isError, }: {
3
+ summary: string;
4
+ isError?: boolean;
5
+ }): React.JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { PALETTE } from "../palette.js";
4
+ export function ToolResultLine({ summary, isError = false, }) {
5
+ return (_jsx(Box, { paddingLeft: 3, children: _jsxs(Text, { wrap: "wrap", color: isError ? PALETTE.error : PALETTE.muted, dimColor: !isError, children: ["\u21B3 ", summary] }) }));
6
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export declare function TurnFooter({ text, marginTop }: {
3
+ text: string;
4
+ marginTop?: boolean;
5
+ }): React.JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { PALETTE } from "../palette.js";
4
+ import { GutterRule } from "./gutter-rule.js";
5
+ export function TurnFooter({ text, marginTop }) {
6
+ return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop ? 1 : 0, paddingTop: 1, children: [_jsx(GutterRule, {}), _jsx(Box, { paddingLeft: 1, marginTop: 0, children: _jsx(Text, { color: PALETTE.muted, dimColor: true, children: text }) })] }));
7
+ }
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ export declare function UserBlock({ text, marginTop, showTurnBreak, }: {
3
+ text: string;
4
+ marginTop: boolean;
5
+ showTurnBreak?: boolean;
6
+ }): React.JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { PALETTE } from "../palette.js";
4
+ export function UserBlock({ text, marginTop, showTurnBreak, }) {
5
+ return (_jsx(Box, { flexDirection: "column", marginTop: showTurnBreak || marginTop ? 1 : 0, borderStyle: "single", borderColor: PALETTE.user, borderLeft: true, borderTop: false, borderRight: false, borderBottom: false, paddingX: 1, paddingY: 1, children: _jsx(Text, { wrap: "wrap", color: PALETTE.user, children: text || " " }) }));
6
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export interface LogoBannerProps {
3
+ bootSummary?: string;
4
+ }
5
+ export declare function LogoBanner({ bootSummary }: LogoBannerProps): React.JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { PALETTE } from "./palette.js";
4
+ import { APP_VERSION } from "../../app-banner.js";
5
+ export function LogoBanner({ bootSummary }) {
6
+ const title = `▲ PRAANA ${APP_VERSION}`;
7
+ return (_jsx(Box, { marginBottom: 1, children: _jsxs(Box, { flexDirection: "column", paddingX: 2, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: PALETTE.assistant, children: title }) }), bootSummary ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: PALETTE.muted, dimColor: true, children: bootSummary }) })) : null, _jsx(Text, { color: PALETTE.text, children: "Terminal coding agent with adaptive context and persistent memory." }), _jsx(Text, { color: PALETTE.text, children: "I remember decisions across sessions and auto-compress stale context." }), _jsx(Box, { marginBottom: 1, marginTop: 2, children: _jsx(Text, { color: PALETTE.assistant, children: "Quick tips:" }) }), _jsxs(Box, { children: [_jsx(Box, { marginLeft: 2, marginRight: 1, children: _jsx(Text, { children: "*" }) }), _jsx(Text, { children: "Say what you want \u2014 I'll read code, run shell commands, make edits" })] }), _jsxs(Box, { children: [_jsx(Box, { marginLeft: 2, marginRight: 1, children: _jsx(Text, { children: "*" }) }), _jsx(Text, { children: "/help \u2014 all slash commands" })] }), _jsxs(Box, { children: [_jsx(Box, { marginLeft: 2, marginRight: 1, children: _jsx(Text, { children: "*" }) }), _jsx(Text, { children: "/stats \u2014 what I know and remember" })] }), _jsxs(Box, { children: [_jsx(Box, { marginLeft: 2, marginRight: 1, children: _jsx(Text, { children: "*" }) }), _jsx(Text, { children: "/recall \u2014 search my persistent memory" })] }), _jsxs(Box, { children: [_jsx(Box, { marginLeft: 2, marginRight: 1, children: _jsx(Text, { children: "*" }) }), _jsx(Text, { children: "/thinking on|off \u2014 show or hide reasoning text" })] }), _jsxs(Box, { children: [_jsx(Box, { marginLeft: 2, marginRight: 1, children: _jsx(Text, { children: "*" }) }), _jsx(Text, { children: "Esc \u2014 interrupt a running turn" })] })] }) }));
8
+ }
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import { type Token } from "marked";
3
+ interface MarkdownRenderProps {
4
+ text: string;
5
+ syntaxHighlighting?: boolean;
6
+ syntaxTheme?: string;
7
+ }
8
+ /** Flatten inline tokens to plain text (used in tests). */
9
+ export declare function plainTextFromInlineTokens(tokens: (Token | string)[]): string;
10
+ export declare function extractCellText(cell: {
11
+ text: string;
12
+ tokens: (Token | string)[];
13
+ }): string;
14
+ export declare function computeColWidths(headerTexts: string[], bodyTexts: string[][], terminalWidth?: number): number[];
15
+ export declare const MarkdownRender: React.NamedExoticComponent<MarkdownRenderProps>;
16
+ export {};