@ouro.bot/cli 0.1.0-alpha.26 → 0.1.0-alpha.260

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 (262) hide show
  1. package/README.md +187 -191
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
  4. package/changelog.json +1576 -0
  5. package/dist/arc/attention-types.js +8 -0
  6. package/dist/arc/cares.js +140 -0
  7. package/dist/arc/episodes.js +117 -0
  8. package/dist/arc/intentions.js +133 -0
  9. package/dist/arc/json-store.js +117 -0
  10. package/dist/arc/obligations.js +237 -0
  11. package/dist/arc/presence.js +185 -0
  12. package/dist/heart/active-work.js +832 -0
  13. package/dist/heart/agent-entry.js +37 -2
  14. package/dist/heart/auth/auth-flow.js +447 -0
  15. package/dist/heart/bridges/manager.js +358 -0
  16. package/dist/heart/bridges/state-machine.js +135 -0
  17. package/dist/heart/bridges/store.js +123 -0
  18. package/dist/heart/commitments.js +111 -0
  19. package/dist/heart/config-registry.js +304 -0
  20. package/dist/heart/config.js +63 -30
  21. package/dist/heart/core.js +644 -196
  22. package/dist/heart/cross-chat-delivery.js +131 -0
  23. package/dist/heart/daemon/agent-config-check.js +149 -0
  24. package/dist/heart/daemon/agent-discovery.js +145 -0
  25. package/dist/heart/daemon/agent-service.js +360 -0
  26. package/dist/heart/daemon/cadence.js +70 -0
  27. package/dist/heart/daemon/cli-defaults.js +557 -0
  28. package/dist/heart/daemon/cli-exec.js +1877 -0
  29. package/dist/heart/daemon/cli-parse.js +759 -0
  30. package/dist/heart/daemon/cli-render.js +464 -0
  31. package/dist/heart/daemon/cli-types.js +8 -0
  32. package/dist/heart/daemon/daemon-cli.js +29 -1179
  33. package/dist/heart/daemon/daemon-entry.js +216 -4
  34. package/dist/heart/daemon/daemon-health.js +136 -0
  35. package/dist/heart/daemon/daemon-runtime-sync.js +153 -12
  36. package/dist/heart/daemon/daemon-tombstone.js +123 -0
  37. package/dist/heart/daemon/daemon.js +590 -54
  38. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  39. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  40. package/dist/heart/daemon/inner-status.js +89 -0
  41. package/dist/heart/daemon/launchd.js +39 -8
  42. package/dist/heart/daemon/log-tailer.js +4 -3
  43. package/dist/heart/daemon/message-router.js +17 -8
  44. package/dist/heart/daemon/os-cron-deps.js +134 -0
  45. package/dist/heart/daemon/ouro-bot-entry.js +1 -1
  46. package/dist/heart/daemon/process-manager.js +123 -0
  47. package/dist/heart/daemon/run-hooks.js +2 -0
  48. package/dist/heart/daemon/runtime-logging.js +58 -15
  49. package/dist/heart/daemon/runtime-metadata.js +101 -0
  50. package/dist/heart/daemon/runtime-mode.js +67 -0
  51. package/dist/heart/daemon/safe-mode.js +161 -0
  52. package/dist/heart/daemon/sense-manager.js +72 -3
  53. package/dist/heart/daemon/session-id-resolver.js +131 -0
  54. package/dist/heart/daemon/skill-management-installer.js +94 -0
  55. package/dist/heart/daemon/socket-client.js +276 -0
  56. package/dist/heart/daemon/task-scheduler.js +3 -25
  57. package/dist/heart/daemon/thoughts.js +510 -0
  58. package/dist/heart/delegation.js +62 -0
  59. package/dist/heart/habits/habit-migration.js +181 -0
  60. package/dist/heart/habits/habit-parser.js +119 -0
  61. package/dist/heart/habits/habit-scheduler.js +371 -0
  62. package/dist/heart/{daemon → hatch}/hatch-flow.js +30 -120
  63. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  64. package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
  65. package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
  66. package/dist/heart/identity.js +157 -55
  67. package/dist/heart/kicks.js +2 -20
  68. package/dist/heart/mcp/mcp-server.js +653 -0
  69. package/dist/heart/migrate-config.js +127 -0
  70. package/dist/heart/model-capabilities.js +59 -0
  71. package/dist/heart/outlook/outlook-http.js +439 -0
  72. package/dist/heart/outlook/outlook-read.js +1618 -0
  73. package/dist/heart/outlook/outlook-render.js +1032 -0
  74. package/dist/heart/outlook/outlook-types.js +7 -0
  75. package/dist/heart/outlook/outlook-view.js +194 -0
  76. package/dist/heart/progress-story.js +42 -0
  77. package/dist/heart/provider-failover.js +88 -0
  78. package/dist/heart/provider-ping.js +162 -0
  79. package/dist/heart/providers/anthropic-token.js +163 -0
  80. package/dist/heart/providers/anthropic.js +169 -46
  81. package/dist/heart/providers/azure.js +98 -11
  82. package/dist/heart/providers/error-classification.js +63 -0
  83. package/dist/heart/providers/github-copilot.js +136 -0
  84. package/dist/heart/providers/minimax-vlm.js +189 -0
  85. package/dist/heart/providers/minimax.js +23 -5
  86. package/dist/heart/providers/openai-codex.js +33 -22
  87. package/dist/heart/session-activity.js +172 -0
  88. package/dist/heart/session-recall.js +216 -0
  89. package/dist/heart/start-of-turn-packet.js +330 -0
  90. package/dist/heart/streaming.js +36 -27
  91. package/dist/heart/sync.js +277 -0
  92. package/dist/heart/target-resolution.js +127 -0
  93. package/dist/heart/tempo.js +93 -0
  94. package/dist/heart/temporal-view.js +41 -0
  95. package/dist/heart/tool-activity-callbacks.js +36 -0
  96. package/dist/heart/tool-description.js +135 -0
  97. package/dist/heart/tool-loop.js +200 -0
  98. package/dist/heart/turn-context.js +358 -0
  99. package/dist/heart/turn-coordinator.js +28 -0
  100. package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
  101. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  102. package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
  103. package/dist/heart/versioning/ouro-version-manager.js +171 -0
  104. package/dist/heart/{daemon → versioning}/staged-restart.js +30 -6
  105. package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
  106. package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +41 -3
  107. package/dist/mind/associative-recall.js +126 -66
  108. package/dist/mind/bundle-manifest.js +8 -1
  109. package/dist/mind/context.js +125 -14
  110. package/dist/mind/{memory.js → diary.js} +68 -96
  111. package/dist/mind/embedding-provider.js +60 -0
  112. package/dist/mind/file-state.js +179 -0
  113. package/dist/mind/first-impressions.js +16 -2
  114. package/dist/mind/friends/channel.js +56 -0
  115. package/dist/mind/friends/group-context.js +144 -0
  116. package/dist/mind/friends/resolver.js +37 -0
  117. package/dist/mind/friends/store-file.js +58 -3
  118. package/dist/mind/friends/trust-explanation.js +74 -0
  119. package/dist/mind/friends/types.js +8 -0
  120. package/dist/mind/journal-index.js +161 -0
  121. package/dist/mind/obligation-steering.js +221 -0
  122. package/dist/mind/pending.js +76 -9
  123. package/dist/mind/phrases.js +1 -0
  124. package/dist/mind/prompt.js +765 -109
  125. package/dist/mind/scrutiny.js +173 -0
  126. package/dist/mind/token-estimate.js +8 -12
  127. package/dist/nerves/coverage/audit.js +1 -1
  128. package/dist/nerves/coverage/file-completeness.js +50 -5
  129. package/dist/nerves/coverage/run-artifacts.js +1 -1
  130. package/dist/nerves/event-buffer.js +111 -0
  131. package/dist/nerves/index.js +64 -4
  132. package/dist/nerves/observation.js +20 -0
  133. package/dist/nerves/runtime.js +5 -1
  134. package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
  135. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  136. package/dist/outlook-ui/index.html +15 -0
  137. package/dist/repertoire/ado-client.js +15 -56
  138. package/dist/repertoire/ado-semantic.js +11 -10
  139. package/dist/repertoire/api-client.js +97 -0
  140. package/dist/repertoire/bitwarden-store.js +319 -0
  141. package/dist/repertoire/bw-installer.js +79 -0
  142. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  143. package/dist/repertoire/coding/context-pack.js +330 -0
  144. package/dist/repertoire/coding/feedback.js +197 -30
  145. package/dist/repertoire/coding/manager.js +149 -2
  146. package/dist/repertoire/coding/spawner.js +55 -9
  147. package/dist/repertoire/coding/tools.js +170 -7
  148. package/dist/repertoire/commerce-errors.js +109 -0
  149. package/dist/repertoire/commerce-self-test.js +156 -0
  150. package/dist/repertoire/credential-access.js +515 -0
  151. package/dist/repertoire/duffel-client.js +185 -0
  152. package/dist/repertoire/github-client.js +14 -55
  153. package/dist/repertoire/graph-client.js +11 -52
  154. package/dist/repertoire/guardrails.js +369 -0
  155. package/dist/repertoire/mcp-client.js +255 -0
  156. package/dist/repertoire/mcp-manager.js +305 -0
  157. package/dist/repertoire/mcp-tools.js +63 -0
  158. package/dist/repertoire/shell-sessions.js +133 -0
  159. package/dist/repertoire/skills.js +14 -23
  160. package/dist/repertoire/stripe-client.js +131 -0
  161. package/dist/repertoire/tasks/board.js +43 -5
  162. package/dist/repertoire/tasks/fix.js +182 -0
  163. package/dist/repertoire/tasks/index.js +28 -10
  164. package/dist/repertoire/tasks/lifecycle.js +2 -2
  165. package/dist/repertoire/tasks/parser.js +3 -2
  166. package/dist/repertoire/tasks/scanner.js +194 -37
  167. package/dist/repertoire/tasks/transitions.js +11 -28
  168. package/dist/repertoire/tools-base.js +43 -771
  169. package/dist/repertoire/tools-bluebubbles.js +201 -0
  170. package/dist/repertoire/tools-bridge.js +141 -0
  171. package/dist/repertoire/tools-config.js +185 -0
  172. package/dist/repertoire/tools-continuity.js +248 -0
  173. package/dist/repertoire/tools-credential.js +182 -0
  174. package/dist/repertoire/tools-files.js +342 -0
  175. package/dist/repertoire/tools-flight.js +224 -0
  176. package/dist/repertoire/tools-flow.js +62 -0
  177. package/dist/repertoire/tools-github.js +1 -7
  178. package/dist/repertoire/tools-memory.js +347 -0
  179. package/dist/repertoire/tools-session.js +739 -0
  180. package/dist/repertoire/tools-shell.js +120 -0
  181. package/dist/repertoire/tools-stripe.js +180 -0
  182. package/dist/repertoire/tools-surface.js +243 -0
  183. package/dist/repertoire/tools-teams.js +12 -62
  184. package/dist/repertoire/tools-travel.js +125 -0
  185. package/dist/repertoire/tools-user-profile.js +144 -0
  186. package/dist/repertoire/tools-vault.js +110 -0
  187. package/dist/repertoire/tools.js +155 -130
  188. package/dist/repertoire/travel-api-client.js +360 -0
  189. package/dist/repertoire/user-profile.js +118 -0
  190. package/dist/repertoire/vault-setup.js +241 -0
  191. package/dist/scripts/claude-code-hook.js +41 -0
  192. package/dist/scripts/claude-code-stop-hook.js +47 -0
  193. package/dist/senses/attention-queue.js +97 -0
  194. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  195. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +145 -10
  196. package/dist/senses/bluebubbles/entry.js +13 -0
  197. package/dist/senses/bluebubbles/inbound-log.js +110 -0
  198. package/dist/senses/bluebubbles/index.js +1374 -0
  199. package/dist/senses/bluebubbles/media.js +455 -0
  200. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +20 -5
  201. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
  202. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  203. package/dist/senses/bluebubbles/session-cleanup.js +72 -0
  204. package/dist/senses/cli/image-paste.js +211 -0
  205. package/dist/senses/cli/ink-app.js +156 -0
  206. package/dist/senses/cli/inline-diff.js +64 -0
  207. package/dist/senses/cli/message-list.js +51 -0
  208. package/dist/senses/cli/ouro-tui.js +416 -0
  209. package/dist/senses/cli/spinner-imperative.js +135 -0
  210. package/dist/senses/cli/spinner.js +101 -0
  211. package/dist/senses/cli/status-line.js +60 -0
  212. package/dist/senses/cli/streaming-markdown.js +526 -0
  213. package/dist/senses/cli/tool-display.js +83 -0
  214. package/dist/senses/cli/tool-render.js +85 -0
  215. package/dist/senses/cli/tui-store.js +236 -0
  216. package/dist/senses/cli/virtual-list.js +35 -0
  217. package/dist/senses/cli-entry.js +1 -1
  218. package/dist/senses/cli-layout.js +187 -0
  219. package/dist/senses/cli.js +568 -246
  220. package/dist/senses/commands.js +65 -1
  221. package/dist/senses/continuity.js +94 -0
  222. package/dist/senses/habit-turn-message.js +108 -0
  223. package/dist/senses/inner-dialog-worker.js +112 -19
  224. package/dist/senses/inner-dialog.js +593 -85
  225. package/dist/senses/pipeline.js +552 -0
  226. package/dist/senses/shared-turn.js +199 -0
  227. package/dist/senses/surface-tool.js +68 -0
  228. package/dist/senses/teams.js +664 -165
  229. package/dist/senses/trust-gate.js +112 -2
  230. package/package.json +14 -7
  231. package/skills/agent-commerce.md +106 -0
  232. package/skills/browser-navigation.md +110 -0
  233. package/skills/commerce-setup-guide.md +116 -0
  234. package/skills/commerce-setup.md +84 -0
  235. package/skills/configure-dev-tools.md +81 -0
  236. package/skills/travel-planning.md +138 -0
  237. package/dist/heart/daemon/subagent-installer.js +0 -134
  238. package/dist/senses/bluebubbles-entry.js +0 -11
  239. package/dist/senses/bluebubbles-media.js +0 -244
  240. package/dist/senses/bluebubbles.js +0 -421
  241. package/dist/senses/debug-activity.js +0 -107
  242. package/subagents/README.md +0 -73
  243. package/subagents/work-doer.md +0 -235
  244. package/subagents/work-merger.md +0 -618
  245. package/subagents/work-planner.md +0 -382
  246. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  247. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  248. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  249. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  250. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  251. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  252. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  253. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  254. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  255. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  256. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  257. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  258. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  259. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  260. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  261. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  262. /package/dist/heart/{daemon → versioning}/update-hooks.js +0 -0
@@ -0,0 +1,416 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QueuedMessages = QueuedMessages;
4
+ exports.OuroTui = OuroTui;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ /**
7
+ * Ouroboros TUI — full terminal interface using Ink v3 + Static.
8
+ *
9
+ * Architecture:
10
+ * <Static> renders completed messages ONCE — they scroll up naturally.
11
+ * Only the "live" area (current streaming + spinner + input) re-renders.
12
+ * This avoids the screen-clearing problem that broke the previous Ink attempt.
13
+ *
14
+ * Design language: ouroboros brand palette from ouroboros.bot / Outlook UI.
15
+ * ZERO business logic here — pure rendering from CliStore state.
16
+ */
17
+ const react_1 = require("react");
18
+ const ink_1 = require("ink");
19
+ const streaming_markdown_1 = require("./streaming-markdown");
20
+ const image_paste_1 = require("./image-paste");
21
+ // ─── Ouroboros Brand Palette (ANSI RGB) ─────────────────────────────
22
+ // From packages/outlook-ui/src/style.css and ouroboros.bot
23
+ const OURO = {
24
+ scale: "#2f8f4e", // primary green
25
+ teal: "#4ec9b0", // tool/accent teal
26
+ glow: "#74e08f", // bright green (highlights)
27
+ bone: "#eef2ea", // light text
28
+ mist: "#a5b8a8", // dim text
29
+ shadow: "#708373", // very dim
30
+ fang: "#d35f47", // error red
31
+ gold: "#d6b56f", // warning amber
32
+ moss: "#183325", // subtle bg accent
33
+ separator: "#3a5a40", // dim line separator
34
+ };
35
+ // ─── Ring Spinner (growing/shrinking) ───────────────────────────────
36
+ const RING_FRAMES = ["∙", "○", "◎", "●", "◎", "○"];
37
+ function ringColor(elapsedSec) {
38
+ if (elapsedSec >= 45)
39
+ return OURO.fang;
40
+ if (elapsedSec >= 15)
41
+ return OURO.gold;
42
+ return OURO.scale;
43
+ }
44
+ // ─── Header ─────────────────────────────────────────────────────────
45
+ /** Terminal width for content (capped at 200, with 2-char margin) */
46
+ function safeWidth() {
47
+ return Math.min(process.stdout.columns || 80, 200) - 2;
48
+ }
49
+ /** Full terminal width for edge-to-edge elements (separators) */
50
+ function termWidth() {
51
+ return process.stdout.columns || 80;
52
+ }
53
+ function Header({ agentName, model, contextPercent, cwd, resumeInfo }) {
54
+ const showCtx = contextPercent > 0;
55
+ const info = [agentName, model, cwd, showCtx ? `ctx ${contextPercent}%` : ""].filter(Boolean).join(" · ");
56
+ // 3-segment snake: TAIL (fixed) + MIDDLE (stretches) + HEAD (fixed)
57
+ const HEAD1 = " ____";
58
+ const HEAD2 = "____/ O \\___/";
59
+ const HEAD3 = "_________/ \\";
60
+ const TAIL1 = " ";
61
+ const TAIL2 = " __";
62
+ const TAIL3 = "<__";
63
+ const fixedWidth = TAIL1.length + HEAD1.length;
64
+ const maxMiddle = safeWidth() - fixedWidth;
65
+ const middleLen = Math.max(Math.min(Math.max(info.length, 20), maxMiddle), 10);
66
+ const textPad = Math.max(middleLen - info.length, 0);
67
+ const line1 = TAIL1 + " ".repeat(middleLen) + HEAD1;
68
+ const line2 = TAIL2 + "_".repeat(middleLen) + HEAD2;
69
+ const line3text = (info.length > middleLen ? info.slice(0, middleLen) : info) + "_".repeat(textPad);
70
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: line1 }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: line2 }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.scale, children: [TAIL3, (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.glow, children: line3text }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: HEAD3 })] }), resumeInfo ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.teal, children: [" resuming \u00b7 ", resumeInfo.messageCount, " messages \u00b7 last active ", resumeInfo.timeAgo] })) : null] }));
71
+ }
72
+ // ─── Message Rendering ──────────────────────────────────────────────
73
+ // Flow control tools are invisible to the user — they are internal agent mechanics
74
+ const FLOW_CONTROL_TOOLS = new Set(["settle", "ponder", "observe", "rest"]);
75
+ function ToolResultLine({ tc }) {
76
+ const icon = tc.success !== false ? "✓" : "✗";
77
+ const iconColor = tc.success !== false ? OURO.scale : OURO.fang;
78
+ const argColor = tc.success === false ? OURO.fang : OURO.shadow;
79
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: iconColor, children: icon }), " ", (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, children: tc.name }), " ", (0, jsx_runtime_1.jsx)(ink_1.Text, { color: argColor, children: tc.argSummary })] }));
80
+ }
81
+ function MessageBlock({ msg }) {
82
+ if (msg.role === "tool") {
83
+ const visibleCalls = msg.toolCalls?.filter(tc => !FLOW_CONTROL_TOOLS.has(tc.name));
84
+ // Flow control tools produce no visible output at all
85
+ if (!visibleCalls || visibleCalls.length === 0)
86
+ return (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" });
87
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: visibleCalls.map((tc, i) => (0, jsx_runtime_1.jsx)(ToolResultLine, { tc: tc }, i)) }));
88
+ }
89
+ if (msg.role === "user") {
90
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: [msg.content ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, bold: true, children: msg.content }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) })] }));
91
+ }
92
+ if (msg.role === "assistant") {
93
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginBottom: 1, children: msg.content ? (0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: msg.content, maxWidth: safeWidth() }) : null }));
94
+ }
95
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: msg.content }), (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) })] }));
96
+ }
97
+ // ─── Live Area (re-renders) ─────────────────────────────────────────
98
+ function Spinner({ phrasePool, elapsed }) {
99
+ const [frame, setFrame] = (0, react_1.useState)(0);
100
+ const [currentPhrase, setCurrentPhrase] = (0, react_1.useState)(() => phrasePool.length > 0 ? phrasePool[Math.floor(Math.random() * phrasePool.length)] : "");
101
+ // Animate ring frames
102
+ (0, react_1.useEffect)(() => {
103
+ const iv = setInterval(() => setFrame(f => (f + 1) % RING_FRAMES.length), 120);
104
+ return () => clearInterval(iv);
105
+ }, []);
106
+ // Rotate phrases every 3 seconds
107
+ (0, react_1.useEffect)(() => {
108
+ if (phrasePool.length <= 1)
109
+ return;
110
+ const iv = setInterval(() => {
111
+ setCurrentPhrase(phrasePool[Math.floor(Math.random() * phrasePool.length)]);
112
+ }, 3000);
113
+ return () => clearInterval(iv);
114
+ }, [phrasePool]);
115
+ const color = ringColor(elapsed);
116
+ const timeStr = elapsed > 0 ? `${elapsed}s` : "";
117
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: color, children: [RING_FRAMES[frame], " ", timeStr ? (0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.shadow, children: [timeStr, " \u00B7 "] }) : "", currentPhrase] }));
118
+ }
119
+ function formatActiveToolArgs(name, args) {
120
+ switch (name) {
121
+ case "shell":
122
+ return `$ ${args.command ?? "?"}`;
123
+ case "read_file":
124
+ return `path=${args.path ?? "?"}`;
125
+ case "write_file":
126
+ return `path=${args.path ?? "?"}`;
127
+ case "edit_file":
128
+ return args.path ?? "?";
129
+ case "glob":
130
+ return `pattern=${args.pattern ?? "?"}`;
131
+ case "grep":
132
+ return `pattern=${args.pattern ?? "?"} ${args.path ? `path=${args.path}` : ""}`.trim();
133
+ default: {
134
+ // Show first 2 key=value pairs
135
+ const entries = Object.entries(args).slice(0, 2);
136
+ return entries.map(([k, v]) => `${k}=${String(v).slice(0, 50)}`).join(" ");
137
+ }
138
+ }
139
+ }
140
+ function ActiveToolLine({ tool }) {
141
+ // Hide flow control tools from in-progress display
142
+ if (FLOW_CONTROL_TOOLS.has(tool.name))
143
+ return (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" });
144
+ const argStr = formatActiveToolArgs(tool.name, tool.args);
145
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: "∙" }), " ", (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, children: tool.name }), " ", (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: argStr })] }));
146
+ }
147
+ function LiveArea({ live, elapsed }) {
148
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [live.streamingText ? ((0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: live.streamingText, maxWidth: safeWidth() })) : null, live.activeTool ? ((0, jsx_runtime_1.jsx)(ActiveToolLine, { tool: live.activeTool })) : null, live.loading ? ((0, jsx_runtime_1.jsx)(Spinner, { phrasePool: live.spinnerPhrasePool, elapsed: elapsed })) : null, live.errorMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.fang, children: ["✗ ", live.errorMessage] })) : null, live.kickMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.gold, children: ["↻ ", live.kickMessage] })) : null] }));
149
+ }
150
+ // ─── Queued Messages ───────────────────────────────────────────────
151
+ function QueuedMessages({ items }) {
152
+ if (items.length === 0)
153
+ return (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" });
154
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: items.map((text, i) => ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: "\u231B queued: " }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.mist, children: ["\"", text, "\""] })] }, i))) }));
155
+ }
156
+ // ─── Input ──────────────────────────────────────────────────────────
157
+ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agentName, model, onImageMap }) {
158
+ const [input, setInput] = (0, react_1.useState)("");
159
+ const [cursorPos, setCursorPos] = (0, react_1.useState)(0); // cursor position within input
160
+ const [tooltip, setTooltip] = (0, react_1.useState)("");
161
+ const [cursorVisible, setCursorVisible] = (0, react_1.useState)(true);
162
+ const inputRef = (0, react_1.useRef)("");
163
+ const cursorRef = (0, react_1.useRef)(0);
164
+ const historyIdx = (0, react_1.useRef)(-1);
165
+ const savedInput = (0, react_1.useRef)("");
166
+ // Deferred ESC handling: Ink 3.2 fires escape events for ESC prefix of arrow
167
+ // keys (\x1b[D) and Alt+Enter (\x1b\r). We defer ESC actions by 80ms — if
168
+ // another key arrives in that window, it was an escape sequence, not standalone ESC.
169
+ const escTimerRef = (0, react_1.useRef)(null);
170
+ const lastEscTime = (0, react_1.useRef)(0);
171
+ const tooltipTimerRef = (0, react_1.useRef)(null);
172
+ (0, react_1.useEffect)(() => () => {
173
+ if (tooltipTimerRef.current)
174
+ clearTimeout(tooltipTimerRef.current);
175
+ }, []);
176
+ // Helper: update input and cursor together
177
+ const updateInput = (text, pos) => {
178
+ inputRef.current = text;
179
+ cursorRef.current = pos ?? text.length;
180
+ setInput(text);
181
+ setCursorPos(cursorRef.current);
182
+ };
183
+ // Blinking cursor
184
+ (0, react_1.useEffect)(() => {
185
+ const iv = setInterval(() => setCursorVisible(v => !v), 530);
186
+ return () => clearInterval(iv);
187
+ }, []);
188
+ const handleCtrlC = (0, react_1.useCallback)(() => {
189
+ const action = onCtrlC(inputRef.current.length > 0);
190
+ if (action === "clear") {
191
+ inputRef.current = "";
192
+ setInput("");
193
+ setTooltip("");
194
+ setTooltip("");
195
+ }
196
+ else if (action === "warn") {
197
+ // tooltip handled below
198
+ setTooltip("Ctrl-C again to exit");
199
+ if (tooltipTimerRef.current)
200
+ clearTimeout(tooltipTimerRef.current);
201
+ tooltipTimerRef.current = setTimeout(() => setTooltip(""), 3000);
202
+ }
203
+ // "abort" and "exit" are handled by the parent (cli.ts)
204
+ }, [onCtrlC]);
205
+ (0, ink_1.useInput)((inputChar, key) => {
206
+ if (key.ctrl && inputChar === "c") {
207
+ handleCtrlC();
208
+ return;
209
+ }
210
+ // Input is NEVER blocked — user can type while agent responds (TTFA)
211
+ // Any non-Ctrl-C input clears tooltip
212
+ setTooltip("");
213
+ if (key.escape) {
214
+ lastEscTime.current = Date.now();
215
+ // Defer ESC action — if another key arrives within 80ms, this was an escape
216
+ // sequence prefix (arrow key, Alt+Enter), not a standalone Escape press.
217
+ if (escTimerRef.current)
218
+ clearTimeout(escTimerRef.current);
219
+ escTimerRef.current = setTimeout(() => {
220
+ escTimerRef.current = null;
221
+ if (inputRef.current) {
222
+ updateInput("");
223
+ historyIdx.current = -1;
224
+ setTooltip("Esc again to clear");
225
+ if (tooltipTimerRef.current)
226
+ clearTimeout(tooltipTimerRef.current);
227
+ tooltipTimerRef.current = setTimeout(() => setTooltip(""), 2000);
228
+ }
229
+ else if (queuedInputs.length > 0) {
230
+ const items = onPopQueue();
231
+ updateInput(items.join("\n"));
232
+ historyIdx.current = -1;
233
+ }
234
+ else {
235
+ setTooltip("");
236
+ }
237
+ }, 80);
238
+ return;
239
+ }
240
+ // Cancel pending ESC action — this key is part of an escape sequence
241
+ if (escTimerRef.current) {
242
+ clearTimeout(escTimerRef.current);
243
+ escTimerRef.current = null;
244
+ }
245
+ if (key.return) {
246
+ // Alt+Enter: detect via key.meta OR recent ESC (within 50ms — Ink splits \x1b\r)
247
+ const recentEsc = (Date.now() - lastEscTime.current) < 50;
248
+ if (key.meta || key.shift || recentEsc) {
249
+ lastEscTime.current = 0;
250
+ const before = inputRef.current.slice(0, cursorRef.current);
251
+ const after = inputRef.current.slice(cursorRef.current);
252
+ updateInput(before + "\n" + after, cursorRef.current + 1);
253
+ return;
254
+ }
255
+ // Backslash+Enter: insert newline (like Claude Code)
256
+ if (cursorRef.current > 0 && inputRef.current[cursorRef.current - 1] === "\\") {
257
+ const before = inputRef.current.slice(0, cursorRef.current - 1);
258
+ const after = inputRef.current.slice(cursorRef.current);
259
+ updateInput(before + "\n" + after, cursorRef.current);
260
+ return;
261
+ }
262
+ const text = inputRef.current;
263
+ if (text.trim()) {
264
+ const { text: processedText, images } = (0, image_paste_1.processSubmitInput)(text);
265
+ if (images.size > 0 && onImageMap) {
266
+ onImageMap(images);
267
+ }
268
+ onSubmit(images.size > 0 ? processedText : text);
269
+ }
270
+ updateInput("");
271
+ historyIdx.current = -1;
272
+ return;
273
+ }
274
+ if (key.backspace || key.delete) {
275
+ if (cursorRef.current > 0) {
276
+ // Option+Backspace: delete word
277
+ if (key.meta) {
278
+ const before = inputRef.current.slice(0, cursorRef.current);
279
+ const after = inputRef.current.slice(cursorRef.current);
280
+ const wordStart = before.replace(/\s*\S+\s*$/, "");
281
+ updateInput(wordStart + after, wordStart.length);
282
+ }
283
+ else {
284
+ const before = inputRef.current.slice(0, cursorRef.current - 1);
285
+ const after = inputRef.current.slice(cursorRef.current);
286
+ updateInput(before + after, cursorRef.current - 1);
287
+ }
288
+ }
289
+ historyIdx.current = -1;
290
+ return;
291
+ }
292
+ // Left/right arrow: move cursor
293
+ if (key.leftArrow) {
294
+ if (key.meta) {
295
+ // Option+Left: jump to previous word boundary
296
+ const before = inputRef.current.slice(0, cursorRef.current);
297
+ const match = before.match(/(?:^|\s)\S+\s*$/);
298
+ const newPos = match ? cursorRef.current - match[0].length + (match[0].startsWith(" ") ? 1 : 0) : 0;
299
+ cursorRef.current = Math.max(0, newPos);
300
+ setCursorPos(cursorRef.current);
301
+ }
302
+ else {
303
+ cursorRef.current = Math.max(0, cursorRef.current - 1);
304
+ setCursorPos(cursorRef.current);
305
+ }
306
+ return;
307
+ }
308
+ if (key.rightArrow) {
309
+ if (key.meta) {
310
+ // Option+Right: jump to next word boundary
311
+ const after = inputRef.current.slice(cursorRef.current);
312
+ const match = after.match(/^\s*\S+/);
313
+ const newPos = match ? cursorRef.current + match[0].length : inputRef.current.length;
314
+ cursorRef.current = Math.min(inputRef.current.length, newPos);
315
+ setCursorPos(cursorRef.current);
316
+ }
317
+ else {
318
+ cursorRef.current = Math.min(inputRef.current.length, cursorRef.current + 1);
319
+ setCursorPos(cursorRef.current);
320
+ }
321
+ return;
322
+ }
323
+ // Up/Down: queue pop takes priority over history
324
+ if (key.upArrow) {
325
+ // If not already browsing history and queue has items, pop queue into input
326
+ if (historyIdx.current === -1 && queuedInputs.length > 0) {
327
+ const items = onPopQueue();
328
+ updateInput(items.join("\n"));
329
+ return;
330
+ }
331
+ // Otherwise, browse history
332
+ if (history.length > 0) {
333
+ if (historyIdx.current === -1) {
334
+ savedInput.current = inputRef.current;
335
+ historyIdx.current = history.length - 1;
336
+ }
337
+ else if (historyIdx.current > 0) {
338
+ historyIdx.current--;
339
+ }
340
+ updateInput(history[historyIdx.current]);
341
+ }
342
+ return;
343
+ }
344
+ if (key.downArrow) {
345
+ if (historyIdx.current >= 0) {
346
+ if (historyIdx.current < history.length - 1) {
347
+ historyIdx.current++;
348
+ updateInput(history[historyIdx.current]);
349
+ }
350
+ else {
351
+ historyIdx.current = -1;
352
+ updateInput(savedInput.current);
353
+ }
354
+ }
355
+ return;
356
+ }
357
+ // Meta+B / Meta+F: word movement (emacs — Option+Arrow on macOS sends these)
358
+ if (key.meta && inputChar === "b") {
359
+ const before = inputRef.current.slice(0, cursorRef.current);
360
+ const match = before.match(/(?:^|\s)\S+\s*$/);
361
+ cursorRef.current = match ? Math.max(0, cursorRef.current - match[0].length + (match[0][0] === " " ? 1 : 0)) : 0;
362
+ setCursorPos(cursorRef.current);
363
+ return;
364
+ }
365
+ if (key.meta && inputChar === "f") {
366
+ const after = inputRef.current.slice(cursorRef.current);
367
+ const match = after.match(/^\s*\S+/);
368
+ cursorRef.current = match ? Math.min(inputRef.current.length, cursorRef.current + match[0].length) : inputRef.current.length;
369
+ setCursorPos(cursorRef.current);
370
+ return;
371
+ }
372
+ // Meta+D: delete word forward
373
+ if (key.meta && inputChar === "d") {
374
+ const after = inputRef.current.slice(cursorRef.current);
375
+ const match = after.match(/^\s*\S+/);
376
+ if (match) {
377
+ const before = inputRef.current.slice(0, cursorRef.current);
378
+ const rest = after.slice(match[0].length);
379
+ updateInput(before + rest, cursorRef.current);
380
+ }
381
+ return;
382
+ }
383
+ // Ctrl+A / Ctrl+E: home / end
384
+ if (key.ctrl && inputChar === "a") {
385
+ cursorRef.current = 0;
386
+ setCursorPos(0);
387
+ return;
388
+ }
389
+ if (key.ctrl && inputChar === "e") {
390
+ cursorRef.current = inputRef.current.length;
391
+ setCursorPos(inputRef.current.length);
392
+ return;
393
+ }
394
+ // Regular character: insert at cursor position
395
+ if (!key.ctrl && !key.meta && inputChar) {
396
+ const before = inputRef.current.slice(0, cursorRef.current);
397
+ const after = inputRef.current.slice(cursorRef.current);
398
+ updateInput(before + inputChar + after, cursorRef.current + 1);
399
+ historyIdx.current = -1;
400
+ }
401
+ });
402
+ const isMultiline = input.includes("\n");
403
+ const inputLines = input.split("\n");
404
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(termWidth()) }), isMultiline ? ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: inputLines.map((line, i) => ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: i === 0 ? ") " : "· " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: line }), i === inputLines.length - 1 ? (cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { backgroundColor: OURO.scale, color: "#000000", children: " " }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " })) : null] }, i))) })) : ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: ") " }), !input && queuedInputs.length > 0 ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: "Press up to edit queued messages" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(0, cursorPos) }), cursorVisible
405
+ ? (0, jsx_runtime_1.jsx)(ink_1.Text, { backgroundColor: OURO.scale, color: "#000000", children: input[cursorPos] ?? " " })
406
+ : (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input[cursorPos] ?? " " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(cursorPos + 1) })] }))] })), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(termWidth()) }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" ", agentName, model ? ` · ${model}` : "", " \u00B7 /help"] }), (0, jsx_runtime_1.jsx)(ink_1.Box, { flexGrow: 1 }), tooltip ? (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: tooltip }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "opt+enter for newline" })] })] }));
407
+ }
408
+ // ─── Main TUI Component ─────────────────────────────────────────────
409
+ function OuroTui({ agentName, model, completedMessages, inputHistory, queuedInputs, live, elapsedSeconds, contextPercent, onSubmit, onCtrlC, onPopQueue, cwd, resumeInfo, onImageMap, }) {
410
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Static, { items: [{ id: "__header__" }, ...completedMessages], children: (item, index) => {
411
+ if (index === 0) {
412
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginBottom: 2, children: [(0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(Header, { agentName: agentName, model: model, contextPercent: contextPercent, cwd: cwd, resumeInfo: resumeInfo }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, dimColor: true, children: " Ctrl-C twice to exit \u00b7 \u2191\u2193 history \u00b7 Esc clear \u00b7 opt+Enter newline" })] }, "header"));
413
+ }
414
+ return (0, jsx_runtime_1.jsx)(MessageBlock, { msg: item }, item.id);
415
+ } }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, (0, jsx_runtime_1.jsx)(LiveArea, { live: live, elapsed: elapsedSeconds }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, queuedInputs.length > 0 ? (0, jsx_runtime_1.jsx)(QueuedMessages, { items: queuedInputs }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(InputArea, { onSubmit: onSubmit, onCtrlC: onCtrlC, history: inputHistory, queuedInputs: queuedInputs, onPopQueue: onPopQueue, agentName: agentName, model: model, onImageMap: onImageMap })] }));
416
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ /**
3
+ * Imperative snake-themed spinner for the CLI.
4
+ *
5
+ * Writes directly to stderr via setInterval — no React, no Ink.
6
+ * Features:
7
+ * - Ouroboros snake animation frames (serpent eating its own tail)
8
+ * - Elapsed time display
9
+ * - Stall detection with color interpolation: green -> amber (15s) -> red (45s)
10
+ * - Phrase rotation from agent.json phrase pools
11
+ * - Pause/resume for log coordination
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.ImperativeSpinner = void 0;
15
+ const phrases_1 = require("../../mind/phrases");
16
+ const runtime_1 = require("../../nerves/runtime");
17
+ // Ring spinner frames (growing/shrinking — option #6)
18
+ const SNAKE_FRAMES = [
19
+ "\u2219", // ∙ (small dot)
20
+ "\u25CB", // ○ (white circle)
21
+ "\u25CE", // ◎ (bullseye)
22
+ "\u25CF", // ● (black circle)
23
+ "\u25CE", // ◎ (bullseye)
24
+ "\u25CB", // ○ (white circle)
25
+ ];
26
+ // ANSI color helpers — RGB escape sequences
27
+ const RGB_GREEN = "\x1b[38;2;46;204;64m"; // #2ecc40 — ouroboros green
28
+ const RGB_AMBER = "\x1b[38;2;243;156;18m"; // #f39c12
29
+ const RGB_RED = "\x1b[38;2;231;76;60m"; // #e74c3c
30
+ const DIM = "\x1b[2m";
31
+ const RESET = "\x1b[0m";
32
+ function stallColor(seconds) {
33
+ if (seconds >= 45)
34
+ return RGB_RED;
35
+ if (seconds >= 15)
36
+ return RGB_AMBER;
37
+ return RGB_GREEN;
38
+ }
39
+ function formatTime(seconds) {
40
+ const h = Math.floor(seconds / 3600);
41
+ const m = Math.floor((seconds % 3600) / 60);
42
+ const s = seconds % 60;
43
+ if (h > 0)
44
+ return `${h}h${m}m`;
45
+ if (m > 0)
46
+ return `${m}m${s}s`;
47
+ return `${s}s`;
48
+ }
49
+ class ImperativeSpinner {
50
+ frames = SNAKE_FRAMES;
51
+ frameIdx = 0;
52
+ iv = null;
53
+ phraseIv = null;
54
+ msg;
55
+ phrases = null;
56
+ lastPhrase = "";
57
+ stopped = false;
58
+ startTime = 0;
59
+ paused = false;
60
+ constructor(message = "working", phrases) {
61
+ this.msg = message;
62
+ if (phrases && phrases.length > 0)
63
+ this.phrases = phrases;
64
+ }
65
+ start() {
66
+ this.stopped = false;
67
+ this.paused = false;
68
+ this.startTime = Date.now();
69
+ process.stderr.write("\r\x1b[K");
70
+ this.render();
71
+ this.iv = setInterval(() => this.render(), 120);
72
+ if (this.phrases) {
73
+ this.phraseIv = setInterval(() => this.rotatePhrase(), 1500);
74
+ }
75
+ (0, runtime_1.emitNervesEvent)({
76
+ component: "senses",
77
+ event: "senses.spinner_start",
78
+ message: "imperative spinner started",
79
+ meta: { phrase: this.msg },
80
+ });
81
+ }
82
+ render() {
83
+ /* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
84
+ if (this.stopped || this.paused)
85
+ return;
86
+ const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
87
+ const color = stallColor(elapsed);
88
+ const timeStr = formatTime(elapsed);
89
+ const frame = this.frames[this.frameIdx % this.frames.length];
90
+ process.stderr.write(`\r\x1b[K${color}${frame} ${this.msg}${RESET} ${DIM}${timeStr}${RESET} `);
91
+ this.frameIdx++;
92
+ }
93
+ rotatePhrase() {
94
+ /* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
95
+ if (this.stopped)
96
+ return;
97
+ const next = (0, phrases_1.pickPhrase)(this.phrases, this.lastPhrase);
98
+ this.lastPhrase = next;
99
+ this.msg = next;
100
+ }
101
+ /* v8 ignore start -- pause/resume: exercised at runtime via log sink coordination @preserve */
102
+ pause() {
103
+ if (this.stopped)
104
+ return;
105
+ this.paused = true;
106
+ process.stderr.write("\r\x1b[K");
107
+ }
108
+ resume() {
109
+ if (this.stopped)
110
+ return;
111
+ this.paused = false;
112
+ this.render();
113
+ }
114
+ /* v8 ignore stop */
115
+ stop(ok) {
116
+ this.stopped = true;
117
+ if (this.iv) {
118
+ clearInterval(this.iv);
119
+ this.iv = null;
120
+ }
121
+ if (this.phraseIv) {
122
+ clearInterval(this.phraseIv);
123
+ this.phraseIv = null;
124
+ }
125
+ process.stderr.write("\r\x1b[K");
126
+ /* v8 ignore next -- ok parameter currently unused by callers @preserve */
127
+ if (ok)
128
+ process.stderr.write(`\x1b[32m\u2713\x1b[0m ${ok}\n`);
129
+ }
130
+ fail(msg) {
131
+ this.stop();
132
+ process.stderr.write(`\x1b[31m\u2717\x1b[0m ${msg}\n`);
133
+ }
134
+ }
135
+ exports.ImperativeSpinner = ImperativeSpinner;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.EnhancedSpinner = EnhancedSpinner;
37
+ const jsx_runtime_1 = require("react/jsx-runtime");
38
+ const react_1 = __importStar(require("react"));
39
+ const ink_1 = require("ink");
40
+ /**
41
+ * Enhanced spinner with ouroboros/snake-themed animation.
42
+ *
43
+ * Features:
44
+ * - Snake-themed animation frames (serpent eating its own tail)
45
+ * - Elapsed time display
46
+ * - Color interpolation: normal -> amber (>15s) -> red (>45s)
47
+ * - Output token counter during streaming
48
+ * - Phrase rotation (driven externally via props)
49
+ * - Reduced-motion: static text, no animation
50
+ */
51
+ // Ouroboros brand palette
52
+ const OURO_GREEN = "#2ecc40";
53
+ const COLOR_AMBER = "#f39c12";
54
+ const COLOR_RED = "#e74c3c";
55
+ // Ouroboros snake-themed spinner frames
56
+ // The serpent cycles through eating its own tail
57
+ const SNAKE_FRAMES = [
58
+ "\u{1F40D}", // snake emoji
59
+ "\u25E0", // upper half circle
60
+ "\u25D4", // circle with upper right quadrant
61
+ "\u25D1", // circle with right half
62
+ "\u25D5", // circle with all but upper left quadrant
63
+ "\u25E1", // lower half circle
64
+ "\u25CB", // white circle (tail swallowed)
65
+ "\u25CF", // black circle (digesting)
66
+ ];
67
+ // Static indicator for reduced-motion
68
+ const STATIC_INDICATOR = "\u2022"; // bullet
69
+ function formatTime(seconds) {
70
+ const h = Math.floor(seconds / 3600);
71
+ const m = Math.floor((seconds % 3600) / 60);
72
+ const s = seconds % 60;
73
+ if (h > 0)
74
+ return `${h}h${m}m`;
75
+ if (m > 0)
76
+ return `${m}m${s}s`;
77
+ return `${s}s`;
78
+ }
79
+ function stallColor(seconds) {
80
+ if (seconds >= 45)
81
+ return COLOR_RED;
82
+ if (seconds >= 15)
83
+ return COLOR_AMBER;
84
+ return OURO_GREEN;
85
+ }
86
+ function EnhancedSpinner({ elapsedSeconds, phrase, outputTokens, reducedMotion, }) {
87
+ const [frame, setFrame] = (0, react_1.useState)(0);
88
+ // Animate spinner (unless reduced-motion)
89
+ react_1.default.useEffect(() => {
90
+ if (reducedMotion)
91
+ return;
92
+ const iv = setInterval(() => {
93
+ setFrame(f => (f + 1) % SNAKE_FRAMES.length);
94
+ }, 120);
95
+ return () => clearInterval(iv);
96
+ }, [reducedMotion]);
97
+ const color = stallColor(elapsedSeconds);
98
+ const timeStr = formatTime(elapsedSeconds);
99
+ const indicator = reducedMotion ? STATIC_INDICATOR : SNAKE_FRAMES[frame % SNAKE_FRAMES.length];
100
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { color: color, children: [indicator, " "] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: color, children: phrase }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: ` ${timeStr}` }), outputTokens !== undefined ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: ` ${outputTokens} tok` })) : null] }));
101
+ }