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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amit Kumar Dubey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # PRAANA
2
+
3
+ **A terminal coding agent with adaptive context and cross-session memory.**
4
+
5
+ PRAANA is experimental software. It runs in your terminal, calls an LLM, executes tools, and tries to keep long sessions usable by compressing old context instead of stuffing everything into the prompt. Between sessions it can extract learnings from transcripts and store them in a local SQLite database.
6
+
7
+ We have **not** benchmarked PRAANA against other agents. Treat memory and the context engine as ideas we're still proving in real use—not solved problems.
8
+
9
+ > **Status:** v0.5.0 <!-- x-release-please-version --> — experimental. Core flows work; long or messy tasks will hit rough edges.
10
+
11
+ > **How it was built:** Entirely vibecoded—this codebase was written by coding agents with human direction and review, not hand-coded line by line.
12
+
13
+ ---
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ git clone https://github.com/amitkumardubey/praana.git
19
+ cd praana
20
+ npm install && npm run build
21
+
22
+ cp praana.config.example.toml praana.config.toml
23
+ export OPENROUTER_API_KEY="sk-or-v1-..." # or another provider — see config example
24
+
25
+ node dist/main.js
26
+ ```
27
+
28
+ Global CLI (optional):
29
+
30
+ ```bash
31
+ npm run build && npm link
32
+ export PATH="$(npm config get prefix)/bin:$PATH"
33
+ praana # or the short alias: pran
34
+ ```
35
+
36
+ Requires Node 22+. Default UI is the Ink TUI when stdout is a TTY (`[ui] mode = "tui"`); use `praana --ui readline` for the classic interface. See [`praana.config.example.toml`](./praana.config.example.toml) for providers, memory, and engine settings.
37
+
38
+ Legacy `aria` config paths and `ARIA_*` environment variables are still read for backward compatibility.
39
+
40
+ ---
41
+
42
+ ## What it does
43
+
44
+ **Two compile modes** (see `[context_engine] enabled` in config):
45
+
46
+ | Mode | Default? | Behaviour |
47
+ |---|---|---|
48
+ | **Classic** | Yes (`enabled = false`) | Full verbatim transcript in the prompt. Same general shape as many coding agents. |
49
+ | **Engine** | Opt-in | Tiered working memory, tool-output distillation, session checkpoint, scored prompt compilation, progressive skills. |
50
+
51
+ **Cross-session memory** (optional, `[memory] enabled = true`):
52
+
53
+ - At `/exit`, a summariser extracts facts, decisions, patterns, mistakes, preferences, and constraints from the transcript.
54
+ - Next session starts with a ranked digest in the prompt.
55
+ - Project-scoped and global scopes; both are queried and merged in project sessions (#56).
56
+
57
+ **Project context:** loads `AGENTS.md` / `CLAUDE.md` plus an optional stack fingerprint (`package.json`, `go.mod`, etc.) on session start.
58
+
59
+ **Skills:** in engine mode, discovers `SKILL.md` files and loads them by relevance; in classic mode, lists paths only.
60
+
61
+ Architecture details: [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) · [docs/concepts.md](./docs/concepts.md)
62
+
63
+ ---
64
+
65
+ ## Known limitations (honest)
66
+
67
+ These are real gaps today—not a roadmap dressed up as marketing.
68
+
69
+ | Area | What's weak |
70
+ |---|---|
71
+ | **Memory recall** | Without Ollama or another semantic embedder, `hash` fallback is not meaningfully semantic. Global and project memories merge in project sessions, but near-duplicate or conflicting entries are not automatically reconciled. |
72
+ | **Context engine** | Off by default. Enabling it adds complexity; fallback to classic if init fails. |
73
+ | **Long sessions** | Tiering and distillation help but don't guarantee the model stays on track. |
74
+ | **Hydration** | Demoted state can be hidden until you mention it or the agent calls `hydrate`—the model doesn't always recover context proactively. |
75
+ | **Summariser** | Session-end learning needs a configured summariser and API access; can run in background on exit. |
76
+ | **Shell tool** | Optional path/command sandbox (`[shell]` in config); **off by default**. When disabled, runs with your user permissions. |
77
+ | **Comparison** | No published evals. We don't know if memory beats a plain transcript agent for your workflows. |
78
+
79
+ If cross-session memory doesn't help you after a few real projects, that's useful feedback—not a surprise.
80
+
81
+ ---
82
+
83
+ ## Slash commands
84
+
85
+ | Command | Purpose |
86
+ |---|---|
87
+ | `/help` | Full list |
88
+ | `/exit` | End session (runs summariser when memory is on) |
89
+ | `/clear`, `/new` | Reset working memory (engine state / checkpoint) |
90
+ | `/state` | Working-memory objects (engine mode) |
91
+ | `/digest` | Cross-session memory digest |
92
+ | `/recall <query>` | Search persistent memory |
93
+ | `/stats` | Session + memory stats |
94
+ | `/events` | Last 20 session log events |
95
+ | `/model <name>` | Switch model |
96
+ | `/sessions` | List sessions to resume |
97
+ | `/thinking <on\|off>` | Show or hide reasoning text |
98
+ | `/incognito <on\|off>` | Disable cross-session memory writes |
99
+ | `/debug` | Verbose tooling + saved prompts |
100
+ | `/why <id>` | Why a context unit was included (engine + debug) |
101
+
102
+ ---
103
+
104
+ ## Development
105
+
106
+ ```bash
107
+ npm run dev
108
+ npm run build
109
+ npm test
110
+ ```
111
+
112
+ ---
113
+
114
+ ## What's next
115
+
116
+ See [ROADMAP.md](./ROADMAP.md). High level: planner task graph, ongoing confidence reinforcement, LSP integration.
117
+
118
+ Issues and PRs welcome.
119
+
120
+ ---
121
+
122
+ ## License
123
+
124
+ MIT — [LICENSE](./LICENSE). Version history: [CHANGELOG.md](./CHANGELOG.md) (auto-generated by release-please on each release).
package/bin/praana.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
7
+ const mainPath = join(root, "dist/main.js");
8
+
9
+ if (!existsSync(mainPath)) {
10
+ process.stderr.write(
11
+ "[praana] Not built yet. From the repo root, run: npm run build\n"
12
+ );
13
+ process.exit(1);
14
+ }
15
+
16
+ const mod = await import(mainPath);
17
+ await mod.main();
package/bin/pran.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
7
+ const mainPath = join(root, "dist/main.js");
8
+
9
+ if (!existsSync(mainPath)) {
10
+ process.stderr.write(
11
+ "[pran] Not built yet. From the repo root, run: npm run build\n"
12
+ );
13
+ process.exit(1);
14
+ }
15
+
16
+ const mod = await import(mainPath);
17
+ await mod.main();
@@ -0,0 +1,11 @@
1
+ import type { Session } from "./session.js";
2
+ export declare const APP_VERSION: string;
3
+ export declare function formatSessionBannerLines(session: Session, cwd: string, model: string): string[];
4
+ export declare function printSessionBanner(session: Session, cwd: string, model: string): void;
5
+ export declare function formatSessionEndSummary(session: Session): string;
6
+ /** Resume hint printed after the TUI exits (OpenCode-style epilogue). */
7
+ export declare function formatSessionEpilogue(sessionId: string): string[];
8
+ export declare function printSessionEndSummary(session: Session): void;
9
+ export declare function getHelpLines(): string[];
10
+ export declare function printHelp(): void;
11
+ export declare function formatRecentConversationLines(session: Session, maxMessages?: number): string[];
@@ -0,0 +1,161 @@
1
+ import chalk from "chalk";
2
+ import boxen from "boxen";
3
+ import { readFileSync } from "node:fs";
4
+ import { getLoadedConfigSources } from "./config.js";
5
+ import { APP_NAME, APP_TAGLINE, CLI_NAME, CLI_SHORT, } from "./app-identity.js";
6
+ export const APP_VERSION = readAppVersion();
7
+ function readAppVersion() {
8
+ try {
9
+ const pkgUrl = new URL("../package.json", import.meta.url);
10
+ const pkg = JSON.parse(readFileSync(pkgUrl, "utf-8"));
11
+ return pkg.version ? `v${pkg.version}` : "v0.0.0";
12
+ }
13
+ catch {
14
+ return "v0.0.0";
15
+ }
16
+ }
17
+ export function formatSessionBannerLines(session, cwd, model) {
18
+ const memoryStats = session.getMemoryStats();
19
+ const digestLen = session.digest?.length ?? 0;
20
+ const configSources = getLoadedConfigSources();
21
+ return [
22
+ `${APP_NAME} ${APP_VERSION}`,
23
+ `session: ${session.id}`,
24
+ `cwd: ${cwd}`,
25
+ `model: ${model}`,
26
+ ...(configSources.length > 0
27
+ ? [`config: ${configSources.join(" → ")}`]
28
+ : [`config: defaults`]),
29
+ `memory entries: ${memoryStats.total}`,
30
+ `digest chars: ${digestLen}`,
31
+ session.memoryEnabled
32
+ ? `memory db: ${session.getMemoryDbPath() ?? "(unknown)"}`
33
+ : session.isIncognito()
34
+ ? "memory: incognito (disabled)"
35
+ : "memory: disabled",
36
+ ];
37
+ }
38
+ export function printSessionBanner(session, cwd, model) {
39
+ const W = 72;
40
+ const title = `▲ ${APP_NAME} [` + APP_VERSION + "]";
41
+ console.log(title + " " + "─".repeat(W - title.length - 2) + "┐");
42
+ const tagline = APP_TAGLINE;
43
+ console.log("│ " + chalk.dim(tagline) + " ".repeat(W - 3 - tagline.length) + "│");
44
+ console.log("└" + "─".repeat(W - 2) + "┘");
45
+ }
46
+ export function formatSessionEndSummary(session) {
47
+ const summary = session.getSessionSummary();
48
+ return `Session ended: ${summary.turns} turns, ${summary.stateObjects} state objects, ${summary.memoriesStored} memories stored`;
49
+ }
50
+ /** Resume hint printed after the TUI exits (OpenCode-style epilogue). */
51
+ export function formatSessionEpilogue(sessionId) {
52
+ return ["", ` ${CLI_NAME} resume ${sessionId}`, ` ${CLI_SHORT} resume ${sessionId}`, ""];
53
+ }
54
+ export function printSessionEndSummary(session) {
55
+ console.log(formatSessionEndSummary(session));
56
+ }
57
+ function usageLines() {
58
+ return [
59
+ ` ${CLI_NAME} Start new session in current directory`,
60
+ ` ${CLI_SHORT} Short alias for ${CLI_NAME}`,
61
+ ` ${CLI_NAME} resume <session> Resume an existing session`,
62
+ ` ${CLI_NAME} --ui tui Start with Ink TUI (default when TTY)`,
63
+ ` ${CLI_NAME} --ui readline Classic readline interface`,
64
+ ` ${CLI_NAME} --screen alternate Full-screen TUI (fixed viewport)`,
65
+ ` ${CLI_NAME} --debug Start with debug mode enabled`,
66
+ ` ${CLI_NAME} --incognito Start without cross-session memory persistence`,
67
+ ` ${CLI_NAME} -I Short alias for --incognito`,
68
+ ` ${CLI_NAME} --config <path> Load config from specific .json/.toml path`,
69
+ ` ${CLI_NAME} --help Show this help`,
70
+ ];
71
+ }
72
+ export function getHelpLines() {
73
+ return [
74
+ chalk.bold(` ${APP_NAME} — ${APP_TAGLINE}`),
75
+ "",
76
+ "Usage:",
77
+ ...usageLines(),
78
+ "",
79
+ "Slash Commands:",
80
+ " /exit End session and save",
81
+ " /state List all state objects for this session",
82
+ " /stats Show session, working-memory, and persistent-memory stats",
83
+ " /digest Print cross-session memory digest",
84
+ " /events Show last 20 events",
85
+ " /recall <query> Search cross-session knowledge base",
86
+ " /model <name> Switch LLM model (e.g., openai/gpt-4o)",
87
+ " /sessions List recent sessions",
88
+ " /debug Toggle debug mode (tool blocks + saved prompts)",
89
+ " /why <unit-id> Explain last compile score for a context unit",
90
+ " /thinking <on|off> Toggle thinking stream visibility",
91
+ " /incognito <on|off> Toggle cross-session memory persistence",
92
+ " /clear Clear working-memory state",
93
+ " /new Clear working-memory state",
94
+ "",
95
+ " Status bar: model, context, mode, repo, memory tiers, skills, task",
96
+ " Esc Esc Interrupt a running turn (Ctrl+C also works)",
97
+ " /help Show this help",
98
+ ];
99
+ }
100
+ export function printHelp() {
101
+ const usage = usageLines().join("\n");
102
+ const commands = [
103
+ " /exit End session and save",
104
+ " /state List all state objects for this session",
105
+ " /stats Show session, working-memory, and persistent-memory stats",
106
+ " /digest Print cross-session memory digest",
107
+ " /events Show last 20 events",
108
+ " /recall <query> Search cross-session knowledge base",
109
+ " /model <name> Switch LLM model (e.g., openai/gpt-4o)",
110
+ " /sessions List recent sessions",
111
+ " /debug Toggle debug mode (tool blocks + saved prompts)",
112
+ " /why <unit-id> Explain last compile score for a context unit",
113
+ " /thinking <on|off> Toggle thinking stream visibility",
114
+ " /incognito <on|off> Toggle cross-session memory persistence",
115
+ " /clear Clear working-memory state",
116
+ " /new Clear working-memory state",
117
+ "",
118
+ " Status bar (above prompt): model, context, mode, repo, memory tiers, skills, task",
119
+ " Esc Esc Interrupt a running turn (Ctrl+C also works)",
120
+ " /help Show this help",
121
+ ].join("\n");
122
+ console.log(chalk.bold(` ${APP_NAME} — ${APP_TAGLINE}`) +
123
+ "\n\n" +
124
+ boxen(usage, {
125
+ padding: { top: 0, bottom: 0, left: 0, right: 0 },
126
+ margin: { top: 0, bottom: 1, left: 0, right: 0 },
127
+ borderStyle: "round",
128
+ borderColor: "cyan",
129
+ title: "Usage",
130
+ titleAlignment: "left",
131
+ }) +
132
+ boxen(commands, {
133
+ padding: { top: 0, bottom: 0, left: 0, right: 0 },
134
+ margin: { top: 0, bottom: 1, left: 0, right: 0 },
135
+ borderStyle: "round",
136
+ borderColor: "green",
137
+ title: "Slash Commands",
138
+ titleAlignment: "left",
139
+ }));
140
+ }
141
+ export function formatRecentConversationLines(session, maxMessages = 6) {
142
+ const recentEvents = session.eventLog.readLast(30);
143
+ const turns = recentEvents.filter((e) => e.kind === "user_message" || e.kind === "agent_message");
144
+ if (turns.length === 0)
145
+ return [];
146
+ const lines = [
147
+ "─".repeat(50),
148
+ ` 📜 Recent conversation (${Math.min(turns.length, maxMessages)} of ${turns.length} messages)`,
149
+ "─".repeat(50),
150
+ ];
151
+ const shown = turns.slice(-maxMessages);
152
+ for (const ev of shown) {
153
+ const prefix = ev.kind === "user_message" ? "You" : APP_NAME;
154
+ const text = ev.payload.text?.trim() ?? "";
155
+ const displayLines = text.split("\n").slice(0, 2).join(" ");
156
+ const display = displayLines.length > 150 ? displayLines.slice(0, 147) + "..." : displayLines;
157
+ lines.push(` ${prefix}: ${display}`);
158
+ }
159
+ lines.push("─".repeat(50));
160
+ return lines;
161
+ }
@@ -0,0 +1,44 @@
1
+ import type { PraanaConfig } from "./types.js";
2
+ import type { CliArgs } from "./cli-args.js";
3
+ import type { UiMode } from "./types.js";
4
+ import { Session } from "./session.js";
5
+ import type { StatusBarInput } from "./status-bar.js";
6
+ import { type SlashCommandResult } from "./slash-commands.js";
7
+ import type { TurnUiSink } from "./ui-events.js";
8
+ export interface StartupInfo {
9
+ session: Session;
10
+ cwd: string;
11
+ model: string;
12
+ bannerLines: string[];
13
+ recentConversationLines: string[];
14
+ /** Full transcript entries rebuilt from event log on resume (TUI). */
15
+ transcriptBootstrap: import("./ui/tui/reducer.js").TranscriptEntry[];
16
+ isResume: boolean;
17
+ }
18
+ export declare class AppController {
19
+ session: Session;
20
+ readonly cwd: string;
21
+ readonly config: PraanaConfig;
22
+ readonly parsed: CliArgs;
23
+ showThinking: boolean;
24
+ currentModel?: string;
25
+ sessionEnded: boolean;
26
+ private readonly turnController;
27
+ private interruptHandling;
28
+ constructor(opts: {
29
+ cwd?: string;
30
+ config: PraanaConfig;
31
+ parsed: CliArgs;
32
+ });
33
+ start(opts?: {
34
+ uiMode?: UiMode;
35
+ }): Promise<StartupInfo>;
36
+ currentModelOrDefault(): string;
37
+ getStatusBarInput(): StatusBarInput;
38
+ isTurnActive(): boolean;
39
+ abortTurn(): void;
40
+ handleUserInterrupt(onPromptExit?: () => void): "abort_turn" | "prompt_exit" | "noop";
41
+ executeSlashCommand(input: string): Promise<SlashCommandResult>;
42
+ runUserTurn(input: string, sink?: TurnUiSink): Promise<void>;
43
+ shutdown(): Promise<void>;
44
+ }
@@ -0,0 +1,143 @@
1
+ import { resolve } from "node:path";
2
+ import { Session } from "./session.js";
3
+ import { runTurn } from "./turn.js";
4
+ import { TurnController } from "./turn-control.js";
5
+ import { buildStatusBarInput } from "./status-bar.js";
6
+ import { executeSlashCommand } from "./slash-commands.js";
7
+ import { createDefaultTurnSink } from "./ui-events.js";
8
+ import { formatRecentConversationLines, formatSessionBannerLines, } from "./app-banner.js";
9
+ import { buildTranscriptFromEvents } from "./ui/tui/transcript-replay.js";
10
+ export class AppController {
11
+ session;
12
+ cwd;
13
+ config;
14
+ parsed;
15
+ showThinking = false;
16
+ currentModel;
17
+ sessionEnded = false;
18
+ turnController = new TurnController();
19
+ interruptHandling = false;
20
+ constructor(opts) {
21
+ this.cwd = opts.cwd ?? resolve(process.cwd());
22
+ this.config = opts.config;
23
+ this.parsed = opts.parsed;
24
+ }
25
+ async start(opts) {
26
+ const { sessionId, resumeMode, debug } = this.parsed;
27
+ const captureNotice = opts?.uiMode === "tui" ? (_line) => { } : undefined;
28
+ if (resumeMode && sessionId) {
29
+ this.session = await Session.resume(sessionId, this.cwd, this.config, {
30
+ captureNotice,
31
+ });
32
+ this.session.debug = debug;
33
+ }
34
+ else {
35
+ this.session = await Session.create(this.cwd, this.config, {
36
+ incognito: this.parsed.incognito,
37
+ captureNotice,
38
+ });
39
+ this.session.debug = debug;
40
+ }
41
+ this.currentModel = this.session.getModelOverride() ?? undefined;
42
+ const model = this.currentModelOrDefault();
43
+ return {
44
+ session: this.session,
45
+ cwd: this.cwd,
46
+ model,
47
+ bannerLines: formatSessionBannerLines(this.session, this.cwd, model),
48
+ recentConversationLines: resumeMode
49
+ ? formatRecentConversationLines(this.session)
50
+ : [],
51
+ transcriptBootstrap: resumeMode
52
+ ? buildTranscriptFromEvents(this.session.eventLog.readAll())
53
+ : [],
54
+ isResume: !!resumeMode,
55
+ };
56
+ }
57
+ currentModelOrDefault() {
58
+ return this.currentModel ?? this.session.config.llm.model;
59
+ }
60
+ getStatusBarInput() {
61
+ const model = this.currentModelOrDefault();
62
+ return buildStatusBarInput(this.session, {
63
+ model,
64
+ debug: this.session.debug,
65
+ thinking: this.showThinking,
66
+ contextWindowTokens: this.session.getContextWindowTokens(model),
67
+ });
68
+ }
69
+ isTurnActive() {
70
+ return this.turnController.isActive();
71
+ }
72
+ abortTurn() {
73
+ this.turnController.abort();
74
+ }
75
+ handleUserInterrupt(onPromptExit) {
76
+ if (this.interruptHandling)
77
+ return "noop";
78
+ this.interruptHandling = true;
79
+ setImmediate(() => {
80
+ this.interruptHandling = false;
81
+ });
82
+ if (this.turnController.isActive()) {
83
+ this.turnController.abort();
84
+ return "abort_turn";
85
+ }
86
+ onPromptExit?.();
87
+ return "prompt_exit";
88
+ }
89
+ async executeSlashCommand(input) {
90
+ return executeSlashCommand(input, this.session, {
91
+ setModel: (m) => {
92
+ this.currentModel = m;
93
+ },
94
+ setThinking: (v) => {
95
+ this.showThinking = v;
96
+ },
97
+ getThinking: () => this.showThinking,
98
+ });
99
+ }
100
+ async runUserTurn(input, sink) {
101
+ const uiSink = sink ?? createDefaultTurnSink();
102
+ const signal = this.turnController.begin();
103
+ uiSink.onSpinnerStart?.("thinking…");
104
+ let spinnerStopped = false;
105
+ const stopSpinnerOnce = () => {
106
+ if (spinnerStopped)
107
+ return;
108
+ uiSink.onSpinnerStop?.();
109
+ spinnerStopped = true;
110
+ };
111
+ const wrappedSink = {
112
+ ...uiSink,
113
+ onTextDelta: (delta) => {
114
+ stopSpinnerOnce();
115
+ uiSink.onTextDelta?.(delta);
116
+ },
117
+ onThinkingDelta: (delta) => {
118
+ stopSpinnerOnce();
119
+ uiSink.onThinkingDelta?.(delta);
120
+ },
121
+ onToolCallsStart: () => {
122
+ uiSink.onToolCallsStart?.();
123
+ },
124
+ };
125
+ try {
126
+ await runTurn(this.session, input, this.currentModel, {
127
+ signal,
128
+ sink: wrappedSink,
129
+ });
130
+ stopSpinnerOnce();
131
+ }
132
+ finally {
133
+ this.turnController.end();
134
+ }
135
+ }
136
+ async shutdown() {
137
+ if (this.sessionEnded)
138
+ return;
139
+ this.sessionEnded = true;
140
+ const events = this.session.getTranscriptEvents();
141
+ await this.session.end("clean", events, { memoryTimeoutMs: 5_000 });
142
+ }
143
+ }
@@ -0,0 +1,18 @@
1
+ /** Product identity — single source of truth for rename-sensitive strings. */
2
+ export declare const APP_NAME = "PRAANA";
3
+ export declare const APP_TAGLINE = "Persistent Reasoning Agent with Adaptive Navigation and Action";
4
+ export declare const APP_AGENT_ID = "praana";
5
+ /** Memory entries written before the PRAANA rename used this agent scope. */
6
+ export declare const LEGACY_APP_AGENT_ID = "aria";
7
+ export declare const APP_HOME_DIR = ".praana";
8
+ export declare const LEGACY_APP_HOME_DIR = ".aria";
9
+ export declare const CLI_NAME = "praana";
10
+ export declare const CLI_SHORT = "pran";
11
+ export declare function appHomePath(...parts: string[]): string;
12
+ export declare function legacyAppHomePath(...parts: string[]): string;
13
+ /** Prefer PRAANA home; fall back to legacy ~/.aria when the new dir is unused. */
14
+ export declare function resolveAppHomePath(...parts: string[]): string;
15
+ export declare function resolveDefaultMemoryDbPath(): string;
16
+ export declare function resolveDefaultSessionLogDir(): string;
17
+ export declare function envOverride(primary: string, legacy?: string): string | undefined;
18
+ export declare function envFlag(primary: string, legacy?: string): boolean | undefined;
@@ -0,0 +1,52 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ /** Product identity — single source of truth for rename-sensitive strings. */
5
+ export const APP_NAME = "PRAANA";
6
+ export const APP_TAGLINE = "Persistent Reasoning Agent with Adaptive Navigation and Action";
7
+ export const APP_AGENT_ID = "praana";
8
+ /** Memory entries written before the PRAANA rename used this agent scope. */
9
+ export const LEGACY_APP_AGENT_ID = "aria";
10
+ export const APP_HOME_DIR = ".praana";
11
+ export const LEGACY_APP_HOME_DIR = ".aria";
12
+ export const CLI_NAME = "praana";
13
+ export const CLI_SHORT = "pran";
14
+ export function appHomePath(...parts) {
15
+ return join(homedir(), APP_HOME_DIR, ...parts);
16
+ }
17
+ export function legacyAppHomePath(...parts) {
18
+ return join(homedir(), LEGACY_APP_HOME_DIR, ...parts);
19
+ }
20
+ /** Prefer PRAANA home; fall back to legacy ~/.aria when the new dir is unused. */
21
+ export function resolveAppHomePath(...parts) {
22
+ const next = appHomePath(...parts);
23
+ if (parts.length === 0) {
24
+ return existsSync(next) || !existsSync(legacyAppHomePath())
25
+ ? next
26
+ : legacyAppHomePath();
27
+ }
28
+ if (existsSync(next))
29
+ return next;
30
+ const legacy = legacyAppHomePath(...parts);
31
+ return existsSync(legacy) ? legacy : next;
32
+ }
33
+ export function resolveDefaultMemoryDbPath() {
34
+ return resolveAppHomePath("memory.db");
35
+ }
36
+ export function resolveDefaultSessionLogDir() {
37
+ return resolveAppHomePath("sessions");
38
+ }
39
+ export function envOverride(primary, legacy) {
40
+ const value = process.env[primary]?.trim();
41
+ if (value)
42
+ return value;
43
+ if (!legacy)
44
+ return undefined;
45
+ return process.env[legacy]?.trim() || undefined;
46
+ }
47
+ export function envFlag(primary, legacy) {
48
+ const raw = envOverride(primary, legacy);
49
+ if (raw === undefined)
50
+ return undefined;
51
+ return raw === "true" || raw === "1";
52
+ }
@@ -0,0 +1,16 @@
1
+ import type { Event } from "./types.js";
2
+ import type { Session } from "./session.js";
3
+ import type { SessionEvent } from "./memory/types.js";
4
+ export interface ClassicCompactionResult {
5
+ compacted: boolean;
6
+ eventsCompacted: number;
7
+ factsStored: number;
8
+ pressureRatio: number;
9
+ }
10
+ export declare function eventsToSessionEvents(events: Event[]): SessionEvent[];
11
+ /**
12
+ * Classic-mode compaction: summarise oldest transcript events into Cognitive Memory
13
+ * and mark them compressed so they leave the compiled prompt.
14
+ */
15
+ export declare function maybeAutoCompactClassic(session: Session, promptTokens: number, modelId: string): Promise<ClassicCompactionResult>;
16
+ export declare function formatCompactionBanner(result: ClassicCompactionResult): string | null;