@timetotest/cli 0.3.1 → 0.3.3
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 +12 -2
- package/dist/bin/ttt.js +0 -6
- package/dist/bin/ttt.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/commands/chat/ChatApp.js +229 -124
- package/dist/src/commands/chat/ChatApp.js.map +1 -1
- package/dist/src/commands/chat/OnboardingApp.js +13 -6
- package/dist/src/commands/chat/OnboardingApp.js.map +1 -1
- package/dist/src/commands/chat/components/ChatInput.js +22 -9
- package/dist/src/commands/chat/components/ChatInput.js.map +1 -1
- package/dist/src/commands/chat/components/ModelPicker.js +62 -0
- package/dist/src/commands/chat/components/ModelPicker.js.map +1 -0
- package/dist/src/commands/chat/components/TodoPanel.js +71 -0
- package/dist/src/commands/chat/components/TodoPanel.js.map +1 -0
- package/dist/src/commands/chat/components/ToolCallDisplay.js +45 -28
- package/dist/src/commands/chat/components/ToolCallDisplay.js.map +1 -1
- package/dist/src/commands/chat/tool-message-matcher.js +20 -0
- package/dist/src/commands/chat/tool-message-matcher.js.map +1 -0
- package/dist/src/commands/chat-ink.js +200 -341
- package/dist/src/commands/chat-ink.js.map +1 -1
- package/dist/src/commands/login.js +21 -5
- package/dist/src/commands/login.js.map +1 -1
- package/dist/src/lib/__tests__/code-mode-integration.test.js +45 -356
- package/dist/src/lib/__tests__/code-mode-integration.test.js.map +1 -1
- package/dist/src/lib/__tests__/login-auth-flow.test.js +14 -0
- package/dist/src/lib/__tests__/login-auth-flow.test.js.map +1 -0
- package/dist/src/lib/__tests__/model-picker-window.test.js +20 -0
- package/dist/src/lib/__tests__/model-picker-window.test.js.map +1 -0
- package/dist/src/lib/__tests__/tool-executor-mode-gating.test.js +46 -0
- package/dist/src/lib/__tests__/tool-executor-mode-gating.test.js.map +1 -0
- package/dist/src/lib/__tests__/tool-message-matcher.test.js +26 -0
- package/dist/src/lib/__tests__/tool-message-matcher.test.js.map +1 -0
- package/dist/src/lib/__tests__/ui-browser-integration.test.js +35 -0
- package/dist/src/lib/__tests__/ui-browser-integration.test.js.map +1 -0
- package/dist/src/lib/agent-orchestrator.js +16 -717
- package/dist/src/lib/agent-orchestrator.js.map +1 -1
- package/dist/src/lib/backend-loop-client.js +377 -0
- package/dist/src/lib/backend-loop-client.js.map +1 -0
- package/dist/src/lib/cli-tool-manifest.js +71 -0
- package/dist/src/lib/cli-tool-manifest.js.map +1 -0
- package/dist/src/lib/config.js +60 -9
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/conversation/turns.js +58 -0
- package/dist/src/lib/conversation/turns.js.map +1 -0
- package/dist/src/lib/events.js +20 -4
- package/dist/src/lib/events.js.map +1 -1
- package/dist/src/lib/http.js +7 -2
- package/dist/src/lib/http.js.map +1 -1
- package/dist/src/lib/prompts/templates.js +18 -0
- package/dist/src/lib/prompts/templates.js.map +1 -1
- package/dist/src/lib/session-manager.js +74 -33
- package/dist/src/lib/session-manager.js.map +1 -1
- package/dist/src/lib/socket.js +15 -3
- package/dist/src/lib/socket.js.map +1 -1
- package/dist/src/lib/todo.js +7 -0
- package/dist/src/lib/todo.js.map +1 -0
- package/dist/src/lib/tool-executor.js +196 -51
- package/dist/src/lib/tool-executor.js.map +1 -1
- package/dist/src/lib/tui/events.js +10 -9
- package/dist/src/lib/tui/events.js.map +1 -1
- package/dist/src/lib/tui/ink/components/TimetoTestLogo.js +1 -1
- package/dist/src/lib/tui/ink/components/TimetoTestLogo.js.map +1 -1
- package/dist/src/lib/utils/json.js +15 -0
- package/dist/src/lib/utils/json.js.map +1 -0
- package/package.json +1 -1
- package/dist/src/commands/report.js +0 -25
- package/dist/src/commands/report.js.map +0 -1
- package/dist/src/commands/restart.js +0 -17
- package/dist/src/commands/restart.js.map +0 -1
- package/dist/src/commands/share.js +0 -18
- package/dist/src/commands/share.js.map +0 -1
- package/dist/src/lib/context-compactor.js +0 -310
- package/dist/src/lib/context-compactor.js.map +0 -1
- package/dist/src/lib/tool-registry.js +0 -971
- package/dist/src/lib/tool-registry.js.map +0 -1
- package/dist/src/lib/tool-result-pruner.js +0 -384
- package/dist/src/lib/tool-result-pruner.js.map +0 -1
|
@@ -9,42 +9,58 @@ import { render } from "ink";
|
|
|
9
9
|
import { AgentOrchestrator } from "../lib/agent-orchestrator.js";
|
|
10
10
|
import { resolveApiUrl, getAuthToken, configManager } from "../lib/config.js";
|
|
11
11
|
import { ChatApp } from "./chat/ChatApp.js";
|
|
12
|
-
import { performInteractiveLogin } from "./login.js";
|
|
12
|
+
import { performCliSignup, performInteractiveLogin } from "./login.js";
|
|
13
13
|
import { resolveTestingMode } from "../lib/testing-mode.js";
|
|
14
14
|
export async function runChatInk(options) {
|
|
15
15
|
try {
|
|
16
16
|
const apiUrl = resolveApiUrl();
|
|
17
17
|
let token = getAuthToken();
|
|
18
|
+
const isAuthError = (error) => {
|
|
19
|
+
const status = error?.response?.status;
|
|
20
|
+
if (status === 401 || status === 403)
|
|
21
|
+
return true;
|
|
22
|
+
const message = String(error?.message || "");
|
|
23
|
+
return message.includes("status code 401") || message.includes("status code 403");
|
|
24
|
+
};
|
|
18
25
|
if (!token) {
|
|
19
|
-
// Use TUI onboarding instead of direct login
|
|
20
|
-
// We need to dynamically import OnboardingApp to avoid top-level side effects if possible
|
|
21
26
|
const { OnboardingApp } = await import("./chat/OnboardingApp.js");
|
|
22
|
-
|
|
23
|
-
await new Promise((resolve, reject) => {
|
|
27
|
+
const promptOnboardingAction = () => new Promise((resolve) => {
|
|
24
28
|
const { unmount } = render(React.createElement(OnboardingApp, {
|
|
25
|
-
|
|
29
|
+
onSignIn: () => {
|
|
26
30
|
unmount();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
}
|
|
31
|
+
resolve("signin");
|
|
32
|
+
},
|
|
33
|
+
onSignUp: () => {
|
|
34
|
+
unmount();
|
|
35
|
+
resolve("signup");
|
|
41
36
|
},
|
|
42
37
|
onExit: () => {
|
|
43
38
|
unmount();
|
|
44
|
-
|
|
39
|
+
resolve("exit");
|
|
45
40
|
},
|
|
46
41
|
}));
|
|
47
42
|
});
|
|
43
|
+
while (!token) {
|
|
44
|
+
const action = await promptOnboardingAction();
|
|
45
|
+
if (action === "exit") {
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
if (action === "signup") {
|
|
50
|
+
await performCliSignup();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
await performInteractiveLogin();
|
|
54
|
+
}
|
|
55
|
+
token = getAuthToken();
|
|
56
|
+
if (!token) {
|
|
57
|
+
throw new Error("Authentication completed but no token was saved.");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error(`❌ Authentication failed: ${error?.message || error}\nRetry with Sign In/Sign Up or press Exit.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
48
64
|
}
|
|
49
65
|
// Fetch user information
|
|
50
66
|
const { createHttpClient } = await import("../lib/http.js");
|
|
@@ -58,24 +74,30 @@ export async function runChatInk(options) {
|
|
|
58
74
|
catch (error) {
|
|
59
75
|
const status = error?.response?.status;
|
|
60
76
|
if (status === 401 || status === 403) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
let reauthed = false;
|
|
78
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
79
|
+
try {
|
|
80
|
+
await performInteractiveLogin();
|
|
81
|
+
token = getAuthToken();
|
|
82
|
+
if (!token) {
|
|
83
|
+
throw new Error("No token returned after login.");
|
|
84
|
+
}
|
|
85
|
+
http = createHttpClient();
|
|
86
|
+
const retryUserResp = await http.get("/api/v1/auth/me");
|
|
87
|
+
userName =
|
|
88
|
+
retryUserResp.data?.email ||
|
|
89
|
+
retryUserResp.data?.display_name ||
|
|
90
|
+
"Unknown";
|
|
91
|
+
reauthed = true;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
catch (reauthError) {
|
|
95
|
+
const suffix = attempt < 2 ? " Retrying once..." : " Continuing with limited context.";
|
|
96
|
+
console.error(`❌ Re-authentication failed: ${reauthError?.message || reauthError}.${suffix}`);
|
|
69
97
|
}
|
|
70
|
-
http = createHttpClient();
|
|
71
|
-
// Retry fetching user info
|
|
72
|
-
const userResp = await http.get("/api/v1/auth/me");
|
|
73
|
-
userName =
|
|
74
|
-
userResp.data?.email || userResp.data?.display_name || "Unknown";
|
|
75
98
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
process.exit(1);
|
|
99
|
+
if (!reauthed) {
|
|
100
|
+
userName = "Unknown";
|
|
79
101
|
}
|
|
80
102
|
}
|
|
81
103
|
else {
|
|
@@ -114,6 +136,15 @@ export async function runChatInk(options) {
|
|
|
114
136
|
apiBaseUrl: options.apiBaseUrl,
|
|
115
137
|
};
|
|
116
138
|
const orchestrator = new AgentOrchestrator(config, options.session);
|
|
139
|
+
try {
|
|
140
|
+
const preferredModel = await configManager.getPreferredModel();
|
|
141
|
+
if (preferredModel) {
|
|
142
|
+
await orchestrator.setModel(preferredModel);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Ignore preference load failures; session can proceed with backend default.
|
|
147
|
+
}
|
|
117
148
|
let userCancelled = false; // Track when the user cancels an operation
|
|
118
149
|
function getAppInterface() {
|
|
119
150
|
return globalThis.__chatAppInterface;
|
|
@@ -168,6 +199,11 @@ export async function runChatInk(options) {
|
|
|
168
199
|
const appInterface = getAppInterface();
|
|
169
200
|
const toolName = data.data?.tool || data.tool;
|
|
170
201
|
const toolArgs = data.data?.arguments || {};
|
|
202
|
+
const callId = data.data?.call_id || data.call_id;
|
|
203
|
+
if (toolName === "todo") {
|
|
204
|
+
// Render todo changes in the dedicated panel (avoid transcript spam).
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
171
207
|
// Add tool call as a system message with tool metadata
|
|
172
208
|
appInterface?.addMessage({
|
|
173
209
|
id: `tool-${Date.now()}`,
|
|
@@ -176,6 +212,7 @@ export async function runChatInk(options) {
|
|
|
176
212
|
metadata: {
|
|
177
213
|
isTool: true,
|
|
178
214
|
toolName,
|
|
215
|
+
toolCallId: callId,
|
|
179
216
|
toolArgs,
|
|
180
217
|
isLoading: true,
|
|
181
218
|
},
|
|
@@ -192,9 +229,28 @@ export async function runChatInk(options) {
|
|
|
192
229
|
const appInterface = getAppInterface();
|
|
193
230
|
const toolName = data.data?.tool || data.tool;
|
|
194
231
|
const result = data.data?.result;
|
|
232
|
+
const callId = data.data?.call_id || data.call_id;
|
|
233
|
+
if (toolName === "todo") {
|
|
234
|
+
const todoData = result?.data || result?.todo_list_snapshot || result?.todo_list || {};
|
|
235
|
+
const items = Array.isArray(todoData.todos) ? todoData.todos : [];
|
|
236
|
+
const snapshot = items.length > 0 || typeof todoData.title === "string" || typeof todoData.description === "string"
|
|
237
|
+
? {
|
|
238
|
+
title: typeof todoData.title === "string" ? todoData.title : undefined,
|
|
239
|
+
description: typeof todoData.description === "string" ? todoData.description : undefined,
|
|
240
|
+
todos: items,
|
|
241
|
+
updated_at: typeof todoData.updated_at === "string"
|
|
242
|
+
? todoData.updated_at
|
|
243
|
+
: new Date().toISOString(),
|
|
244
|
+
current_id: typeof todoData.current_id === "string" ? todoData.current_id : undefined,
|
|
245
|
+
}
|
|
246
|
+
: null;
|
|
247
|
+
appInterface?.setTodos?.(snapshot);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
195
250
|
// Update the last tool message with the result
|
|
196
251
|
appInterface?.updateLastToolMessage?.({
|
|
197
252
|
toolName,
|
|
253
|
+
callId,
|
|
198
254
|
result,
|
|
199
255
|
isLoading: false,
|
|
200
256
|
});
|
|
@@ -241,117 +297,8 @@ export async function runChatInk(options) {
|
|
|
241
297
|
chatContext.toolCount = history.filter((msg) => msg.role === "tool").length;
|
|
242
298
|
};
|
|
243
299
|
updateContextFromOrchestrator();
|
|
244
|
-
// Define slash commands (
|
|
300
|
+
// Define slash commands (only user-approved set)
|
|
245
301
|
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
|
-
},
|
|
355
302
|
{
|
|
356
303
|
name: "help",
|
|
357
304
|
description: "Show available slash commands",
|
|
@@ -367,149 +314,6 @@ export async function runChatInk(options) {
|
|
|
367
314
|
return "continue";
|
|
368
315
|
},
|
|
369
316
|
},
|
|
370
|
-
{
|
|
371
|
-
name: "history",
|
|
372
|
-
description: "Show the last few conversation turns",
|
|
373
|
-
handler: async () => {
|
|
374
|
-
const history = orchestrator.getConversationHistory();
|
|
375
|
-
const recent = history.slice(-10);
|
|
376
|
-
const historyText = recent
|
|
377
|
-
.map((message) => {
|
|
378
|
-
const roleLabel = message.role === "user"
|
|
379
|
-
? "You"
|
|
380
|
-
: message.role === "assistant"
|
|
381
|
-
? "Agent"
|
|
382
|
-
: message.role.toUpperCase();
|
|
383
|
-
const content = typeof message.content === "string"
|
|
384
|
-
? message.content
|
|
385
|
-
: JSON.stringify(message.content, null, 2);
|
|
386
|
-
return `${roleLabel}: ${content}`;
|
|
387
|
-
})
|
|
388
|
-
.join("\n");
|
|
389
|
-
getAppInterface()?.addMessage({
|
|
390
|
-
id: Date.now().toString(),
|
|
391
|
-
type: "system",
|
|
392
|
-
content: `History (${history.length} total):\n${historyText}`,
|
|
393
|
-
});
|
|
394
|
-
return "continue";
|
|
395
|
-
},
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
name: "progress",
|
|
399
|
-
description: "Show conversation summary and tool usage",
|
|
400
|
-
handler: async () => {
|
|
401
|
-
const history = orchestrator.getConversationHistory();
|
|
402
|
-
const totals = history.reduce((acc, message) => {
|
|
403
|
-
acc.total += 1;
|
|
404
|
-
acc[message.role] = (acc[message.role] || 0) + 1;
|
|
405
|
-
return acc;
|
|
406
|
-
}, {
|
|
407
|
-
total: 0,
|
|
408
|
-
user: 0,
|
|
409
|
-
assistant: 0,
|
|
410
|
-
tool: 0,
|
|
411
|
-
system: 0,
|
|
412
|
-
});
|
|
413
|
-
let progressText = `Messages: ${totals.total} (you ${totals.user}, agent ${totals.assistant}, tool ${totals.tool})`;
|
|
414
|
-
if (totals.tool > 0) {
|
|
415
|
-
const toolNames = history
|
|
416
|
-
.filter((msg) => msg.role === "tool" && msg.toolCall)
|
|
417
|
-
.map((msg) => msg.toolCall.name);
|
|
418
|
-
const uniqueTools = Array.from(new Set(toolNames));
|
|
419
|
-
progressText += `\nTools used: ${uniqueTools.join(", ")}`;
|
|
420
|
-
}
|
|
421
|
-
getAppInterface()?.addMessage({
|
|
422
|
-
id: Date.now().toString(),
|
|
423
|
-
type: "system",
|
|
424
|
-
content: progressText,
|
|
425
|
-
});
|
|
426
|
-
return "continue";
|
|
427
|
-
},
|
|
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
|
-
},
|
|
495
|
-
{
|
|
496
|
-
name: "session",
|
|
497
|
-
description: "Display session metadata and storage path",
|
|
498
|
-
handler: async () => {
|
|
499
|
-
const sessionText = [
|
|
500
|
-
`ID: ${chatContext.sessionId}`,
|
|
501
|
-
`Path: ${chatContext.sessionPath}`,
|
|
502
|
-
`Mode: ${chatContext.mode}`,
|
|
503
|
-
`Testing mode: ${chatContext.testingMode}`,
|
|
504
|
-
].join("\n");
|
|
505
|
-
getAppInterface()?.addMessage({
|
|
506
|
-
id: Date.now().toString(),
|
|
507
|
-
type: "system",
|
|
508
|
-
content: sessionText,
|
|
509
|
-
});
|
|
510
|
-
return "continue";
|
|
511
|
-
},
|
|
512
|
-
},
|
|
513
317
|
{
|
|
514
318
|
name: "stop",
|
|
515
319
|
description: "Stop the assistant (same as ESC)",
|
|
@@ -518,29 +322,6 @@ export async function runChatInk(options) {
|
|
|
518
322
|
return "continue";
|
|
519
323
|
},
|
|
520
324
|
},
|
|
521
|
-
{
|
|
522
|
-
name: "logout",
|
|
523
|
-
description: "Log out and clear local credentials",
|
|
524
|
-
handler: async () => {
|
|
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";
|
|
542
|
-
},
|
|
543
|
-
},
|
|
544
325
|
{
|
|
545
326
|
name: "model",
|
|
546
327
|
description: "Switch AI model (Pro tier required)",
|
|
@@ -562,37 +343,59 @@ export async function runChatInk(options) {
|
|
|
562
343
|
}
|
|
563
344
|
// No args - show available models
|
|
564
345
|
if (args.length === 0) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
: ""
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
: "";
|
|
573
|
-
return ` ${m.id} - ${m.name}${creditInfo}${usesInfo}`;
|
|
574
|
-
})
|
|
575
|
-
.join("\n");
|
|
576
|
-
// Show appropriate balance/plan info
|
|
577
|
-
const planTier = data.planTier || "free";
|
|
578
|
-
const isPaidPlan = planTier !== "free";
|
|
579
|
-
const creditBalance = data.creditBalance || 0;
|
|
580
|
-
let balanceInfo = "";
|
|
581
|
-
if (isPaidPlan) {
|
|
582
|
-
// Paid plan users - show plan tier
|
|
583
|
-
balanceInfo = `Plan: ${planTier.charAt(0).toUpperCase() + planTier.slice(1)}`;
|
|
584
|
-
if (creditBalance > 0) {
|
|
585
|
-
balanceInfo += ` | Extra credits: ${creditBalance}`;
|
|
586
|
-
}
|
|
346
|
+
if (availableModels.length === 0) {
|
|
347
|
+
appInterface?.addMessage({
|
|
348
|
+
id: Date.now().toString(),
|
|
349
|
+
type: "system",
|
|
350
|
+
content: "No models are currently available for your account.",
|
|
351
|
+
});
|
|
352
|
+
return "continue";
|
|
587
353
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
354
|
+
if (appInterface?.promptModelSelection) {
|
|
355
|
+
const currentModelForPicker = orchestrator.getCurrentModel?.() || data.currentModel;
|
|
356
|
+
const selectedModelId = await appInterface.promptModelSelection({
|
|
357
|
+
models: availableModels,
|
|
358
|
+
currentModel: currentModelForPicker,
|
|
359
|
+
planTier: data.planTier,
|
|
360
|
+
creditBalance: data.creditBalance,
|
|
361
|
+
});
|
|
362
|
+
if (!selectedModelId) {
|
|
363
|
+
appInterface?.addMessage({
|
|
364
|
+
id: Date.now().toString(),
|
|
365
|
+
type: "system",
|
|
366
|
+
content: "Model selection cancelled.",
|
|
367
|
+
});
|
|
368
|
+
return "continue";
|
|
369
|
+
}
|
|
370
|
+
const selectedModel = availableModels.find((m) => m.id === selectedModelId);
|
|
371
|
+
if (!selectedModel) {
|
|
372
|
+
appInterface?.addMessage({
|
|
373
|
+
id: Date.now().toString(),
|
|
374
|
+
type: "system",
|
|
375
|
+
content: `❌ Unknown model: ${selectedModelId}`,
|
|
376
|
+
});
|
|
377
|
+
return "continue";
|
|
378
|
+
}
|
|
379
|
+
await orchestrator.setModel(selectedModelId);
|
|
380
|
+
await configManager.setPreferredModel(selectedModelId);
|
|
381
|
+
const statusInfo = selectedModel.credits > 0
|
|
382
|
+
? ` (${selectedModel.credits} credit${selectedModel.credits !== 1 ? "s" : ""}/msg)`
|
|
383
|
+
: "";
|
|
384
|
+
appInterface?.addMessage({
|
|
385
|
+
id: Date.now().toString(),
|
|
386
|
+
type: "system",
|
|
387
|
+
content: `✅ Switched to ${selectedModel.name}${statusInfo}`,
|
|
388
|
+
});
|
|
389
|
+
return "continue";
|
|
591
390
|
}
|
|
391
|
+
// Fallback for non-INK contexts
|
|
392
|
+
const modelsList = availableModels
|
|
393
|
+
.map((m) => ` ${m.id} - ${m.name}`)
|
|
394
|
+
.join("\n");
|
|
592
395
|
appInterface?.addMessage({
|
|
593
396
|
id: Date.now().toString(),
|
|
594
397
|
type: "system",
|
|
595
|
-
content: `Available Models:\n${modelsList}\n\
|
|
398
|
+
content: `Available Models:\n${modelsList}\n\nUsage: /model <model-id>`,
|
|
596
399
|
});
|
|
597
400
|
return "continue";
|
|
598
401
|
}
|
|
@@ -610,6 +413,7 @@ export async function runChatInk(options) {
|
|
|
610
413
|
return "continue";
|
|
611
414
|
}
|
|
612
415
|
await orchestrator.setModel(modelId);
|
|
416
|
+
await configManager.setPreferredModel(modelId);
|
|
613
417
|
// Show appropriate success message
|
|
614
418
|
const planTier = data.planTier || "free";
|
|
615
419
|
const isPaidPlan = planTier !== "free";
|
|
@@ -643,6 +447,34 @@ export async function runChatInk(options) {
|
|
|
643
447
|
}
|
|
644
448
|
},
|
|
645
449
|
},
|
|
450
|
+
{
|
|
451
|
+
name: "logout",
|
|
452
|
+
description: "Log out and clear local credentials",
|
|
453
|
+
handler: async () => {
|
|
454
|
+
try {
|
|
455
|
+
const { clearAuthToken } = await import("../lib/config.js");
|
|
456
|
+
clearAuthToken();
|
|
457
|
+
getAppInterface()?.addMessage({
|
|
458
|
+
id: Date.now().toString(),
|
|
459
|
+
type: "system",
|
|
460
|
+
content: "✅ Logged out. Credentials cleared.",
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
catch (e) {
|
|
464
|
+
getAppInterface()?.addMessage({
|
|
465
|
+
id: Date.now().toString(),
|
|
466
|
+
type: "system",
|
|
467
|
+
content: `❌ Failed to logout: ${e?.message || String(e)}`,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return "exit";
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: "exit",
|
|
475
|
+
description: "Exit the session (saves progress)",
|
|
476
|
+
handler: async () => "exit",
|
|
477
|
+
},
|
|
646
478
|
];
|
|
647
479
|
const commandMap = new Map(slashCommands.map((command) => [command.name, command.handler]));
|
|
648
480
|
const executeSlashCommand = async (cmd, args) => {
|
|
@@ -665,7 +497,34 @@ export async function runChatInk(options) {
|
|
|
665
497
|
text: "Agent thinking...",
|
|
666
498
|
type: "loading",
|
|
667
499
|
});
|
|
668
|
-
|
|
500
|
+
let response = "";
|
|
501
|
+
try {
|
|
502
|
+
response = await orchestrator.chat(message);
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
if (!isAuthError(error)) {
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
appInterface?.setStatus({
|
|
509
|
+
text: "Authentication required. Opening login...",
|
|
510
|
+
type: "info",
|
|
511
|
+
});
|
|
512
|
+
try {
|
|
513
|
+
await performInteractiveLogin();
|
|
514
|
+
token = getAuthToken();
|
|
515
|
+
if (!token) {
|
|
516
|
+
throw new Error("Login did not return a valid token");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
catch (loginError) {
|
|
520
|
+
throw new Error(`Authentication required. Login failed: ${loginError?.message || loginError}. Retry your message or run /logout then sign in again.`);
|
|
521
|
+
}
|
|
522
|
+
appInterface?.setStatus({
|
|
523
|
+
text: "Authenticated. Retrying...",
|
|
524
|
+
type: "loading",
|
|
525
|
+
});
|
|
526
|
+
response = await orchestrator.chat(message);
|
|
527
|
+
}
|
|
669
528
|
if (userCancelled) {
|
|
670
529
|
return;
|
|
671
530
|
}
|
|
@@ -705,7 +564,7 @@ export async function runChatInk(options) {
|
|
|
705
564
|
};
|
|
706
565
|
const handleExit = () => {
|
|
707
566
|
cleanupOrchestratorListeners();
|
|
708
|
-
console.error(chalk.green("\n✓ Session saved. Run 'ttt
|
|
567
|
+
console.error(chalk.green("\n✓ Session saved. Run 'ttt chat --session <id>' to continue."));
|
|
709
568
|
process.exit(0);
|
|
710
569
|
};
|
|
711
570
|
// Prepare slash commands for the UI
|