@timetotest/cli 0.2.3 → 0.3.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 (200) hide show
  1. package/README.md +49 -40
  2. package/dist/bin/ttt.js +0 -2
  3. package/dist/bin/ttt.js.map +1 -1
  4. package/dist/package.json +13 -6
  5. package/dist/src/commands/chat/ChatApp.js +270 -100
  6. package/dist/src/commands/chat/ChatApp.js.map +1 -1
  7. package/dist/src/commands/chat/components/Banner.js +1 -1
  8. package/dist/src/commands/chat/components/ChatInput.js +97 -36
  9. package/dist/src/commands/chat/components/ChatInput.js.map +1 -1
  10. package/dist/src/commands/chat/components/ChatMessage.js +102 -0
  11. package/dist/src/commands/chat/components/ChatMessage.js.map +1 -0
  12. package/dist/src/commands/chat/components/MessageBubble.js +2 -1
  13. package/dist/src/commands/chat/components/MessageBubble.js.map +1 -1
  14. package/dist/src/commands/chat/components/PermissionPrompt.js +92 -0
  15. package/dist/src/commands/chat/components/PermissionPrompt.js.map +1 -0
  16. package/dist/src/commands/chat/components/StatusIndicator.js +21 -5
  17. package/dist/src/commands/chat/components/StatusIndicator.js.map +1 -1
  18. package/dist/src/commands/chat/components/ToolCallDisplay.js +141 -0
  19. package/dist/src/commands/chat/components/ToolCallDisplay.js.map +1 -0
  20. package/dist/src/commands/chat-ink.js +389 -61
  21. package/dist/src/commands/chat-ink.js.map +1 -1
  22. package/dist/src/commands/login.js +5 -5
  23. package/dist/src/commands/login.js.map +1 -1
  24. package/dist/src/commands/test.js +14 -194
  25. package/dist/src/commands/test.js.map +1 -1
  26. package/dist/src/lib/__tests__/code-mode-integration.test.js +381 -0
  27. package/dist/src/lib/__tests__/code-mode-integration.test.js.map +1 -0
  28. package/dist/src/lib/__tests__/config-manager.test.js +81 -0
  29. package/dist/src/lib/__tests__/config-manager.test.js.map +1 -0
  30. package/dist/src/lib/__tests__/mode-persistence-integration.test.js +75 -0
  31. package/dist/src/lib/__tests__/mode-persistence-integration.test.js.map +1 -0
  32. package/dist/src/lib/__tests__/permission-flow-integration.test.js +145 -0
  33. package/dist/src/lib/__tests__/permission-flow-integration.test.js.map +1 -0
  34. package/dist/src/lib/__tests__/permissions.test.js +132 -0
  35. package/dist/src/lib/__tests__/permissions.test.js.map +1 -0
  36. package/dist/src/lib/agent-orchestrator.js +263 -4
  37. package/dist/src/lib/agent-orchestrator.js.map +1 -1
  38. package/dist/src/lib/config.js +40 -0
  39. package/dist/src/lib/config.js.map +1 -1
  40. package/dist/src/lib/context-compactor.js +310 -0
  41. package/dist/src/lib/context-compactor.js.map +1 -0
  42. package/dist/src/lib/http.js +8 -0
  43. package/dist/src/lib/http.js.map +1 -1
  44. package/dist/src/lib/local-tools/code/__tests__/grep-search.test.js +146 -0
  45. package/dist/src/lib/local-tools/code/__tests__/grep-search.test.js.map +1 -0
  46. package/dist/src/lib/local-tools/code/__tests__/list-directory.test.js +192 -0
  47. package/dist/src/lib/local-tools/code/__tests__/list-directory.test.js.map +1 -0
  48. package/dist/src/lib/local-tools/code/__tests__/read-file.test.js +169 -0
  49. package/dist/src/lib/local-tools/code/__tests__/read-file.test.js.map +1 -0
  50. package/dist/src/lib/local-tools/code/__tests__/run-command.test.js +101 -0
  51. package/dist/src/lib/local-tools/code/__tests__/run-command.test.js.map +1 -0
  52. package/dist/src/lib/local-tools/code/__tests__/search-files.test.js +191 -0
  53. package/dist/src/lib/local-tools/code/__tests__/search-files.test.js.map +1 -0
  54. package/dist/src/lib/local-tools/code/grep-search.js +404 -0
  55. package/dist/src/lib/local-tools/code/grep-search.js.map +1 -0
  56. package/dist/src/lib/local-tools/code/index.js +11 -0
  57. package/dist/src/lib/local-tools/code/index.js.map +1 -0
  58. package/dist/src/lib/local-tools/code/list-directory.js +276 -0
  59. package/dist/src/lib/local-tools/code/list-directory.js.map +1 -0
  60. package/dist/src/lib/local-tools/code/read-file.js +301 -0
  61. package/dist/src/lib/local-tools/code/read-file.js.map +1 -0
  62. package/dist/src/lib/local-tools/code/run-command.js +235 -0
  63. package/dist/src/lib/local-tools/code/run-command.js.map +1 -0
  64. package/dist/src/lib/local-tools/code/search-files.js +297 -0
  65. package/dist/src/lib/local-tools/code/search-files.js.map +1 -0
  66. package/dist/src/lib/local-tools/code/types.js +6 -0
  67. package/dist/src/lib/local-tools/code/types.js.map +1 -0
  68. package/dist/src/lib/local-tools/ui/playwright-mcp.js +1 -1
  69. package/dist/src/lib/permissions.js +94 -0
  70. package/dist/src/lib/permissions.js.map +1 -0
  71. package/dist/src/lib/prompts/builder.js +13 -10
  72. package/dist/src/lib/prompts/builder.js.map +1 -1
  73. package/dist/src/lib/prompts/templates.js +78 -0
  74. package/dist/src/lib/prompts/templates.js.map +1 -1
  75. package/dist/src/lib/session-manager.js.map +1 -1
  76. package/dist/src/lib/testing-mode.js +2 -2
  77. package/dist/src/lib/testing-mode.js.map +1 -1
  78. package/dist/src/lib/tool-executor.js +131 -2
  79. package/dist/src/lib/tool-executor.js.map +1 -1
  80. package/dist/src/lib/tool-registry.js +171 -3
  81. package/dist/src/lib/tool-registry.js.map +1 -1
  82. package/dist/src/lib/tool-result-pruner.js +4 -4
  83. package/dist/src/lib/tool-result-pruner.js.map +1 -1
  84. package/dist/src/lib/tui/ink/components/AppFrame.js +17 -0
  85. package/dist/src/lib/tui/ink/components/AppFrame.js.map +1 -0
  86. package/dist/src/lib/tui/ink/components/CommandPalette.js +24 -0
  87. package/dist/src/lib/tui/ink/components/CommandPalette.js.map +1 -0
  88. package/dist/src/lib/tui/ink/components/Pill.js +19 -0
  89. package/dist/src/lib/tui/ink/components/Pill.js.map +1 -0
  90. package/dist/src/lib/tui/ink/components/TimetoTestLogo.js +30 -0
  91. package/dist/src/lib/tui/ink/components/TimetoTestLogo.js.map +1 -0
  92. package/dist/src/lib/tui/ink/theme.js +28 -0
  93. package/dist/src/lib/tui/ink/theme.js.map +1 -0
  94. package/dist/src/lib/tui/interactive-chat.js +35 -35
  95. package/dist/src/lib/tui/interactive-chat.js.map +1 -1
  96. package/dist/src/lib/tui/print.js +18 -18
  97. package/dist/src/lib/tui/print.js.map +1 -1
  98. package/dist/src/lib/tui/prompt.js +3 -3
  99. package/dist/src/lib/tui/prompt.js.map +1 -1
  100. package/dist/src/lib/tui/status.js +1 -1
  101. package/dist/src/lib/tui/status.js.map +1 -1
  102. package/dist/src/lib/update.js +10 -10
  103. package/dist/src/lib/update.js.map +1 -1
  104. package/package.json +13 -6
  105. package/dist/src/commands/ask/AskApp.js +0 -121
  106. package/dist/src/commands/ask/AskApp.js.map +0 -1
  107. package/dist/src/commands/ask/components/AssistantResponse.js +0 -31
  108. package/dist/src/commands/ask/components/AssistantResponse.js.map +0 -1
  109. package/dist/src/commands/ask/components/Banner.js +0 -15
  110. package/dist/src/commands/ask/components/Banner.js.map +0 -1
  111. package/dist/src/commands/ask/components/ChatInput.js +0 -93
  112. package/dist/src/commands/ask/components/ChatInput.js.map +0 -1
  113. package/dist/src/commands/ask/components/Divider.js +0 -17
  114. package/dist/src/commands/ask/components/Divider.js.map +0 -1
  115. package/dist/src/commands/ask/components/IntroTips.js +0 -19
  116. package/dist/src/commands/ask/components/IntroTips.js.map +0 -1
  117. package/dist/src/commands/ask/components/MessageBubble.js +0 -47
  118. package/dist/src/commands/ask/components/MessageBubble.js.map +0 -1
  119. package/dist/src/commands/ask/components/SessionInfo.js +0 -20
  120. package/dist/src/commands/ask/components/SessionInfo.js.map +0 -1
  121. package/dist/src/commands/ask/components/StatusIndicator.js +0 -67
  122. package/dist/src/commands/ask/components/StatusIndicator.js.map +0 -1
  123. package/dist/src/commands/ask-ink.js +0 -380
  124. package/dist/src/commands/ask-ink.js.map +0 -1
  125. package/dist/src/commands/ask.js +0 -991
  126. package/dist/src/commands/ask.js.map +0 -1
  127. package/dist/src/commands/chat/components/Divider.js +0 -7
  128. package/dist/src/commands/chat/components/Divider.js.map +0 -1
  129. package/dist/src/commands/chat/components/SessionInfo.js +0 -11
  130. package/dist/src/commands/chat/components/SessionInfo.js.map +0 -1
  131. package/dist/src/commands/chat.js +0 -82
  132. package/dist/src/commands/chat.js.map +0 -1
  133. package/dist/src/commands/start-test.js +0 -119
  134. package/dist/src/commands/start-test.js.map +0 -1
  135. package/dist/src/commands/stream.js +0 -17
  136. package/dist/src/commands/stream.js.map +0 -1
  137. package/dist/src/lib/legacy-chat-runner.js +0 -37
  138. package/dist/src/lib/legacy-chat-runner.js.map +0 -1
  139. package/dist/src/lib/local-tools/ui/click-element.js +0 -105
  140. package/dist/src/lib/local-tools/ui/click-element.js.map +0 -1
  141. package/dist/src/lib/local-tools/ui/dom-rag.js +0 -201
  142. package/dist/src/lib/local-tools/ui/dom-rag.js.map +0 -1
  143. package/dist/src/lib/local-tools/ui/find-element.js +0 -31
  144. package/dist/src/lib/local-tools/ui/find-element.js.map +0 -1
  145. package/dist/src/lib/local-tools/ui/hover-element.js +0 -94
  146. package/dist/src/lib/local-tools/ui/hover-element.js.map +0 -1
  147. package/dist/src/lib/local-tools/ui/manage-tab.js +0 -65
  148. package/dist/src/lib/local-tools/ui/manage-tab.js.map +0 -1
  149. package/dist/src/lib/local-tools/ui/navigate.js +0 -35
  150. package/dist/src/lib/local-tools/ui/navigate.js.map +0 -1
  151. package/dist/src/lib/local-tools/ui/page-discovery.js +0 -32
  152. package/dist/src/lib/local-tools/ui/page-discovery.js.map +0 -1
  153. package/dist/src/lib/local-tools/ui/screenshot.js +0 -19
  154. package/dist/src/lib/local-tools/ui/screenshot.js.map +0 -1
  155. package/dist/src/lib/local-tools/ui/search-interactive-elements.js +0 -18
  156. package/dist/src/lib/local-tools/ui/search-interactive-elements.js.map +0 -1
  157. package/dist/src/lib/local-tools/ui/selector-resolver.js +0 -153
  158. package/dist/src/lib/local-tools/ui/selector-resolver.js.map +0 -1
  159. package/dist/src/lib/local-tools/ui/type-text.js +0 -40
  160. package/dist/src/lib/local-tools/ui/type-text.js.map +0 -1
  161. package/dist/src/lib/tui/components/AskIntro.js +0 -6
  162. package/dist/src/lib/tui/components/AskIntro.js.map +0 -1
  163. package/dist/src/lib/tui/components/Banner.js +0 -15
  164. package/dist/src/lib/tui/components/Banner.js.map +0 -1
  165. package/dist/src/lib/tui/components/Divider.js +0 -17
  166. package/dist/src/lib/tui/components/Divider.js.map +0 -1
  167. package/dist/src/lib/tui/components/EventLine.js +0 -110
  168. package/dist/src/lib/tui/components/EventLine.js.map +0 -1
  169. package/dist/src/lib/tui/components/Header.js +0 -15
  170. package/dist/src/lib/tui/components/Header.js.map +0 -1
  171. package/dist/src/lib/tui/components/InputBox.js +0 -9
  172. package/dist/src/lib/tui/components/InputBox.js.map +0 -1
  173. package/dist/src/lib/tui/components/Mapping.js +0 -8
  174. package/dist/src/lib/tui/components/Mapping.js.map +0 -1
  175. package/dist/src/lib/tui/components/ProjectList.js +0 -6
  176. package/dist/src/lib/tui/components/ProjectList.js.map +0 -1
  177. package/dist/src/lib/tui/components/Spinner.js +0 -20
  178. package/dist/src/lib/tui/components/Spinner.js.map +0 -1
  179. package/dist/src/lib/tui/components/StatusBanner.js +0 -12
  180. package/dist/src/lib/tui/components/StatusBanner.js.map +0 -1
  181. package/dist/src/lib/tui/components/StatusBar.js +0 -11
  182. package/dist/src/lib/tui/components/StatusBar.js.map +0 -1
  183. package/dist/src/lib/tui/components/UserBubble.js +0 -6
  184. package/dist/src/lib/tui/components/UserBubble.js.map +0 -1
  185. package/dist/src/lib/tui/components/index.js +0 -16
  186. package/dist/src/lib/tui/components/index.js.map +0 -1
  187. package/dist/src/lib/tui/ink-print.js +0 -41
  188. package/dist/src/lib/tui/ink-print.js.map +0 -1
  189. package/dist/src/test-agent-flow.js +0 -148
  190. package/dist/src/test-agent-flow.js.map +0 -1
  191. package/dist/src/test-browser-session.js +0 -152
  192. package/dist/src/test-browser-session.js.map +0 -1
  193. package/dist/src/test-browser-snapshot.js +0 -187
  194. package/dist/src/test-browser-snapshot.js.map +0 -1
  195. package/dist/src/test-snapshot-detailed.js +0 -219
  196. package/dist/src/test-snapshot-detailed.js.map +0 -1
  197. package/dist/src/test-snapshot-simple.js +0 -85
  198. package/dist/src/test-snapshot-simple.js.map +0 -1
  199. package/dist/src/test-snapshot-tabs.js +0 -110
  200. package/dist/src/test-snapshot-tabs.js.map +0 -1
package/README.md CHANGED
@@ -1,66 +1,75 @@
1
- # Time to Test CLI
1
+ # TimetoTest CLI
2
2
 
3
- Transform your testing workflow with an agent that understands your application.
3
+ **Ship Faster with AI Agents That Don't Flake**
4
4
 
5
- ## Why Time to Test CLI?
5
+ Give our agent a URL and a goal. It plans, executes, and self-heals end-to-end tests in an isolated environment. Built for developers and QA engineers who demand verifiable evidence, not black-box magic.
6
6
 
7
- - Test without writing code: describe scenarios in plain English.
8
- - Local-first: runs on your machine for fast, private feedback.
9
- - Intelligent discovery: finds pages, endpoints, and flows to expand coverage.
10
- - Clear results: human-readable reports that explain what passed and why failures happened.
7
+ ## Capabilities
11
8
 
12
- ## How it works
9
+ - **UI Testing** — Real browser automation with semantic DOM understanding
10
+ - **API Testing** — Validate endpoints, schemas, and response data
11
+ - **Code Analysis** — Local code analysis for bug finding and quality checks
12
+ - **End-to-End** — Complete user journey validation
13
+ - **Regression** — Catch breaking changes before production
13
14
 
14
- The CLI launches an AI testing agent that plans and executes tests based on your prompts and your app’s behavior.
15
-
16
- - Local mode (default): runs browser automation and API checks on your machine; ideal for localhost and PR previews.
17
- - Cloud mode: optional remote execution without localhost access; better for prod/staging where local access isn’t possible.
18
- - Post-run: you get an actionable summary and a report capturing steps, assertions, and failures.
19
-
20
- ## Quick start
15
+ ## Quick Start
21
16
 
22
17
  ```bash
23
18
  npm install -g @timetotest/cli
24
19
 
25
- # Start an interactive local session (default)
20
+ # Start an interactive session
26
21
  ttt
27
22
 
28
- # Run a cloud test with a prompt (automatically handles tunnels & project context)
29
- ttt test "Smoke test the login flow" --url http://localhost:3000
23
+ # Run a test with a prompt
24
+ ttt test "Verify the login flow works" --url http://localhost:3000
30
25
  ```
31
26
 
32
- ## What makes this different
27
+ ## How It Works
33
28
 
34
- - Natural language over boilerplate tests.
35
- - The agent explores and adapts to your app instead of running only predefined scripts.
36
- - Zero config to test localhost; minimal friction to get useful feedback.
29
+ 1. **Describe Your Test** Write your testing goal in plain English. No scripts, no complex selectors.
30
+ 2. **Smart Test Planning** The agent analyzes your application and builds a comprehensive test plan.
31
+ 3. **Automated Execution** Watch the agent execute in a real browser, interacting exactly like a human.
32
+ 4. **Actionable Results** — Get clear reports with screenshots, video logs, and step-by-step diagnostics.
37
33
 
38
- ## Privacy & security
34
+ ## Technical Excellence
39
35
 
40
- - Local mode keeps execution and data on your machine.
41
- - Authentication uses secure tokens; you control what the agent can access.
36
+ - **Semantic DOM Parsing** Understands context, not just pixels. No brittle visual-only testing.
37
+ - **LLM-Powered Planning** Decomposes complex flows into atomic, verifiable actions.
38
+ - **Self-Healing Tests** — Adapts to UI changes automatically. Stop maintaining broken selectors.
39
+ - **Isolated Execution** — Tests run in ephemeral sandboxes destroyed after every run.
40
+ - **Evidence-Based Reports** — Screenshots, video logs, and step-by-step diagnostics for every test.
42
41
 
43
- ## Requirements
42
+ ## Use Cases
44
43
 
45
- - Node.js 18+
46
- - macOS, Linux, or Windows
47
- - Chrome/Chromium available for UI testing
44
+ - **Regression Testing** — Automated suites that run on every deploy
45
+ - **E2E Flow Validation** — Validate complete user journeys without writing test scripts
46
+ - **API Contract Testing** Automatic schema validation and edge case handling
47
+ - **Sprint QA Automation** — Ship faster by automating QA within your sprint cycles
48
+ - **Bug Reproduction** — Turn vague bug reports into actionable test cases with evidence
48
49
 
49
- ## Getting help
50
+ ## Code Mode
50
51
 
51
- - Docs and updates: https://timetotest.tech
52
- - Issues and support: use the support links on the website
52
+ Analyze your local codebase for bugs, run linters, and execute tests — no GitHub integration required.
53
53
 
54
- ## Social callouts
54
+ ```bash
55
+ # Start in code mode
56
+ ttt --mode code
55
57
 
56
- If this helps you ship faster, please ⭐ star the repo and share it.
58
+ # Example prompts
59
+ > "Find potential null pointer issues in src/api/"
60
+ > "Run ESLint and fix the errors"
61
+ > "Find and run the auth tests"
62
+ ```
63
+
64
+ ## Requirements
57
65
 
58
- Tweet about it: https://twitter.com/intent/tweet?text=I%20just%20tried%20Time%20to%20Test%20CLI%20%E2%80%94%20AI-powered%20tests%20from%20natural%20language.%20Zero%20config%20for%20localhost!&url=https://timetotest.tech
66
+ - Node.js 18+
67
+ - macOS, Linux, or Windows
68
+ - Chrome/Chromium for UI testing
59
69
 
60
- ## Who it’s for
70
+ ## Links
61
71
 
62
- Built for:
72
+ - Website: https://timetotest.tech
73
+ - Docs: https://timetotest.tech/docs
63
74
 
64
- - Developers who want smoke tests without writing test code
65
- - Startup teams needing fast feedback on PRs
66
- - QA engineers exploring flows rapidly in staging
75
+ No credit card required Free tier forever
package/dist/bin/ttt.js CHANGED
@@ -5,7 +5,6 @@ import { login } from "../src/commands/login.js";
5
5
  import { test } from "../src/commands/test.js";
6
6
  import { status } from "../src/commands/status.js";
7
7
  import { restart } from "../src/commands/restart.js";
8
- import { stream } from "../src/commands/stream.js";
9
8
  import { report } from "../src/commands/report.js";
10
9
  import { share } from "../src/commands/share.js";
11
10
  import { applyStyledHelpRecursively } from "../src/lib/help.js";
@@ -18,7 +17,6 @@ program.addCommand(login);
18
17
  program.addCommand(test);
19
18
  program.addCommand(status);
20
19
  program.addCommand(restart);
21
- program.addCommand(stream);
22
20
  program.addCommand(report);
23
21
  program.addCommand(share);
24
22
  program.addCommand(chat);
@@ -1 +1 @@
1
- {"version":3,"file":"ttt.js","sourceRoot":"","sources":["../../bin/ttt.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAClC,OAAO,WAAW,MAAM,iBAAiB,CAAC,OAAM,IAAI,EAAE,MAAM,EAAC,CAAC;AAC9D,OAAO,EAAC,KAAK,EAAC,MAAM,0BAA0B,CAAC;AAC/C,OAAO,EAAC,IAAI,EAAC,MAAM,yBAAyB,CAAC;AAC7C,OAAO,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AACjD,OAAO,EAAC,OAAO,EAAC,MAAM,4BAA4B,CAAC;AACnD,OAAO,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AACjD,OAAO,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AACjD,OAAO,EAAC,KAAK,EAAC,MAAM,0BAA0B,CAAC;AAG/C,OAAO,EAAC,0BAA0B,EAAC,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAC,0BAA0B,EAAC,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAC,OAAO,IAAI,IAAI,EAAC,MAAM,6BAA6B,CAAC;AAE5D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAC9B,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC;AAC1D,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAExE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAC1B,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AACzB,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3B,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AAC5B,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3B,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3B,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAE1B,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAEzB,0BAA0B,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAE3C,MAAM,0BAA0B,EAAE,CAAC;AACnC,gEAAgE;AAChE,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AACD,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"ttt.js","sourceRoot":"","sources":["../../bin/ttt.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAClC,OAAO,WAAW,MAAM,iBAAiB,CAAC,OAAM,IAAI,EAAE,MAAM,EAAC,CAAC;AAC9D,OAAO,EAAC,KAAK,EAAC,MAAM,0BAA0B,CAAC;AAC/C,OAAO,EAAC,IAAI,EAAC,MAAM,yBAAyB,CAAC;AAC7C,OAAO,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AACjD,OAAO,EAAC,OAAO,EAAC,MAAM,4BAA4B,CAAC;AACnD,OAAO,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AACjD,OAAO,EAAC,KAAK,EAAC,MAAM,0BAA0B,CAAC;AAC/C,OAAO,EAAC,0BAA0B,EAAC,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAC,0BAA0B,EAAC,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAC,OAAO,IAAI,IAAI,EAAC,MAAM,6BAA6B,CAAC;AAE5D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAC9B,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC;AAC1D,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAExE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAC1B,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AACzB,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3B,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AAC5B,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3B,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAC1B,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAEzB,0BAA0B,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAE3C,MAAM,0BAA0B,EAAE,CAAC;AACnC,gEAAgE;AAChE,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AACD,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timetotest/cli",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Time to Test CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,15 +12,20 @@
12
12
  "LICENSE"
13
13
  ],
14
14
  "scripts": {
15
- "dev": "node dist/bin/ttt.js",
15
+ "dev": "npm run build && node dist/bin/ttt.js",
16
16
  "build": "tsc --project tsconfig.json",
17
17
  "clean": "rm -rf dist",
18
- "prepublishOnly": "npm run build | cat",
18
+ "prepublishOnly": "npm run clean && npm run build",
19
19
  "version:patch": "npm version patch -m 'chore(release): %s'",
20
20
  "version:minor": "npm version minor -m 'chore(release): %s'",
21
21
  "version:major": "npm version major -m 'chore(release): %s'",
22
- "release": "npm run build && npm publish --access public",
23
- "upgrade-and-push": "npm run version:patch && git push && git push --tags && npm run release"
22
+ "version:dev": "npm version prerelease --preid dev -m 'chore(release): %s'",
23
+ "publish": "node scripts/publish.mjs",
24
+ "publish:dev": "node scripts/publish.mjs --channel dev",
25
+ "publish:prod": "node scripts/publish.mjs --channel prod",
26
+ "upgrade-and-push": "npm run version:patch && git push && git push --tags && npm run publish",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest"
24
29
  },
25
30
  "publishConfig": {
26
31
  "access": "public"
@@ -51,6 +56,7 @@
51
56
  "react": "^19.2.0",
52
57
  "readline": "^1.3.0",
53
58
  "socket.io-client": "^4.7.5",
59
+ "tiktoken": "^1.0.17",
54
60
  "yaml": "^2.8.1"
55
61
  },
56
62
  "devDependencies": {
@@ -59,6 +65,7 @@
59
65
  "@types/inquirer": "^9.0.9",
60
66
  "@types/node": "^20.11.19",
61
67
  "@types/react": "^19.2.2",
62
- "typescript": "^5.5.4"
68
+ "typescript": "^5.5.4",
69
+ "vitest": "^1.6.0"
63
70
  }
64
71
  }
@@ -1,78 +1,208 @@
1
- import React, { useState, useEffect } from "react";
2
- import { Box, Text, useApp, Static } from "ink";
1
+ import React, { useEffect, useMemo, useRef, useState } from "react";
2
+ import { Box, Text, useApp, useInput, useStdout } from "ink";
3
3
  import os from "node:os";
4
- import { Banner } from "./components/Banner.js";
5
4
  import { ChatInput } from "./components/ChatInput.js";
6
- import { MessageBubble } from "./components/MessageBubble.js";
7
- import { AssistantResponse } from "./components/AssistantResponse.js";
8
5
  import { StatusIndicator } from "./components/StatusIndicator.js";
9
- const DEFAULT_COMMANDS = [
10
- {
11
- name: "help",
12
- description: "Show available commands",
13
- value: "/help",
14
- label: "/help",
15
- },
16
- {
17
- name: "clear",
18
- description: "Clear chat history",
19
- value: "/clear",
20
- label: "/clear",
21
- },
22
- {
23
- name: "reset",
24
- description: "Restart the session",
25
- value: "/reset",
26
- label: "/reset",
27
- },
28
- {
29
- name: "config",
30
- description: "Show current configuration",
31
- value: "/config",
32
- label: "/config",
33
- },
34
- {
35
- name: "mode",
36
- description: "Switch between local/cloud mode",
37
- value: "/mode",
38
- label: "/mode",
39
- },
40
- ];
41
- export const ChatApp = ({ user, context, slashCommands, onUserMessage, onSlashCommand, onExit, onCancel, initialMessages = [], }) => {
42
- // Merge default commands with passed commands
43
- const allSlashCommands = [...DEFAULT_COMMANDS, ...slashCommands];
6
+ import { PermissionPrompt } from "./components/PermissionPrompt.js";
7
+ import { ToolCallDisplay } from "./components/ToolCallDisplay.js";
8
+ import { TESTING_MODES } from "../../lib/testing-mode.js";
9
+ import { TimetoTestLogo } from "../../lib/tui/ink/components/TimetoTestLogo.js";
10
+ import { ChatMessage } from "./components/ChatMessage.js";
11
+ import { CommandPalette, } from "../../lib/tui/ink/components/CommandPalette.js";
12
+ export const ChatApp = ({ user, context, slashCommands, onUserMessage, onSlashCommand, onSetTestingMode, onExit, onCancel, initialMessages = [], initialInput, }) => {
13
+ // Single source of truth: `slashCommands` comes from `chat-ink.ts`.
14
+ const allSlashCommands = slashCommands;
44
15
  const { exit } = useApp();
45
- // Initialize messages with banner if empty
46
- const [messages, setMessages] = useState(() => {
47
- if (initialMessages.length > 0)
48
- return initialMessages;
49
- return [{
50
- id: "banner",
51
- type: "banner",
52
- content: "banner"
53
- }];
54
- });
16
+ const { stdout } = useStdout();
17
+ const [messages, setMessages] = useState(() => initialMessages.length > 0 ? initialMessages : []);
55
18
  const [status, setStatus] = useState(null);
56
19
  const [inputDisabled, setInputDisabled] = useState(false);
57
20
  const [chatContext, setChatContext] = useState(context);
21
+ const [inputValue, setInputValue] = useState(initialInput ?? "");
22
+ const inputHistoryRef = useRef([]);
23
+ const historyIndexRef = useRef(-1);
24
+ const [showSystem, setShowSystem] = useState(true);
25
+ const [paletteOpen, setPaletteOpen] = useState(false);
26
+ const [paletteQuery, setPaletteQuery] = useState("");
27
+ const [paletteIndex, setPaletteIndex] = useState(0);
28
+ // Permission prompt state
29
+ const [permissionRequest, setPermissionRequest] = useState(null);
30
+ const permissionResolverRef = useRef(null);
31
+ const paletteItems = useMemo(() => {
32
+ const base = [
33
+ { id: "help", title: "Help", hint: "/help" },
34
+ { id: "progress", title: "Progress", hint: "/progress" },
35
+ { id: "session", title: "Session info", hint: "/session" },
36
+ { id: "bugs", title: "List Bugs", hint: "/bugs" },
37
+ { id: "report", title: "Generate Report", hint: "/report" },
38
+ { id: "screenshot", title: "Take Screenshot", hint: "/screenshot" },
39
+ { id: "scan", title: "Scan Page", hint: "/scan" },
40
+ { id: "where", title: "Where am I?", hint: "/where" },
41
+ {
42
+ id: "toggle_details",
43
+ title: showSystem ? "Hide details" : "Show details",
44
+ hint: "toggle tool/system messages",
45
+ },
46
+ { id: "clear", title: "Clear transcript", hint: "ctrl+l" },
47
+ { id: "exit_session", title: "Exit", hint: "ctrl+q" },
48
+ ];
49
+ const q = paletteQuery.trim().toLowerCase();
50
+ if (!q)
51
+ return base;
52
+ return base.filter((i) => i.title.toLowerCase().includes(q) ||
53
+ (i.hint || "").toLowerCase().includes(q));
54
+ }, [paletteQuery, showSystem]);
55
+ const runPaletteAction = async (id) => {
56
+ if (id === "help")
57
+ await onSlashCommand("help", []);
58
+ else if (id === "progress")
59
+ await onSlashCommand("progress", []);
60
+ else if (id === "bugs")
61
+ await onSlashCommand("bugs", []);
62
+ else if (id === "report")
63
+ await onSlashCommand("report", []);
64
+ else if (id === "screenshot")
65
+ await onSlashCommand("screenshot", []);
66
+ else if (id === "scan")
67
+ await onSlashCommand("scan", []);
68
+ else if (id === "where")
69
+ await onSlashCommand("where", []);
70
+ else if (id === "session")
71
+ await onSlashCommand("session", []);
72
+ else if (id === "toggle_details")
73
+ setShowSystem((v) => !v);
74
+ else if (id === "clear")
75
+ setMessages([]);
76
+ else if (id === "exit_session") {
77
+ exit();
78
+ onExit();
79
+ }
80
+ };
81
+ const toggleTestingMode = () => {
82
+ const current = chatContext.testingMode;
83
+ const idx = TESTING_MODES.indexOf(current);
84
+ const next = TESTING_MODES[(idx + 1) % TESTING_MODES.length] ?? "ui";
85
+ setChatContext((prev) => ({ ...prev, testingMode: next }));
86
+ onSetTestingMode?.(next);
87
+ };
88
+ useInput((input, key) => {
89
+ // Global keybinds (avoid fighting with input)
90
+ if (key.ctrl && input === "l") {
91
+ setMessages([]);
92
+ return;
93
+ }
94
+ if (key.ctrl && input === "q") {
95
+ exit();
96
+ onExit();
97
+ return;
98
+ }
99
+ if (key.ctrl && input === "k") {
100
+ setPaletteOpen(true);
101
+ setPaletteQuery("");
102
+ setPaletteIndex(0);
103
+ return;
104
+ }
105
+ if (!paletteOpen)
106
+ return;
107
+ // Palette input handling
108
+ if (key.escape) {
109
+ setPaletteOpen(false);
110
+ return;
111
+ }
112
+ if (key.upArrow) {
113
+ setPaletteIndex((i) => Math.max(0, i - 1));
114
+ return;
115
+ }
116
+ if (key.downArrow) {
117
+ setPaletteIndex((i) => Math.min(paletteItems.length - 1, i + 1));
118
+ return;
119
+ }
120
+ if (key.backspace || key.delete) {
121
+ setPaletteQuery((q) => q.slice(0, -1));
122
+ return;
123
+ }
124
+ if (key.return) {
125
+ const selected = paletteItems[paletteIndex];
126
+ if (selected) {
127
+ setPaletteOpen(false);
128
+ void runPaletteAction(selected.id);
129
+ }
130
+ return;
131
+ }
132
+ if (input && !key.ctrl && !key.meta) {
133
+ setPaletteQuery((q) => q + input);
134
+ setPaletteIndex(0);
135
+ }
136
+ });
58
137
  // Public methods to update UI from parent
59
138
  useEffect(() => {
60
139
  // Store reference for external updates
61
140
  globalThis.__chatAppInterface = {
62
141
  addMessage: (message) => {
63
- setMessages((prev) => [...prev, message]);
142
+ try {
143
+ setMessages((prev) => [...prev, message]);
144
+ }
145
+ catch { }
64
146
  },
65
147
  setStatus: (statusMsg) => {
66
- setStatus(statusMsg);
148
+ try {
149
+ setStatus(statusMsg);
150
+ }
151
+ catch { }
67
152
  },
68
153
  clearStatus: () => {
69
- setStatus(null);
154
+ try {
155
+ setStatus(null);
156
+ }
157
+ catch { }
70
158
  },
71
159
  setInputDisabled: (disabled) => {
72
- setInputDisabled(disabled);
160
+ try {
161
+ setInputDisabled(disabled);
162
+ }
163
+ catch { }
73
164
  },
74
165
  updateContext: (newContext) => {
75
- setChatContext((prev) => ({ ...prev, ...newContext }));
166
+ try {
167
+ setChatContext((prev) => ({ ...prev, ...newContext }));
168
+ }
169
+ catch { }
170
+ },
171
+ // Update the last tool message with result
172
+ updateLastToolMessage: (update) => {
173
+ try {
174
+ setMessages((prev) => {
175
+ // Find the last tool message matching this tool (iterate backwards)
176
+ let lastIndex = -1;
177
+ for (let i = prev.length - 1; i >= 0; i--) {
178
+ if (prev[i].metadata?.isTool &&
179
+ prev[i].metadata?.toolName === update.toolName) {
180
+ lastIndex = i;
181
+ break;
182
+ }
183
+ }
184
+ if (lastIndex === -1)
185
+ return prev;
186
+ const updated = [...prev];
187
+ updated[lastIndex] = {
188
+ ...updated[lastIndex],
189
+ metadata: {
190
+ ...updated[lastIndex].metadata,
191
+ toolResult: update.result,
192
+ isLoading: update.isLoading,
193
+ },
194
+ };
195
+ return updated;
196
+ });
197
+ }
198
+ catch { }
199
+ },
200
+ // Permission prompt handler - returns a promise that resolves with user's response
201
+ promptPermission: (request) => {
202
+ return new Promise((resolve) => {
203
+ permissionResolverRef.current = resolve;
204
+ setPermissionRequest(request);
205
+ });
76
206
  },
77
207
  };
78
208
  return () => {
@@ -89,25 +219,32 @@ export const ChatApp = ({ user, context, slashCommands, onUserMessage, onSlashCo
89
219
  // setShowIntro(false);
90
220
  // }
91
221
  // Check for exit commands
92
- if (normalized === "exit" || normalized === "quit") {
222
+ if (normalized === "exit") {
93
223
  exit();
94
224
  onExit();
95
225
  return;
96
226
  }
97
227
  // Check for slash commands
98
228
  if (trimmed.startsWith("/")) {
99
- const [cmd, ...args] = trimmed.slice(1).split(/\s+/);
229
+ const parts = trimmed.slice(1).split(/\s+/);
230
+ const cmd = parts[0];
231
+ const args = parts.slice(1);
232
+ if (!cmd)
233
+ return; // Ignore lone "/"
100
234
  // Handle client-side commands
101
235
  if (cmd === "clear") {
102
236
  setMessages([]);
103
237
  return;
104
238
  }
105
239
  if (cmd === "config") {
106
- setMessages(prev => [...prev, {
240
+ setMessages((prev) => [
241
+ ...prev,
242
+ {
107
243
  id: Date.now().toString(),
108
244
  type: "system",
109
- content: `Current Configuration:\nUser: ${user}\nMode: ${chatContext.mode}\nSession ID: ${chatContext.sessionId}`
110
- }]);
245
+ content: `Current Configuration:\nUser: ${user}\nMode: ${chatContext.mode}\nSession ID: ${chatContext.sessionId}`,
246
+ },
247
+ ]);
111
248
  return;
112
249
  }
113
250
  const result = await onSlashCommand(cmd, args);
@@ -124,13 +261,21 @@ export const ChatApp = ({ user, context, slashCommands, onUserMessage, onSlashCo
124
261
  content: trimmed,
125
262
  };
126
263
  setMessages((prev) => [...prev, userMessage]);
264
+ inputHistoryRef.current.push(trimmed);
265
+ historyIndexRef.current = inputHistoryRef.current.length;
127
266
  await onUserMessage(trimmed);
128
267
  };
129
268
  const handleCancel = () => {
130
- console.log("Cancelled — the assistant has stopped. Type whenever you're ready.");
131
269
  setInputDisabled(false); // Re-enable input after cancellation
132
270
  onCancel?.();
133
271
  };
272
+ const handlePermissionResponse = (response) => {
273
+ if (permissionResolverRef.current) {
274
+ permissionResolverRef.current(response);
275
+ permissionResolverRef.current = null;
276
+ }
277
+ setPermissionRequest(null);
278
+ };
134
279
  const getSuggestions = (input) => {
135
280
  if (!input.startsWith("/"))
136
281
  return [];
@@ -148,44 +293,69 @@ export const ChatApp = ({ user, context, slashCommands, onUserMessage, onSlashCo
148
293
  const displayPath = workspacePath.startsWith(homeDir)
149
294
  ? `~${workspacePath.slice(homeDir.length)}`
150
295
  : workspacePath;
151
- const statusLine = `path: ${displayPath} · mode: ${chatContext.mode}`;
152
- const footerLines = ["Press ESC to stop"];
153
- return (React.createElement(Box, { flexDirection: "column" },
154
- React.createElement(Static, { items: messages }, (message, index) => {
155
- if (message.type === "user") {
156
- return (React.createElement(Box, { key: message.id },
157
- React.createElement(MessageBubble, { message: message.content })));
158
- }
159
- else if (message.type === "assistant") {
160
- return (React.createElement(Box, { key: message.id, paddingX: 1 },
161
- React.createElement(AssistantResponse, { content: message.content, isError: message.isError })));
162
- }
163
- else if (message.type === "system") {
164
- return (React.createElement(Box, { key: message.id, marginTop: index === 0 ? 0 : 1, paddingX: 1 },
165
- React.createElement(Text, { color: "gray" }, message.content)));
166
- }
167
- else if (message.type === "banner") {
168
- return (React.createElement(Box, { key: message.id, paddingX: 0, marginBottom: 1 },
169
- React.createElement(Box, { flexDirection: "column" },
170
- React.createElement(Banner, { user: user, mode: chatContext.mode, path: displayPath }),
171
- React.createElement(Box, { marginTop: 1, flexDirection: "column" },
172
- React.createElement(Text, { color: "gray" }, "To get started, describe a task or try one of these commands:"),
173
- React.createElement(Box, { marginTop: 1, flexDirection: "column" }, allSlashCommands
174
- .filter(cmd => ["/help", "/mode", "/clear", "/reset"].includes(cmd.value))
175
- .map(cmd => (React.createElement(Text, { key: cmd.value },
176
- React.createElement(Text, { color: "white", bold: true }, cmd.label),
177
- React.createElement(Text, { color: "gray" },
178
- " \u2013 ",
179
- cmd.description)))))))));
180
- }
181
- return React.createElement(Box, { key: message.id });
182
- }),
183
- React.createElement(Box, { flexDirection: "column" },
184
- React.createElement(Box, { height: 1 }, status ? (React.createElement(StatusIndicator, { message: status.text, type: status.type })) : (React.createElement(Text, null, " "))),
185
- React.createElement(Box, { height: 1, paddingX: 1 },
186
- React.createElement(Text, { color: "gray", dimColor: true }, "100% context left")),
187
- React.createElement(Box, null,
188
- React.createElement(ChatInput, { placeholder: "Ask anything or use /help for commands", onSubmit: handleSubmit, onCancel: handleCancel, getSuggestions: getSuggestions, disabled: inputDisabled })),
189
- React.createElement(Box, { height: 1, paddingX: 1 }, footerLines && footerLines.length > 0 ? (React.createElement(Text, { color: "gray", dimColor: true }, footerLines[0])) : (React.createElement(Text, null, " "))))));
296
+ const subtitleLines = useMemo(() => {
297
+ const who = user && user !== "Unknown" ? user : "unknown (check auth profile)";
298
+ return [];
299
+ }, [
300
+ user,
301
+ chatContext.mode,
302
+ chatContext.testingMode,
303
+ chatContext.sessionId,
304
+ displayPath,
305
+ ]);
306
+ const footer = status ? (React.createElement(Box, { flexDirection: "column" },
307
+ React.createElement(StatusIndicator, { message: status.text, type: status.type }))) : undefined;
308
+ return (React.createElement(Box, { flexDirection: "column", width: "100%", height: "100%", overflow: "hidden" },
309
+ paletteOpen ? (React.createElement(CommandPalette, { query: paletteQuery, items: paletteItems, selectedIndex: paletteIndex })) : null,
310
+ React.createElement(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden" },
311
+ React.createElement(TimetoTestLogo, null),
312
+ messages.map((message) => {
313
+ if (!showSystem && message.type === "system") {
314
+ return null;
315
+ }
316
+ // Render tool calls with ToolCallDisplay
317
+ if (message.metadata?.isTool) {
318
+ return (React.createElement(ToolCallDisplay, { key: message.id, toolName: message.metadata.toolName || "unknown", args: message.metadata.toolArgs, result: message.metadata.toolResult, isLoading: message.metadata.isLoading }));
319
+ }
320
+ // Render reasoning with italic/dimmed style
321
+ if (message.metadata?.isReasoning) {
322
+ return (React.createElement(Box, { key: message.id, paddingLeft: 2, marginBottom: 1 },
323
+ React.createElement(Text, { color: "yellow", dimColor: true, italic: true }, message.content)));
324
+ }
325
+ return (React.createElement(ChatMessage, { key: message.id, type: message.type, content: message.content, isError: message.isError }));
326
+ })),
327
+ status && (React.createElement(Box, { paddingY: 1, overflow: "hidden" },
328
+ React.createElement(StatusIndicator, { message: status.text, type: status.type }))),
329
+ permissionRequest && (React.createElement(PermissionPrompt, { request: permissionRequest, onResponse: handlePermissionResponse })),
330
+ React.createElement(ChatInput, { placeholder: "Ask anything or use /help for commands", value: inputValue, onChange: setInputValue, onSubmit: handleSubmit, onCancel: handleCancel, getSuggestions: getSuggestions, disabled: inputDisabled || paletteOpen, testingMode: chatContext.testingMode, directory: displayPath, hintsRight: paletteOpen ? ("esc close") : (React.createElement(Text, null,
331
+ React.createElement(Text, { bold: true, color: "white" }, "tab"),
332
+ " ",
333
+ React.createElement(Text, { dimColor: true }, "mode"),
334
+ " \u2022 ",
335
+ React.createElement(Text, { bold: true, color: "white" }, "ctrl+k"),
336
+ " ",
337
+ React.createElement(Text, { dimColor: true }, "commands"))), onToggleMode: toggleTestingMode, onHistoryUp: () => {
338
+ const hist = inputHistoryRef.current;
339
+ if (hist.length === 0)
340
+ return;
341
+ const next = Math.max(0, (historyIndexRef.current < 0
342
+ ? hist.length
343
+ : historyIndexRef.current) - 1);
344
+ historyIndexRef.current = next;
345
+ setInputValue(hist[next] || "");
346
+ }, onHistoryDown: () => {
347
+ const hist = inputHistoryRef.current;
348
+ if (hist.length === 0)
349
+ return;
350
+ const next = Math.min(hist.length, (historyIndexRef.current < 0
351
+ ? hist.length
352
+ : historyIndexRef.current) + 1);
353
+ historyIndexRef.current = next;
354
+ if (next >= hist.length) {
355
+ setInputValue("");
356
+ return;
357
+ }
358
+ setInputValue(hist[next] || "");
359
+ } })));
190
360
  };
191
361
  //# sourceMappingURL=ChatApp.js.map