@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.
- package/README.md +49 -40
- package/dist/bin/ttt.js +0 -2
- package/dist/bin/ttt.js.map +1 -1
- package/dist/package.json +13 -6
- package/dist/src/commands/chat/ChatApp.js +270 -100
- package/dist/src/commands/chat/ChatApp.js.map +1 -1
- package/dist/src/commands/chat/components/Banner.js +1 -1
- package/dist/src/commands/chat/components/ChatInput.js +97 -36
- package/dist/src/commands/chat/components/ChatInput.js.map +1 -1
- package/dist/src/commands/chat/components/ChatMessage.js +102 -0
- package/dist/src/commands/chat/components/ChatMessage.js.map +1 -0
- package/dist/src/commands/chat/components/MessageBubble.js +2 -1
- package/dist/src/commands/chat/components/MessageBubble.js.map +1 -1
- package/dist/src/commands/chat/components/PermissionPrompt.js +92 -0
- package/dist/src/commands/chat/components/PermissionPrompt.js.map +1 -0
- package/dist/src/commands/chat/components/StatusIndicator.js +21 -5
- package/dist/src/commands/chat/components/StatusIndicator.js.map +1 -1
- package/dist/src/commands/chat/components/ToolCallDisplay.js +141 -0
- package/dist/src/commands/chat/components/ToolCallDisplay.js.map +1 -0
- package/dist/src/commands/chat-ink.js +389 -61
- package/dist/src/commands/chat-ink.js.map +1 -1
- package/dist/src/commands/login.js +5 -5
- package/dist/src/commands/login.js.map +1 -1
- package/dist/src/commands/test.js +14 -194
- package/dist/src/commands/test.js.map +1 -1
- package/dist/src/lib/__tests__/code-mode-integration.test.js +381 -0
- package/dist/src/lib/__tests__/code-mode-integration.test.js.map +1 -0
- package/dist/src/lib/__tests__/config-manager.test.js +81 -0
- package/dist/src/lib/__tests__/config-manager.test.js.map +1 -0
- package/dist/src/lib/__tests__/mode-persistence-integration.test.js +75 -0
- package/dist/src/lib/__tests__/mode-persistence-integration.test.js.map +1 -0
- package/dist/src/lib/__tests__/permission-flow-integration.test.js +145 -0
- package/dist/src/lib/__tests__/permission-flow-integration.test.js.map +1 -0
- package/dist/src/lib/__tests__/permissions.test.js +132 -0
- package/dist/src/lib/__tests__/permissions.test.js.map +1 -0
- package/dist/src/lib/agent-orchestrator.js +263 -4
- package/dist/src/lib/agent-orchestrator.js.map +1 -1
- package/dist/src/lib/config.js +40 -0
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/context-compactor.js +310 -0
- package/dist/src/lib/context-compactor.js.map +1 -0
- package/dist/src/lib/http.js +8 -0
- package/dist/src/lib/http.js.map +1 -1
- package/dist/src/lib/local-tools/code/__tests__/grep-search.test.js +146 -0
- package/dist/src/lib/local-tools/code/__tests__/grep-search.test.js.map +1 -0
- package/dist/src/lib/local-tools/code/__tests__/list-directory.test.js +192 -0
- package/dist/src/lib/local-tools/code/__tests__/list-directory.test.js.map +1 -0
- package/dist/src/lib/local-tools/code/__tests__/read-file.test.js +169 -0
- package/dist/src/lib/local-tools/code/__tests__/read-file.test.js.map +1 -0
- package/dist/src/lib/local-tools/code/__tests__/run-command.test.js +101 -0
- package/dist/src/lib/local-tools/code/__tests__/run-command.test.js.map +1 -0
- package/dist/src/lib/local-tools/code/__tests__/search-files.test.js +191 -0
- package/dist/src/lib/local-tools/code/__tests__/search-files.test.js.map +1 -0
- package/dist/src/lib/local-tools/code/grep-search.js +404 -0
- package/dist/src/lib/local-tools/code/grep-search.js.map +1 -0
- package/dist/src/lib/local-tools/code/index.js +11 -0
- package/dist/src/lib/local-tools/code/index.js.map +1 -0
- package/dist/src/lib/local-tools/code/list-directory.js +276 -0
- package/dist/src/lib/local-tools/code/list-directory.js.map +1 -0
- package/dist/src/lib/local-tools/code/read-file.js +301 -0
- package/dist/src/lib/local-tools/code/read-file.js.map +1 -0
- package/dist/src/lib/local-tools/code/run-command.js +235 -0
- package/dist/src/lib/local-tools/code/run-command.js.map +1 -0
- package/dist/src/lib/local-tools/code/search-files.js +297 -0
- package/dist/src/lib/local-tools/code/search-files.js.map +1 -0
- package/dist/src/lib/local-tools/code/types.js +6 -0
- package/dist/src/lib/local-tools/code/types.js.map +1 -0
- package/dist/src/lib/local-tools/ui/playwright-mcp.js +1 -1
- package/dist/src/lib/permissions.js +94 -0
- package/dist/src/lib/permissions.js.map +1 -0
- package/dist/src/lib/prompts/builder.js +13 -10
- package/dist/src/lib/prompts/builder.js.map +1 -1
- package/dist/src/lib/prompts/templates.js +78 -0
- package/dist/src/lib/prompts/templates.js.map +1 -1
- package/dist/src/lib/session-manager.js.map +1 -1
- package/dist/src/lib/testing-mode.js +2 -2
- package/dist/src/lib/testing-mode.js.map +1 -1
- package/dist/src/lib/tool-executor.js +131 -2
- package/dist/src/lib/tool-executor.js.map +1 -1
- package/dist/src/lib/tool-registry.js +171 -3
- package/dist/src/lib/tool-registry.js.map +1 -1
- package/dist/src/lib/tool-result-pruner.js +4 -4
- package/dist/src/lib/tool-result-pruner.js.map +1 -1
- package/dist/src/lib/tui/ink/components/AppFrame.js +17 -0
- package/dist/src/lib/tui/ink/components/AppFrame.js.map +1 -0
- package/dist/src/lib/tui/ink/components/CommandPalette.js +24 -0
- package/dist/src/lib/tui/ink/components/CommandPalette.js.map +1 -0
- package/dist/src/lib/tui/ink/components/Pill.js +19 -0
- package/dist/src/lib/tui/ink/components/Pill.js.map +1 -0
- package/dist/src/lib/tui/ink/components/TimetoTestLogo.js +30 -0
- package/dist/src/lib/tui/ink/components/TimetoTestLogo.js.map +1 -0
- package/dist/src/lib/tui/ink/theme.js +28 -0
- package/dist/src/lib/tui/ink/theme.js.map +1 -0
- package/dist/src/lib/tui/interactive-chat.js +35 -35
- package/dist/src/lib/tui/interactive-chat.js.map +1 -1
- package/dist/src/lib/tui/print.js +18 -18
- package/dist/src/lib/tui/print.js.map +1 -1
- package/dist/src/lib/tui/prompt.js +3 -3
- package/dist/src/lib/tui/prompt.js.map +1 -1
- package/dist/src/lib/tui/status.js +1 -1
- package/dist/src/lib/tui/status.js.map +1 -1
- package/dist/src/lib/update.js +10 -10
- package/dist/src/lib/update.js.map +1 -1
- package/package.json +13 -6
- package/dist/src/commands/ask/AskApp.js +0 -121
- package/dist/src/commands/ask/AskApp.js.map +0 -1
- package/dist/src/commands/ask/components/AssistantResponse.js +0 -31
- package/dist/src/commands/ask/components/AssistantResponse.js.map +0 -1
- package/dist/src/commands/ask/components/Banner.js +0 -15
- package/dist/src/commands/ask/components/Banner.js.map +0 -1
- package/dist/src/commands/ask/components/ChatInput.js +0 -93
- package/dist/src/commands/ask/components/ChatInput.js.map +0 -1
- package/dist/src/commands/ask/components/Divider.js +0 -17
- package/dist/src/commands/ask/components/Divider.js.map +0 -1
- package/dist/src/commands/ask/components/IntroTips.js +0 -19
- package/dist/src/commands/ask/components/IntroTips.js.map +0 -1
- package/dist/src/commands/ask/components/MessageBubble.js +0 -47
- package/dist/src/commands/ask/components/MessageBubble.js.map +0 -1
- package/dist/src/commands/ask/components/SessionInfo.js +0 -20
- package/dist/src/commands/ask/components/SessionInfo.js.map +0 -1
- package/dist/src/commands/ask/components/StatusIndicator.js +0 -67
- package/dist/src/commands/ask/components/StatusIndicator.js.map +0 -1
- package/dist/src/commands/ask-ink.js +0 -380
- package/dist/src/commands/ask-ink.js.map +0 -1
- package/dist/src/commands/ask.js +0 -991
- package/dist/src/commands/ask.js.map +0 -1
- package/dist/src/commands/chat/components/Divider.js +0 -7
- package/dist/src/commands/chat/components/Divider.js.map +0 -1
- package/dist/src/commands/chat/components/SessionInfo.js +0 -11
- package/dist/src/commands/chat/components/SessionInfo.js.map +0 -1
- package/dist/src/commands/chat.js +0 -82
- package/dist/src/commands/chat.js.map +0 -1
- package/dist/src/commands/start-test.js +0 -119
- package/dist/src/commands/start-test.js.map +0 -1
- package/dist/src/commands/stream.js +0 -17
- package/dist/src/commands/stream.js.map +0 -1
- package/dist/src/lib/legacy-chat-runner.js +0 -37
- package/dist/src/lib/legacy-chat-runner.js.map +0 -1
- package/dist/src/lib/local-tools/ui/click-element.js +0 -105
- package/dist/src/lib/local-tools/ui/click-element.js.map +0 -1
- package/dist/src/lib/local-tools/ui/dom-rag.js +0 -201
- package/dist/src/lib/local-tools/ui/dom-rag.js.map +0 -1
- package/dist/src/lib/local-tools/ui/find-element.js +0 -31
- package/dist/src/lib/local-tools/ui/find-element.js.map +0 -1
- package/dist/src/lib/local-tools/ui/hover-element.js +0 -94
- package/dist/src/lib/local-tools/ui/hover-element.js.map +0 -1
- package/dist/src/lib/local-tools/ui/manage-tab.js +0 -65
- package/dist/src/lib/local-tools/ui/manage-tab.js.map +0 -1
- package/dist/src/lib/local-tools/ui/navigate.js +0 -35
- package/dist/src/lib/local-tools/ui/navigate.js.map +0 -1
- package/dist/src/lib/local-tools/ui/page-discovery.js +0 -32
- package/dist/src/lib/local-tools/ui/page-discovery.js.map +0 -1
- package/dist/src/lib/local-tools/ui/screenshot.js +0 -19
- package/dist/src/lib/local-tools/ui/screenshot.js.map +0 -1
- package/dist/src/lib/local-tools/ui/search-interactive-elements.js +0 -18
- package/dist/src/lib/local-tools/ui/search-interactive-elements.js.map +0 -1
- package/dist/src/lib/local-tools/ui/selector-resolver.js +0 -153
- package/dist/src/lib/local-tools/ui/selector-resolver.js.map +0 -1
- package/dist/src/lib/local-tools/ui/type-text.js +0 -40
- package/dist/src/lib/local-tools/ui/type-text.js.map +0 -1
- package/dist/src/lib/tui/components/AskIntro.js +0 -6
- package/dist/src/lib/tui/components/AskIntro.js.map +0 -1
- package/dist/src/lib/tui/components/Banner.js +0 -15
- package/dist/src/lib/tui/components/Banner.js.map +0 -1
- package/dist/src/lib/tui/components/Divider.js +0 -17
- package/dist/src/lib/tui/components/Divider.js.map +0 -1
- package/dist/src/lib/tui/components/EventLine.js +0 -110
- package/dist/src/lib/tui/components/EventLine.js.map +0 -1
- package/dist/src/lib/tui/components/Header.js +0 -15
- package/dist/src/lib/tui/components/Header.js.map +0 -1
- package/dist/src/lib/tui/components/InputBox.js +0 -9
- package/dist/src/lib/tui/components/InputBox.js.map +0 -1
- package/dist/src/lib/tui/components/Mapping.js +0 -8
- package/dist/src/lib/tui/components/Mapping.js.map +0 -1
- package/dist/src/lib/tui/components/ProjectList.js +0 -6
- package/dist/src/lib/tui/components/ProjectList.js.map +0 -1
- package/dist/src/lib/tui/components/Spinner.js +0 -20
- package/dist/src/lib/tui/components/Spinner.js.map +0 -1
- package/dist/src/lib/tui/components/StatusBanner.js +0 -12
- package/dist/src/lib/tui/components/StatusBanner.js.map +0 -1
- package/dist/src/lib/tui/components/StatusBar.js +0 -11
- package/dist/src/lib/tui/components/StatusBar.js.map +0 -1
- package/dist/src/lib/tui/components/UserBubble.js +0 -6
- package/dist/src/lib/tui/components/UserBubble.js.map +0 -1
- package/dist/src/lib/tui/components/index.js +0 -16
- package/dist/src/lib/tui/components/index.js.map +0 -1
- package/dist/src/lib/tui/ink-print.js +0 -41
- package/dist/src/lib/tui/ink-print.js.map +0 -1
- package/dist/src/test-agent-flow.js +0 -148
- package/dist/src/test-agent-flow.js.map +0 -1
- package/dist/src/test-browser-session.js +0 -152
- package/dist/src/test-browser-session.js.map +0 -1
- package/dist/src/test-browser-snapshot.js +0 -187
- package/dist/src/test-browser-snapshot.js.map +0 -1
- package/dist/src/test-snapshot-detailed.js +0 -219
- package/dist/src/test-snapshot-detailed.js.map +0 -1
- package/dist/src/test-snapshot-simple.js +0 -85
- package/dist/src/test-snapshot-simple.js.map +0 -1
- package/dist/src/test-snapshot-tabs.js +0 -110
- package/dist/src/test-snapshot-tabs.js.map +0 -1
|
@@ -7,19 +7,11 @@ import chalk from "chalk";
|
|
|
7
7
|
import React from "react";
|
|
8
8
|
import { render } from "ink";
|
|
9
9
|
import { AgentOrchestrator } from "../lib/agent-orchestrator.js";
|
|
10
|
-
import { resolveApiUrl, getAuthToken } from "../lib/config.js";
|
|
11
|
-
import { getToolDescription } from "../lib/tool-descriptions.js";
|
|
10
|
+
import { resolveApiUrl, getAuthToken, configManager } from "../lib/config.js";
|
|
12
11
|
import { ChatApp } from "./chat/ChatApp.js";
|
|
13
12
|
import { performInteractiveLogin } from "./login.js";
|
|
14
13
|
import { resolveTestingMode } from "../lib/testing-mode.js";
|
|
15
|
-
export
|
|
16
|
-
.name("chat")
|
|
17
|
-
.description("Start an interactive chat session with the test agent (INK UI)")
|
|
18
|
-
.option("--mode <mode>", "Testing mode: ui or api", "ui")
|
|
19
|
-
.option("--base-url <url>", "Base URL for UI testing", process.env.TTT_BASE_URL)
|
|
20
|
-
.option("--api-base-url <url>", "Base URL for API testing", process.env.TTT_API_BASE_URL)
|
|
21
|
-
.option("--session <id>", "Resume existing session by ID")
|
|
22
|
-
.action(async (options) => {
|
|
14
|
+
export async function runChatInk(options) {
|
|
23
15
|
try {
|
|
24
16
|
const apiUrl = resolveApiUrl();
|
|
25
17
|
let token = getAuthToken();
|
|
@@ -71,19 +63,27 @@ export const chatInk = new Command()
|
|
|
71
63
|
}
|
|
72
64
|
}
|
|
73
65
|
else {
|
|
74
|
-
console.
|
|
66
|
+
console.error(chalk.gray(`ℹ️ Unable to fetch user info: ${error?.message || error}`));
|
|
75
67
|
}
|
|
76
68
|
}
|
|
77
69
|
// Determine mode (local is default)
|
|
78
70
|
const mode = "local";
|
|
79
|
-
|
|
71
|
+
// Load saved mode preference, use --mode flag if provided, otherwise fall back to saved or default
|
|
80
72
|
let testingMode;
|
|
81
73
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
if (options.mode) {
|
|
75
|
+
// User explicitly provided --mode flag
|
|
76
|
+
testingMode = resolveTestingMode(options.mode, {
|
|
77
|
+
defaultMode: "ui",
|
|
78
|
+
strict: true,
|
|
79
|
+
contextLabel: "--mode",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Try to load saved mode preference
|
|
84
|
+
const savedMode = await configManager.getLastMode();
|
|
85
|
+
testingMode = savedMode ?? "ui";
|
|
86
|
+
}
|
|
87
87
|
}
|
|
88
88
|
catch (error) {
|
|
89
89
|
console.error(chalk.red(error?.message || String(error)));
|
|
@@ -102,6 +102,17 @@ export const chatInk = new Command()
|
|
|
102
102
|
function getAppInterface() {
|
|
103
103
|
return globalThis.__chatAppInterface;
|
|
104
104
|
}
|
|
105
|
+
// Set up permission prompt handler - delegates to ChatApp UI
|
|
106
|
+
const permissionPromptFn = async (request) => {
|
|
107
|
+
const appInterface = getAppInterface();
|
|
108
|
+
if (!appInterface?.promptPermission) {
|
|
109
|
+
// Fallback: deny if UI not available
|
|
110
|
+
return "deny";
|
|
111
|
+
}
|
|
112
|
+
return appInterface.promptPermission(request);
|
|
113
|
+
};
|
|
114
|
+
// Wire up permission handler to orchestrator's tool executor
|
|
115
|
+
orchestrator.setPermissionPromptFn(permissionPromptFn);
|
|
105
116
|
function handleUserCancel() {
|
|
106
117
|
const appInterface = getAppInterface();
|
|
107
118
|
const wasCancelled = userCancelled;
|
|
@@ -118,46 +129,61 @@ export const chatInk = new Command()
|
|
|
118
129
|
orchestrator.cancel();
|
|
119
130
|
}
|
|
120
131
|
// Set up event listeners for tool execution with frontend-style display
|
|
121
|
-
|
|
132
|
+
const onAssistantReasoning = (data) => {
|
|
122
133
|
if (userCancelled) {
|
|
123
134
|
return;
|
|
124
135
|
}
|
|
125
136
|
const appInterface = getAppInterface();
|
|
126
137
|
const reasoning = data.data?.reasoning || data.message;
|
|
127
138
|
if (reasoning && reasoning.trim()) {
|
|
128
|
-
// Add reasoning as a system message
|
|
139
|
+
// Add reasoning as a system message with special formatting
|
|
129
140
|
appInterface?.addMessage({
|
|
130
141
|
id: `reasoning-${Date.now()}`,
|
|
131
142
|
type: "system",
|
|
132
|
-
content:
|
|
143
|
+
content: reasoning,
|
|
144
|
+
metadata: { isReasoning: true },
|
|
133
145
|
});
|
|
134
146
|
}
|
|
135
|
-
}
|
|
136
|
-
|
|
147
|
+
};
|
|
148
|
+
const onToolStart = (data) => {
|
|
137
149
|
if (userCancelled) {
|
|
138
150
|
return;
|
|
139
151
|
}
|
|
140
152
|
const appInterface = getAppInterface();
|
|
141
153
|
const toolName = data.data?.tool || data.tool;
|
|
142
|
-
const
|
|
143
|
-
// Add tool
|
|
154
|
+
const toolArgs = data.data?.arguments || {};
|
|
155
|
+
// Add tool call as a system message with tool metadata
|
|
144
156
|
appInterface?.addMessage({
|
|
145
157
|
id: `tool-${Date.now()}`,
|
|
146
158
|
type: "system",
|
|
147
|
-
content:
|
|
159
|
+
content: "", // Content will be rendered by ToolCallDisplay
|
|
160
|
+
metadata: {
|
|
161
|
+
isTool: true,
|
|
162
|
+
toolName,
|
|
163
|
+
toolArgs,
|
|
164
|
+
isLoading: true,
|
|
165
|
+
},
|
|
148
166
|
});
|
|
149
167
|
appInterface?.setStatus({
|
|
150
168
|
text: "Agent thinking...",
|
|
151
169
|
type: "loading",
|
|
152
170
|
});
|
|
153
|
-
}
|
|
154
|
-
|
|
171
|
+
};
|
|
172
|
+
const onToolResult = (data) => {
|
|
155
173
|
if (userCancelled) {
|
|
156
174
|
return;
|
|
157
175
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
176
|
+
const appInterface = getAppInterface();
|
|
177
|
+
const toolName = data.data?.tool || data.tool;
|
|
178
|
+
const result = data.data?.result;
|
|
179
|
+
// Update the last tool message with the result
|
|
180
|
+
appInterface?.updateLastToolMessage?.({
|
|
181
|
+
toolName,
|
|
182
|
+
result,
|
|
183
|
+
isLoading: false,
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
const onAgentCancelled = () => {
|
|
161
187
|
if (userCancelled) {
|
|
162
188
|
return;
|
|
163
189
|
}
|
|
@@ -168,7 +194,18 @@ export const chatInk = new Command()
|
|
|
168
194
|
type: "system",
|
|
169
195
|
content: "🛑 Cancelled — the assistant has stopped. Type whenever you're ready.",
|
|
170
196
|
});
|
|
171
|
-
}
|
|
197
|
+
};
|
|
198
|
+
orchestrator.on("assistant_reasoning", onAssistantReasoning);
|
|
199
|
+
orchestrator.on("tool_start", onToolStart);
|
|
200
|
+
orchestrator.on("tool_result", onToolResult);
|
|
201
|
+
orchestrator.on("agent_cancelled", onAgentCancelled);
|
|
202
|
+
const cleanupOrchestratorListeners = () => {
|
|
203
|
+
// EventEmitter supports `off` in modern Node, but be tolerant.
|
|
204
|
+
(orchestrator.off ?? orchestrator.removeListener).call(orchestrator, "assistant_reasoning", onAssistantReasoning);
|
|
205
|
+
(orchestrator.off ?? orchestrator.removeListener).call(orchestrator, "tool_start", onToolStart);
|
|
206
|
+
(orchestrator.off ?? orchestrator.removeListener).call(orchestrator, "tool_result", onToolResult);
|
|
207
|
+
(orchestrator.off ?? orchestrator.removeListener).call(orchestrator, "agent_cancelled", onAgentCancelled);
|
|
208
|
+
};
|
|
172
209
|
// Initialize context
|
|
173
210
|
const chatContext = {
|
|
174
211
|
sessionId: orchestrator.getSessionId(),
|
|
@@ -188,8 +225,117 @@ export const chatInk = new Command()
|
|
|
188
225
|
chatContext.toolCount = history.filter((msg) => msg.role === "tool").length;
|
|
189
226
|
};
|
|
190
227
|
updateContextFromOrchestrator();
|
|
191
|
-
// Define slash commands
|
|
228
|
+
// Define slash commands (alphabetically sorted)
|
|
192
229
|
const slashCommands = [
|
|
230
|
+
{
|
|
231
|
+
name: "cancel",
|
|
232
|
+
description: "Alias for /stop",
|
|
233
|
+
handler: async () => {
|
|
234
|
+
handleUserCancel();
|
|
235
|
+
return "continue";
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "clear",
|
|
240
|
+
description: "Clear the transcript visually",
|
|
241
|
+
handler: async () => "continue",
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "bugs",
|
|
245
|
+
description: "List all bugs identified in this session",
|
|
246
|
+
handler: async (args, orchestrator) => {
|
|
247
|
+
const history = orchestrator.getConversationHistory();
|
|
248
|
+
const findings = history
|
|
249
|
+
.filter((msg) => msg.role === "tool" && msg.toolCall?.name === "generate_document")
|
|
250
|
+
.map((msg) => {
|
|
251
|
+
try {
|
|
252
|
+
const res = JSON.parse(msg.content);
|
|
253
|
+
return res.findings || [];
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
.flat();
|
|
260
|
+
const appInterface = getAppInterface();
|
|
261
|
+
if (findings.length === 0) {
|
|
262
|
+
appInterface?.addMessage({
|
|
263
|
+
id: Date.now().toString(),
|
|
264
|
+
type: "system",
|
|
265
|
+
content: "No bugs have been formally reported yet. Use /report to generate a report.",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
const list = findings
|
|
270
|
+
.map((f, i) => `${i + 1}. [${f.severity?.toUpperCase()}] ${f.title}`)
|
|
271
|
+
.join("\n");
|
|
272
|
+
appInterface?.addMessage({
|
|
273
|
+
id: Date.now().toString(),
|
|
274
|
+
type: "system",
|
|
275
|
+
content: `🐞 Identified Bugs:\n${list}`,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return "continue";
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "compact",
|
|
283
|
+
description: "Compact conversation context to manage token limits",
|
|
284
|
+
handler: async () => {
|
|
285
|
+
const stats = orchestrator.getContextStats();
|
|
286
|
+
const appInterface = getAppInterface();
|
|
287
|
+
if (!stats.needsCompaction) {
|
|
288
|
+
appInterface?.addMessage({
|
|
289
|
+
id: Date.now().toString(),
|
|
290
|
+
type: "system",
|
|
291
|
+
content: `Context: ${stats.totalMessages} messages, ${stats.tokens.toLocaleString()} tokens (${stats.utilizationPercent.toFixed(1)}% of limit), ${stats.conversationTurns} turns. No compaction needed.`,
|
|
292
|
+
});
|
|
293
|
+
return "continue";
|
|
294
|
+
}
|
|
295
|
+
const result = await orchestrator.compactContext();
|
|
296
|
+
appInterface?.addMessage({
|
|
297
|
+
id: Date.now().toString(),
|
|
298
|
+
type: "system",
|
|
299
|
+
content: `✅ Context compacted: ${result.stats.removedCount} messages removed\n` +
|
|
300
|
+
`Before: ${result.stats.tokensBeforeCompaction.toLocaleString()} tokens\n` +
|
|
301
|
+
`After: ${result.stats.tokensAfterCompaction.toLocaleString()} tokens\n` +
|
|
302
|
+
`Strategy: ${result.stats.strategy}` +
|
|
303
|
+
(result.stats.summaryGenerated
|
|
304
|
+
? "\n📝 LLM summary generated"
|
|
305
|
+
: ""),
|
|
306
|
+
});
|
|
307
|
+
updateContextFromOrchestrator();
|
|
308
|
+
appInterface?.updateContext(chatContext);
|
|
309
|
+
return "continue";
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "exit",
|
|
314
|
+
description: "Exit the session (saves progress)",
|
|
315
|
+
handler: async () => "exit",
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: "goto",
|
|
319
|
+
description: "Direct the agent to a specific URL",
|
|
320
|
+
handler: async (args, orchestrator) => {
|
|
321
|
+
if (args.length === 0) {
|
|
322
|
+
getAppInterface()?.addMessage({
|
|
323
|
+
id: Date.now().toString(),
|
|
324
|
+
type: "system",
|
|
325
|
+
content: "Usage: /goto <url>",
|
|
326
|
+
});
|
|
327
|
+
return "continue";
|
|
328
|
+
}
|
|
329
|
+
const url = args[0];
|
|
330
|
+
getAppInterface()?.addMessage({
|
|
331
|
+
id: Date.now().toString(),
|
|
332
|
+
type: "system",
|
|
333
|
+
content: `🚀 Navigating to ${url}...`,
|
|
334
|
+
});
|
|
335
|
+
void handleUserMessage(`Navigate to ${url} and tell me what you see.`);
|
|
336
|
+
return "continue";
|
|
337
|
+
},
|
|
338
|
+
},
|
|
193
339
|
{
|
|
194
340
|
name: "help",
|
|
195
341
|
description: "Show available slash commands",
|
|
@@ -265,56 +411,96 @@ export const chatInk = new Command()
|
|
|
265
411
|
},
|
|
266
412
|
},
|
|
267
413
|
{
|
|
268
|
-
name: "
|
|
269
|
-
description: "
|
|
270
|
-
handler: async () => {
|
|
271
|
-
const sessionText = [
|
|
272
|
-
`ID: ${chatContext.sessionId}`,
|
|
273
|
-
`Path: ${chatContext.sessionPath}`,
|
|
274
|
-
`Mode: ${chatContext.mode}`,
|
|
275
|
-
`Testing mode: ${chatContext.testingMode}`,
|
|
276
|
-
].join("\n");
|
|
414
|
+
name: "report",
|
|
415
|
+
description: "Force the agent to generate a final report",
|
|
416
|
+
handler: async (args, orchestrator) => {
|
|
277
417
|
getAppInterface()?.addMessage({
|
|
278
418
|
id: Date.now().toString(),
|
|
279
419
|
type: "system",
|
|
280
|
-
content:
|
|
420
|
+
content: "📑 Generating report based on session history...",
|
|
281
421
|
});
|
|
422
|
+
void handleUserMessage("Please generate a final report of your findings for this session.");
|
|
282
423
|
return "continue";
|
|
283
424
|
},
|
|
284
425
|
},
|
|
285
426
|
{
|
|
286
|
-
name: "
|
|
287
|
-
description: "
|
|
427
|
+
name: "scan",
|
|
428
|
+
description: "Instruct the agent to scan the page for interactive elements",
|
|
288
429
|
handler: async () => {
|
|
289
|
-
|
|
430
|
+
getAppInterface()?.addMessage({
|
|
431
|
+
id: Date.now().toString(),
|
|
432
|
+
type: "system",
|
|
433
|
+
content: "🔍 Scanning page for interactive elements...",
|
|
434
|
+
});
|
|
435
|
+
void handleUserMessage("Scan the current page and list all interactive elements you find.");
|
|
290
436
|
return "continue";
|
|
291
437
|
},
|
|
292
438
|
},
|
|
293
439
|
{
|
|
294
|
-
name: "
|
|
295
|
-
description: "
|
|
440
|
+
name: "screenshot",
|
|
441
|
+
description: "Force the agent to take a screenshot and show the state",
|
|
296
442
|
handler: async () => {
|
|
297
|
-
|
|
443
|
+
getAppInterface()?.addMessage({
|
|
444
|
+
id: Date.now().toString(),
|
|
445
|
+
type: "system",
|
|
446
|
+
content: "📸 Capturing fresh screenshot...",
|
|
447
|
+
});
|
|
448
|
+
void handleUserMessage("Please take a screenshot of the current page and tell me what's visible.");
|
|
298
449
|
return "continue";
|
|
299
450
|
},
|
|
300
451
|
},
|
|
301
452
|
{
|
|
302
|
-
name: "
|
|
303
|
-
description: "
|
|
453
|
+
name: "where",
|
|
454
|
+
description: "Get the current URL and page title from the agent",
|
|
304
455
|
handler: async () => {
|
|
305
|
-
|
|
456
|
+
getAppInterface()?.addMessage({
|
|
457
|
+
id: Date.now().toString(),
|
|
458
|
+
type: "system",
|
|
459
|
+
content: "📍 Checking location...",
|
|
460
|
+
});
|
|
461
|
+
void handleUserMessage("What is the current URL and page title?");
|
|
306
462
|
return "continue";
|
|
307
463
|
},
|
|
308
464
|
},
|
|
309
465
|
{
|
|
310
|
-
name: "
|
|
311
|
-
description: "
|
|
312
|
-
handler: async () =>
|
|
466
|
+
name: "reset",
|
|
467
|
+
description: "Reset session and clear all messages",
|
|
468
|
+
handler: async (args, orchestrator) => {
|
|
469
|
+
orchestrator.cancel();
|
|
470
|
+
getAppInterface()?.addMessage({
|
|
471
|
+
id: Date.now().toString(),
|
|
472
|
+
type: "system",
|
|
473
|
+
content: "🧹 Session reset. History cleared.",
|
|
474
|
+
});
|
|
475
|
+
// Note: In a real app we'd want to tell the orchestrator to reset its internal state too
|
|
476
|
+
return "continue";
|
|
477
|
+
},
|
|
313
478
|
},
|
|
314
479
|
{
|
|
315
|
-
name: "
|
|
316
|
-
description: "
|
|
317
|
-
handler: async () =>
|
|
480
|
+
name: "session",
|
|
481
|
+
description: "Display session metadata and storage path",
|
|
482
|
+
handler: async () => {
|
|
483
|
+
const sessionText = [
|
|
484
|
+
`ID: ${chatContext.sessionId}`,
|
|
485
|
+
`Path: ${chatContext.sessionPath}`,
|
|
486
|
+
`Mode: ${chatContext.mode}`,
|
|
487
|
+
`Testing mode: ${chatContext.testingMode}`,
|
|
488
|
+
].join("\n");
|
|
489
|
+
getAppInterface()?.addMessage({
|
|
490
|
+
id: Date.now().toString(),
|
|
491
|
+
type: "system",
|
|
492
|
+
content: sessionText,
|
|
493
|
+
});
|
|
494
|
+
return "continue";
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: "stop",
|
|
499
|
+
description: "Stop the assistant (same as ESC)",
|
|
500
|
+
handler: async () => {
|
|
501
|
+
handleUserCancel();
|
|
502
|
+
return "continue";
|
|
503
|
+
},
|
|
318
504
|
},
|
|
319
505
|
{
|
|
320
506
|
name: "logout",
|
|
@@ -339,6 +525,108 @@ export const chatInk = new Command()
|
|
|
339
525
|
return "exit";
|
|
340
526
|
},
|
|
341
527
|
},
|
|
528
|
+
{
|
|
529
|
+
name: "model",
|
|
530
|
+
description: "Switch AI model (Pro tier required)",
|
|
531
|
+
handler: async (args, orchestrator, context) => {
|
|
532
|
+
const appInterface = getAppInterface();
|
|
533
|
+
try {
|
|
534
|
+
const response = await http.get("/api/v1/ai/models");
|
|
535
|
+
const data = response.data;
|
|
536
|
+
// Check if user can use premium models
|
|
537
|
+
const availableModels = data.models.filter((m) => m.available);
|
|
538
|
+
const hasPremiumAccess = availableModels.some((m) => m.tier === "premium");
|
|
539
|
+
if (!hasPremiumAccess) {
|
|
540
|
+
appInterface?.addMessage({
|
|
541
|
+
id: Date.now().toString(),
|
|
542
|
+
type: "system",
|
|
543
|
+
content: "❌ Model switching requires Pro tier. Upgrade at https://timetotest.com/pricing",
|
|
544
|
+
});
|
|
545
|
+
return "continue";
|
|
546
|
+
}
|
|
547
|
+
// No args - show available models
|
|
548
|
+
if (args.length === 0) {
|
|
549
|
+
const modelsList = availableModels
|
|
550
|
+
.map((m) => {
|
|
551
|
+
const usesInfo = m.usesRemaining !== null
|
|
552
|
+
? ` (${m.usesRemaining} free uses left)`
|
|
553
|
+
: "";
|
|
554
|
+
const creditInfo = m.credits > 0
|
|
555
|
+
? ` - ${m.credits} credit${m.credits !== 1 ? "s" : ""}/msg`
|
|
556
|
+
: "";
|
|
557
|
+
return ` ${m.id} - ${m.name}${creditInfo}${usesInfo}`;
|
|
558
|
+
})
|
|
559
|
+
.join("\n");
|
|
560
|
+
// Show appropriate balance/plan info
|
|
561
|
+
const planTier = data.planTier || "free";
|
|
562
|
+
const isPaidPlan = planTier !== "free";
|
|
563
|
+
const creditBalance = data.creditBalance || 0;
|
|
564
|
+
let balanceInfo = "";
|
|
565
|
+
if (isPaidPlan) {
|
|
566
|
+
// Paid plan users - show plan tier
|
|
567
|
+
balanceInfo = `Plan: ${planTier.charAt(0).toUpperCase() + planTier.slice(1)}`;
|
|
568
|
+
if (creditBalance > 0) {
|
|
569
|
+
balanceInfo += ` | Extra credits: ${creditBalance}`;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
// Free tier - show credits
|
|
574
|
+
balanceInfo = `Credits: ${creditBalance}`;
|
|
575
|
+
}
|
|
576
|
+
appInterface?.addMessage({
|
|
577
|
+
id: Date.now().toString(),
|
|
578
|
+
type: "system",
|
|
579
|
+
content: `Available Models:\n${modelsList}\n\n${balanceInfo}\n\nUsage: /model <model-id>`,
|
|
580
|
+
});
|
|
581
|
+
return "continue";
|
|
582
|
+
}
|
|
583
|
+
// Model switching
|
|
584
|
+
const modelId = args[0];
|
|
585
|
+
const model = data.models.find((m) => m.id === modelId);
|
|
586
|
+
if (!model || !model.available) {
|
|
587
|
+
appInterface?.addMessage({
|
|
588
|
+
id: Date.now().toString(),
|
|
589
|
+
type: "system",
|
|
590
|
+
content: model
|
|
591
|
+
? `❌ ${model.name} is unavailable`
|
|
592
|
+
: `❌ Unknown model: ${modelId}`,
|
|
593
|
+
});
|
|
594
|
+
return "continue";
|
|
595
|
+
}
|
|
596
|
+
await orchestrator.setModel(modelId);
|
|
597
|
+
// Show appropriate success message
|
|
598
|
+
const planTier = data.planTier || "free";
|
|
599
|
+
const isPaidPlan = planTier !== "free";
|
|
600
|
+
const creditBalance = data.creditBalance || 0;
|
|
601
|
+
let statusInfo = "";
|
|
602
|
+
if (model.credits > 0) {
|
|
603
|
+
if (isPaidPlan) {
|
|
604
|
+
statusInfo = ` (${model.credits} credit${model.credits !== 1 ? "s" : ""}/msg, ${planTier} plan)`;
|
|
605
|
+
}
|
|
606
|
+
else if (creditBalance > 0) {
|
|
607
|
+
statusInfo = ` (${model.credits} credit${model.credits !== 1 ? "s" : ""}/msg, ${creditBalance} credits remaining)`;
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
statusInfo = ` (${model.credits} credit${model.credits !== 1 ? "s" : ""}/msg)`;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
appInterface?.addMessage({
|
|
614
|
+
id: Date.now().toString(),
|
|
615
|
+
type: "system",
|
|
616
|
+
content: `✅ Switched to ${model.name}${statusInfo}`,
|
|
617
|
+
});
|
|
618
|
+
return "continue";
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
appInterface?.addMessage({
|
|
622
|
+
id: Date.now().toString(),
|
|
623
|
+
type: "system",
|
|
624
|
+
content: `❌ Failed: ${error?.message || "Unknown error"}`,
|
|
625
|
+
});
|
|
626
|
+
return "continue";
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
},
|
|
342
630
|
];
|
|
343
631
|
const commandMap = new Map(slashCommands.map((command) => [command.name, command.handler]));
|
|
344
632
|
const executeSlashCommand = async (cmd, args) => {
|
|
@@ -400,31 +688,71 @@ export const chatInk = new Command()
|
|
|
400
688
|
}
|
|
401
689
|
};
|
|
402
690
|
const handleExit = () => {
|
|
403
|
-
|
|
691
|
+
cleanupOrchestratorListeners();
|
|
692
|
+
console.error(chalk.green("\n✓ Session saved. Run 'ttt resume' to continue."));
|
|
404
693
|
process.exit(0);
|
|
405
694
|
};
|
|
406
695
|
// Prepare slash commands for the UI
|
|
407
696
|
const slashCommandsForUI = slashCommands.map((cmd) => ({
|
|
408
697
|
name: cmd.name,
|
|
409
698
|
description: cmd.description,
|
|
410
|
-
value: cmd.name
|
|
699
|
+
value: `/${cmd.name}`,
|
|
411
700
|
label: `/${cmd.name}`,
|
|
412
701
|
}));
|
|
413
702
|
// Render the INK app
|
|
414
|
-
const { waitUntilExit } = render(React.createElement(ChatApp, {
|
|
703
|
+
const { waitUntilExit, clear } = render(React.createElement(ChatApp, {
|
|
415
704
|
user: userName,
|
|
416
705
|
context: chatContext,
|
|
417
706
|
slashCommands: slashCommandsForUI,
|
|
418
707
|
onUserMessage: handleUserMessage,
|
|
419
708
|
onSlashCommand: executeSlashCommand,
|
|
709
|
+
onSetTestingMode: async (mode) => {
|
|
710
|
+
orchestrator.setTestingMode(mode);
|
|
711
|
+
updateContextFromOrchestrator();
|
|
712
|
+
getAppInterface()?.updateContext(chatContext);
|
|
713
|
+
// Save mode preference for next session
|
|
714
|
+
await configManager.setLastMode(mode);
|
|
715
|
+
},
|
|
420
716
|
onExit: handleExit,
|
|
421
717
|
onCancel: handleUserCancel,
|
|
422
|
-
|
|
718
|
+
initialInput: options.initialInput,
|
|
719
|
+
}), {
|
|
720
|
+
stdin: process.stdin,
|
|
721
|
+
stdout: process.stdout,
|
|
722
|
+
debug: false,
|
|
723
|
+
patchConsole: false,
|
|
724
|
+
});
|
|
725
|
+
// Clear and re-render on terminal resize to prevent ghosting
|
|
726
|
+
const handleResize = () => {
|
|
727
|
+
// Clear entire terminal including scrollback
|
|
728
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
729
|
+
// Reset Ink's line counter and force re-render
|
|
730
|
+
clear();
|
|
731
|
+
// Force a re-render by triggering state change
|
|
732
|
+
const appInterface = getAppInterface();
|
|
733
|
+
if (appInterface) {
|
|
734
|
+
appInterface.setStatus({ text: "", type: "info" });
|
|
735
|
+
setTimeout(() => appInterface.clearStatus(), 0);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
process.stdout.on("resize", handleResize);
|
|
423
739
|
await waitUntilExit();
|
|
740
|
+
process.stdout.off("resize", handleResize);
|
|
741
|
+
cleanupOrchestratorListeners();
|
|
424
742
|
}
|
|
425
743
|
catch (error) {
|
|
426
744
|
console.error(chalk.red(`Error: ${error.message}`));
|
|
427
745
|
process.exit(1);
|
|
428
746
|
}
|
|
747
|
+
}
|
|
748
|
+
export const chatInk = new Command()
|
|
749
|
+
.name("chat")
|
|
750
|
+
.description("Start an interactive chat session with the test agent (INK UI). Supports UI testing (browser automation), API testing (HTTP requests), and Code analysis (local file analysis and bug finding).")
|
|
751
|
+
.option("--mode <mode>", "Testing mode: ui, api, or code", "ui")
|
|
752
|
+
.option("--base-url <url>", "Base URL for UI testing", process.env.TTT_BASE_URL)
|
|
753
|
+
.option("--api-base-url <url>", "Base URL for API testing", process.env.TTT_API_BASE_URL)
|
|
754
|
+
.option("--session <id>", "Resume existing session by ID")
|
|
755
|
+
.action(async (options) => {
|
|
756
|
+
await runChatInk(options);
|
|
429
757
|
});
|
|
430
758
|
//# sourceMappingURL=chat-ink.js.map
|