@timetotest/cli 0.2.4 → 0.3.1
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 +8 -3
- package/dist/src/commands/chat/ChatApp.js +249 -67
- package/dist/src/commands/chat/ChatApp.js.map +1 -1
- package/dist/src/commands/chat/OnboardingApp.js +49 -0
- package/dist/src/commands/chat/OnboardingApp.js.map +1 -0
- package/dist/src/commands/chat/components/Banner.js +1 -1
- package/dist/src/commands/chat/components/Banner.js.map +1 -1
- package/dist/src/commands/chat/components/ChatInput.js +61 -22
- 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 +1 -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 +328 -93
- 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 -291
- 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 +259 -5
- 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/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 +13 -6
- package/dist/src/lib/tui/ink/theme.js.map +1 -1
- 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 +8 -3
- package/dist/src/commands/start-test.js +0 -180
- package/dist/src/commands/start-test.js.map +0 -1
- package/dist/src/commands/stream/StreamApp.js +0 -127
- package/dist/src/commands/stream/StreamApp.js.map +0 -1
- package/dist/src/commands/stream.js +0 -52
- package/dist/src/commands/stream.js.map +0 -1
- package/dist/src/commands/test/TestRunApp.js +0 -183
- package/dist/src/commands/test/TestRunApp.js.map +0 -1
|
@@ -7,36 +7,44 @@ 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();
|
|
26
18
|
if (!token) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
// Use TUI onboarding instead of direct login
|
|
20
|
+
// We need to dynamically import OnboardingApp to avoid top-level side effects if possible
|
|
21
|
+
const { OnboardingApp } = await import("./chat/OnboardingApp.js");
|
|
22
|
+
// Wrap TUI in a promise to wait for user interaction
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
const { unmount } = render(React.createElement(OnboardingApp, {
|
|
25
|
+
onLogin: async () => {
|
|
26
|
+
unmount();
|
|
27
|
+
try {
|
|
28
|
+
await performInteractiveLogin();
|
|
29
|
+
// Update token after authentication
|
|
30
|
+
token = getAuthToken();
|
|
31
|
+
if (!token) {
|
|
32
|
+
console.error("❌ Authentication failed");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
resolve();
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error(`❌ Authentication failed: ${error?.message || error}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
onExit: () => {
|
|
43
|
+
unmount();
|
|
44
|
+
process.exit(0);
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
});
|
|
40
48
|
}
|
|
41
49
|
// Fetch user information
|
|
42
50
|
const { createHttpClient } = await import("../lib/http.js");
|
|
@@ -71,19 +79,27 @@ export const chatInk = new Command()
|
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
else {
|
|
74
|
-
console.
|
|
82
|
+
console.error(chalk.gray(`ℹ️ Unable to fetch user info: ${error?.message || error}`));
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
85
|
// Determine mode (local is default)
|
|
78
86
|
const mode = "local";
|
|
79
|
-
|
|
87
|
+
// Load saved mode preference, use --mode flag if provided, otherwise fall back to saved or default
|
|
80
88
|
let testingMode;
|
|
81
89
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
if (options.mode) {
|
|
91
|
+
// User explicitly provided --mode flag
|
|
92
|
+
testingMode = resolveTestingMode(options.mode, {
|
|
93
|
+
defaultMode: "ui",
|
|
94
|
+
strict: true,
|
|
95
|
+
contextLabel: "--mode",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Try to load saved mode preference
|
|
100
|
+
const savedMode = await configManager.getLastMode();
|
|
101
|
+
testingMode = savedMode ?? "ui";
|
|
102
|
+
}
|
|
87
103
|
}
|
|
88
104
|
catch (error) {
|
|
89
105
|
console.error(chalk.red(error?.message || String(error)));
|
|
@@ -91,7 +107,7 @@ export const chatInk = new Command()
|
|
|
91
107
|
}
|
|
92
108
|
const config = {
|
|
93
109
|
apiUrl,
|
|
94
|
-
token,
|
|
110
|
+
token: token || undefined,
|
|
95
111
|
mode: "local",
|
|
96
112
|
testingMode,
|
|
97
113
|
baseUrl: options.baseUrl,
|
|
@@ -102,6 +118,17 @@ export const chatInk = new Command()
|
|
|
102
118
|
function getAppInterface() {
|
|
103
119
|
return globalThis.__chatAppInterface;
|
|
104
120
|
}
|
|
121
|
+
// Set up permission prompt handler - delegates to ChatApp UI
|
|
122
|
+
const permissionPromptFn = async (request) => {
|
|
123
|
+
const appInterface = getAppInterface();
|
|
124
|
+
if (!appInterface?.promptPermission) {
|
|
125
|
+
// Fallback: deny if UI not available
|
|
126
|
+
return "deny";
|
|
127
|
+
}
|
|
128
|
+
return appInterface.promptPermission(request);
|
|
129
|
+
};
|
|
130
|
+
// Wire up permission handler to orchestrator's tool executor
|
|
131
|
+
orchestrator.setPermissionPromptFn(permissionPromptFn);
|
|
105
132
|
function handleUserCancel() {
|
|
106
133
|
const appInterface = getAppInterface();
|
|
107
134
|
const wasCancelled = userCancelled;
|
|
@@ -125,11 +152,12 @@ export const chatInk = new Command()
|
|
|
125
152
|
const appInterface = getAppInterface();
|
|
126
153
|
const reasoning = data.data?.reasoning || data.message;
|
|
127
154
|
if (reasoning && reasoning.trim()) {
|
|
128
|
-
// Add reasoning as a system message
|
|
155
|
+
// Add reasoning as a system message with special formatting
|
|
129
156
|
appInterface?.addMessage({
|
|
130
157
|
id: `reasoning-${Date.now()}`,
|
|
131
158
|
type: "system",
|
|
132
|
-
content:
|
|
159
|
+
content: reasoning,
|
|
160
|
+
metadata: { isReasoning: true },
|
|
133
161
|
});
|
|
134
162
|
}
|
|
135
163
|
};
|
|
@@ -139,23 +167,37 @@ export const chatInk = new Command()
|
|
|
139
167
|
}
|
|
140
168
|
const appInterface = getAppInterface();
|
|
141
169
|
const toolName = data.data?.tool || data.tool;
|
|
142
|
-
const
|
|
143
|
-
// Add tool
|
|
170
|
+
const toolArgs = data.data?.arguments || {};
|
|
171
|
+
// Add tool call as a system message with tool metadata
|
|
144
172
|
appInterface?.addMessage({
|
|
145
173
|
id: `tool-${Date.now()}`,
|
|
146
174
|
type: "system",
|
|
147
|
-
content:
|
|
175
|
+
content: "", // Content will be rendered by ToolCallDisplay
|
|
176
|
+
metadata: {
|
|
177
|
+
isTool: true,
|
|
178
|
+
toolName,
|
|
179
|
+
toolArgs,
|
|
180
|
+
isLoading: true,
|
|
181
|
+
},
|
|
148
182
|
});
|
|
149
183
|
appInterface?.setStatus({
|
|
150
184
|
text: "Agent thinking...",
|
|
151
185
|
type: "loading",
|
|
152
186
|
});
|
|
153
187
|
};
|
|
154
|
-
const onToolResult = () => {
|
|
188
|
+
const onToolResult = (data) => {
|
|
155
189
|
if (userCancelled) {
|
|
156
190
|
return;
|
|
157
191
|
}
|
|
158
|
-
|
|
192
|
+
const appInterface = getAppInterface();
|
|
193
|
+
const toolName = data.data?.tool || data.tool;
|
|
194
|
+
const result = data.data?.result;
|
|
195
|
+
// Update the last tool message with the result
|
|
196
|
+
appInterface?.updateLastToolMessage?.({
|
|
197
|
+
toolName,
|
|
198
|
+
result,
|
|
199
|
+
isLoading: false,
|
|
200
|
+
});
|
|
159
201
|
};
|
|
160
202
|
const onAgentCancelled = () => {
|
|
161
203
|
if (userCancelled) {
|
|
@@ -199,8 +241,117 @@ export const chatInk = new Command()
|
|
|
199
241
|
chatContext.toolCount = history.filter((msg) => msg.role === "tool").length;
|
|
200
242
|
};
|
|
201
243
|
updateContextFromOrchestrator();
|
|
202
|
-
// Define slash commands
|
|
244
|
+
// Define slash commands (alphabetically sorted)
|
|
203
245
|
const slashCommands = [
|
|
246
|
+
{
|
|
247
|
+
name: "cancel",
|
|
248
|
+
description: "Alias for /stop",
|
|
249
|
+
handler: async () => {
|
|
250
|
+
handleUserCancel();
|
|
251
|
+
return "continue";
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: "clear",
|
|
256
|
+
description: "Clear the transcript visually",
|
|
257
|
+
handler: async () => "continue",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "bugs",
|
|
261
|
+
description: "List all bugs identified in this session",
|
|
262
|
+
handler: async (args, orchestrator) => {
|
|
263
|
+
const history = orchestrator.getConversationHistory();
|
|
264
|
+
const findings = history
|
|
265
|
+
.filter((msg) => msg.role === "tool" && msg.toolCall?.name === "generate_document")
|
|
266
|
+
.map((msg) => {
|
|
267
|
+
try {
|
|
268
|
+
const res = JSON.parse(msg.content);
|
|
269
|
+
return res.findings || [];
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
.flat();
|
|
276
|
+
const appInterface = getAppInterface();
|
|
277
|
+
if (findings.length === 0) {
|
|
278
|
+
appInterface?.addMessage({
|
|
279
|
+
id: Date.now().toString(),
|
|
280
|
+
type: "system",
|
|
281
|
+
content: "No bugs have been formally reported yet. Use /report to generate a report.",
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const list = findings
|
|
286
|
+
.map((f, i) => `${i + 1}. [${f.severity?.toUpperCase()}] ${f.title}`)
|
|
287
|
+
.join("\n");
|
|
288
|
+
appInterface?.addMessage({
|
|
289
|
+
id: Date.now().toString(),
|
|
290
|
+
type: "system",
|
|
291
|
+
content: `🐞 Identified Bugs:\n${list}`,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return "continue";
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "compact",
|
|
299
|
+
description: "Compact conversation context to manage token limits",
|
|
300
|
+
handler: async () => {
|
|
301
|
+
const stats = orchestrator.getContextStats();
|
|
302
|
+
const appInterface = getAppInterface();
|
|
303
|
+
if (!stats.needsCompaction) {
|
|
304
|
+
appInterface?.addMessage({
|
|
305
|
+
id: Date.now().toString(),
|
|
306
|
+
type: "system",
|
|
307
|
+
content: `Context: ${stats.totalMessages} messages, ${stats.tokens.toLocaleString()} tokens (${stats.utilizationPercent.toFixed(1)}% of limit), ${stats.conversationTurns} turns. No compaction needed.`,
|
|
308
|
+
});
|
|
309
|
+
return "continue";
|
|
310
|
+
}
|
|
311
|
+
const result = await orchestrator.compactContext();
|
|
312
|
+
appInterface?.addMessage({
|
|
313
|
+
id: Date.now().toString(),
|
|
314
|
+
type: "system",
|
|
315
|
+
content: `✅ Context compacted: ${result.stats.removedCount} messages removed\n` +
|
|
316
|
+
`Before: ${result.stats.tokensBeforeCompaction.toLocaleString()} tokens\n` +
|
|
317
|
+
`After: ${result.stats.tokensAfterCompaction.toLocaleString()} tokens\n` +
|
|
318
|
+
`Strategy: ${result.stats.strategy}` +
|
|
319
|
+
(result.stats.summaryGenerated
|
|
320
|
+
? "\n📝 LLM summary generated"
|
|
321
|
+
: ""),
|
|
322
|
+
});
|
|
323
|
+
updateContextFromOrchestrator();
|
|
324
|
+
appInterface?.updateContext(chatContext);
|
|
325
|
+
return "continue";
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "exit",
|
|
330
|
+
description: "Exit the session (saves progress)",
|
|
331
|
+
handler: async () => "exit",
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: "goto",
|
|
335
|
+
description: "Direct the agent to a specific URL",
|
|
336
|
+
handler: async (args, orchestrator) => {
|
|
337
|
+
if (args.length === 0) {
|
|
338
|
+
getAppInterface()?.addMessage({
|
|
339
|
+
id: Date.now().toString(),
|
|
340
|
+
type: "system",
|
|
341
|
+
content: "Usage: /goto <url>",
|
|
342
|
+
});
|
|
343
|
+
return "continue";
|
|
344
|
+
}
|
|
345
|
+
const url = args[0];
|
|
346
|
+
getAppInterface()?.addMessage({
|
|
347
|
+
id: Date.now().toString(),
|
|
348
|
+
type: "system",
|
|
349
|
+
content: `🚀 Navigating to ${url}...`,
|
|
350
|
+
});
|
|
351
|
+
void handleUserMessage(`Navigate to ${url} and tell me what you see.`);
|
|
352
|
+
return "continue";
|
|
353
|
+
},
|
|
354
|
+
},
|
|
204
355
|
{
|
|
205
356
|
name: "help",
|
|
206
357
|
description: "Show available slash commands",
|
|
@@ -275,6 +426,72 @@ export const chatInk = new Command()
|
|
|
275
426
|
return "continue";
|
|
276
427
|
},
|
|
277
428
|
},
|
|
429
|
+
{
|
|
430
|
+
name: "report",
|
|
431
|
+
description: "Force the agent to generate a final report",
|
|
432
|
+
handler: async (args, orchestrator) => {
|
|
433
|
+
getAppInterface()?.addMessage({
|
|
434
|
+
id: Date.now().toString(),
|
|
435
|
+
type: "system",
|
|
436
|
+
content: "📑 Generating report based on session history...",
|
|
437
|
+
});
|
|
438
|
+
void handleUserMessage("Please generate a final report of your findings for this session.");
|
|
439
|
+
return "continue";
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: "scan",
|
|
444
|
+
description: "Instruct the agent to scan the page for interactive elements",
|
|
445
|
+
handler: async () => {
|
|
446
|
+
getAppInterface()?.addMessage({
|
|
447
|
+
id: Date.now().toString(),
|
|
448
|
+
type: "system",
|
|
449
|
+
content: "🔍 Scanning page for interactive elements...",
|
|
450
|
+
});
|
|
451
|
+
void handleUserMessage("Scan the current page and list all interactive elements you find.");
|
|
452
|
+
return "continue";
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: "screenshot",
|
|
457
|
+
description: "Force the agent to take a screenshot and show the state",
|
|
458
|
+
handler: async () => {
|
|
459
|
+
getAppInterface()?.addMessage({
|
|
460
|
+
id: Date.now().toString(),
|
|
461
|
+
type: "system",
|
|
462
|
+
content: "📸 Capturing fresh screenshot...",
|
|
463
|
+
});
|
|
464
|
+
void handleUserMessage("Please take a screenshot of the current page and tell me what's visible.");
|
|
465
|
+
return "continue";
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: "where",
|
|
470
|
+
description: "Get the current URL and page title from the agent",
|
|
471
|
+
handler: async () => {
|
|
472
|
+
getAppInterface()?.addMessage({
|
|
473
|
+
id: Date.now().toString(),
|
|
474
|
+
type: "system",
|
|
475
|
+
content: "📍 Checking location...",
|
|
476
|
+
});
|
|
477
|
+
void handleUserMessage("What is the current URL and page title?");
|
|
478
|
+
return "continue";
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: "reset",
|
|
483
|
+
description: "Reset session and clear all messages",
|
|
484
|
+
handler: async (args, orchestrator) => {
|
|
485
|
+
orchestrator.cancel();
|
|
486
|
+
getAppInterface()?.addMessage({
|
|
487
|
+
id: Date.now().toString(),
|
|
488
|
+
type: "system",
|
|
489
|
+
content: "🧹 Session reset. History cleared.",
|
|
490
|
+
});
|
|
491
|
+
// Note: In a real app we'd want to tell the orchestrator to reset its internal state too
|
|
492
|
+
return "continue";
|
|
493
|
+
},
|
|
494
|
+
},
|
|
278
495
|
{
|
|
279
496
|
name: "session",
|
|
280
497
|
description: "Display session metadata and storage path",
|
|
@@ -293,14 +510,6 @@ export const chatInk = new Command()
|
|
|
293
510
|
return "continue";
|
|
294
511
|
},
|
|
295
512
|
},
|
|
296
|
-
{
|
|
297
|
-
name: "clear",
|
|
298
|
-
description: "Clear the terminal and redraw the header",
|
|
299
|
-
handler: async () => {
|
|
300
|
-
console.clear();
|
|
301
|
-
return "continue";
|
|
302
|
-
},
|
|
303
|
-
},
|
|
304
513
|
{
|
|
305
514
|
name: "stop",
|
|
306
515
|
description: "Stop the assistant (same as ESC)",
|
|
@@ -310,23 +519,28 @@ export const chatInk = new Command()
|
|
|
310
519
|
},
|
|
311
520
|
},
|
|
312
521
|
{
|
|
313
|
-
name: "
|
|
314
|
-
description: "
|
|
522
|
+
name: "logout",
|
|
523
|
+
description: "Log out and clear local credentials",
|
|
315
524
|
handler: async () => {
|
|
316
|
-
|
|
317
|
-
|
|
525
|
+
try {
|
|
526
|
+
const { clearAuthToken } = await import("../lib/config.js");
|
|
527
|
+
clearAuthToken();
|
|
528
|
+
getAppInterface()?.addMessage({
|
|
529
|
+
id: Date.now().toString(),
|
|
530
|
+
type: "system",
|
|
531
|
+
content: "✅ Logged out. Credentials cleared.",
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
catch (e) {
|
|
535
|
+
getAppInterface()?.addMessage({
|
|
536
|
+
id: Date.now().toString(),
|
|
537
|
+
type: "system",
|
|
538
|
+
content: `❌ Failed to logout: ${e?.message || String(e)}`,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
return "exit";
|
|
318
542
|
},
|
|
319
543
|
},
|
|
320
|
-
{
|
|
321
|
-
name: "exit",
|
|
322
|
-
description: "Exit the chat session",
|
|
323
|
-
handler: async () => "exit",
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
name: "quit",
|
|
327
|
-
description: "Alias for /exit",
|
|
328
|
-
handler: async () => "exit",
|
|
329
|
-
},
|
|
330
544
|
{
|
|
331
545
|
name: "model",
|
|
332
546
|
description: "Switch AI model (Pro tier required)",
|
|
@@ -350,8 +564,12 @@ export const chatInk = new Command()
|
|
|
350
564
|
if (args.length === 0) {
|
|
351
565
|
const modelsList = availableModels
|
|
352
566
|
.map((m) => {
|
|
353
|
-
const usesInfo = m.usesRemaining !== null
|
|
354
|
-
|
|
567
|
+
const usesInfo = m.usesRemaining !== null
|
|
568
|
+
? ` (${m.usesRemaining} free uses left)`
|
|
569
|
+
: "";
|
|
570
|
+
const creditInfo = m.credits > 0
|
|
571
|
+
? ` - ${m.credits} credit${m.credits !== 1 ? "s" : ""}/msg`
|
|
572
|
+
: "";
|
|
355
573
|
return ` ${m.id} - ${m.name}${creditInfo}${usesInfo}`;
|
|
356
574
|
})
|
|
357
575
|
.join("\n");
|
|
@@ -385,7 +603,9 @@ export const chatInk = new Command()
|
|
|
385
603
|
appInterface?.addMessage({
|
|
386
604
|
id: Date.now().toString(),
|
|
387
605
|
type: "system",
|
|
388
|
-
content: model
|
|
606
|
+
content: model
|
|
607
|
+
? `❌ ${model.name} is unavailable`
|
|
608
|
+
: `❌ Unknown model: ${modelId}`,
|
|
389
609
|
});
|
|
390
610
|
return "continue";
|
|
391
611
|
}
|
|
@@ -397,13 +617,13 @@ export const chatInk = new Command()
|
|
|
397
617
|
let statusInfo = "";
|
|
398
618
|
if (model.credits > 0) {
|
|
399
619
|
if (isPaidPlan) {
|
|
400
|
-
statusInfo = ` (${model.credits} credit${model.credits !== 1 ?
|
|
620
|
+
statusInfo = ` (${model.credits} credit${model.credits !== 1 ? "s" : ""}/msg, ${planTier} plan)`;
|
|
401
621
|
}
|
|
402
622
|
else if (creditBalance > 0) {
|
|
403
|
-
statusInfo = ` (${model.credits} credit${model.credits !== 1 ?
|
|
623
|
+
statusInfo = ` (${model.credits} credit${model.credits !== 1 ? "s" : ""}/msg, ${creditBalance} credits remaining)`;
|
|
404
624
|
}
|
|
405
625
|
else {
|
|
406
|
-
statusInfo = ` (${model.credits} credit${model.credits !== 1 ?
|
|
626
|
+
statusInfo = ` (${model.credits} credit${model.credits !== 1 ? "s" : ""}/msg)`;
|
|
407
627
|
}
|
|
408
628
|
}
|
|
409
629
|
appInterface?.addMessage({
|
|
@@ -423,29 +643,6 @@ export const chatInk = new Command()
|
|
|
423
643
|
}
|
|
424
644
|
},
|
|
425
645
|
},
|
|
426
|
-
{
|
|
427
|
-
name: "logout",
|
|
428
|
-
description: "Log out and clear local credentials",
|
|
429
|
-
handler: async () => {
|
|
430
|
-
try {
|
|
431
|
-
const { clearAuthToken } = await import("../lib/config.js");
|
|
432
|
-
clearAuthToken();
|
|
433
|
-
getAppInterface()?.addMessage({
|
|
434
|
-
id: Date.now().toString(),
|
|
435
|
-
type: "system",
|
|
436
|
-
content: "✅ Logged out. Credentials cleared.",
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
catch (e) {
|
|
440
|
-
getAppInterface()?.addMessage({
|
|
441
|
-
id: Date.now().toString(),
|
|
442
|
-
type: "system",
|
|
443
|
-
content: `❌ Failed to logout: ${e?.message || String(e)}`,
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
return "exit";
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
646
|
];
|
|
450
647
|
const commandMap = new Map(slashCommands.map((command) => [command.name, command.handler]));
|
|
451
648
|
const executeSlashCommand = async (cmd, args) => {
|
|
@@ -508,7 +705,7 @@ export const chatInk = new Command()
|
|
|
508
705
|
};
|
|
509
706
|
const handleExit = () => {
|
|
510
707
|
cleanupOrchestratorListeners();
|
|
511
|
-
console.
|
|
708
|
+
console.error(chalk.green("\n✓ Session saved. Run 'ttt resume' to continue."));
|
|
512
709
|
process.exit(0);
|
|
513
710
|
};
|
|
514
711
|
// Prepare slash commands for the UI
|
|
@@ -519,21 +716,59 @@ export const chatInk = new Command()
|
|
|
519
716
|
label: `/${cmd.name}`,
|
|
520
717
|
}));
|
|
521
718
|
// Render the INK app
|
|
522
|
-
const { waitUntilExit } = render(React.createElement(ChatApp, {
|
|
719
|
+
const { waitUntilExit, clear } = render(React.createElement(ChatApp, {
|
|
523
720
|
user: userName,
|
|
524
721
|
context: chatContext,
|
|
525
722
|
slashCommands: slashCommandsForUI,
|
|
526
723
|
onUserMessage: handleUserMessage,
|
|
527
724
|
onSlashCommand: executeSlashCommand,
|
|
725
|
+
onSetTestingMode: async (mode) => {
|
|
726
|
+
orchestrator.setTestingMode(mode);
|
|
727
|
+
updateContextFromOrchestrator();
|
|
728
|
+
getAppInterface()?.updateContext(chatContext);
|
|
729
|
+
// Save mode preference for next session
|
|
730
|
+
await configManager.setLastMode(mode);
|
|
731
|
+
},
|
|
528
732
|
onExit: handleExit,
|
|
529
733
|
onCancel: handleUserCancel,
|
|
530
|
-
|
|
734
|
+
initialInput: options.initialInput,
|
|
735
|
+
}), {
|
|
736
|
+
stdin: process.stdin,
|
|
737
|
+
stdout: process.stdout,
|
|
738
|
+
debug: false,
|
|
739
|
+
patchConsole: false,
|
|
740
|
+
});
|
|
741
|
+
// Clear and re-render on terminal resize to prevent ghosting
|
|
742
|
+
const handleResize = () => {
|
|
743
|
+
// Clear entire terminal including scrollback
|
|
744
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
745
|
+
// Reset Ink's line counter and force re-render
|
|
746
|
+
clear();
|
|
747
|
+
// Force a re-render by triggering state change
|
|
748
|
+
const appInterface = getAppInterface();
|
|
749
|
+
if (appInterface) {
|
|
750
|
+
appInterface.setStatus({ text: "", type: "info" });
|
|
751
|
+
setTimeout(() => appInterface.clearStatus(), 0);
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
process.stdout.on("resize", handleResize);
|
|
531
755
|
await waitUntilExit();
|
|
756
|
+
process.stdout.off("resize", handleResize);
|
|
532
757
|
cleanupOrchestratorListeners();
|
|
533
758
|
}
|
|
534
759
|
catch (error) {
|
|
535
760
|
console.error(chalk.red(`Error: ${error.message}`));
|
|
536
761
|
process.exit(1);
|
|
537
762
|
}
|
|
763
|
+
}
|
|
764
|
+
export const chatInk = new Command()
|
|
765
|
+
.name("chat")
|
|
766
|
+
.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).")
|
|
767
|
+
.option("--mode <mode>", "Testing mode: ui, api, or code", "ui")
|
|
768
|
+
.option("--base-url <url>", "Base URL for UI testing", process.env.TTT_BASE_URL)
|
|
769
|
+
.option("--api-base-url <url>", "Base URL for API testing", process.env.TTT_API_BASE_URL)
|
|
770
|
+
.option("--session <id>", "Resume existing session by ID")
|
|
771
|
+
.action(async (options) => {
|
|
772
|
+
await runChatInk(options);
|
|
538
773
|
});
|
|
539
774
|
//# sourceMappingURL=chat-ink.js.map
|