@timetotest/cli 0.2.3 → 0.2.4
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/dist/package.json +8 -5
- package/dist/src/commands/chat/ChatApp.js +30 -42
- 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 +39 -17
- package/dist/src/commands/chat/components/ChatInput.js.map +1 -1
- 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-ink.js +118 -9
- package/dist/src/commands/chat-ink.js.map +1 -1
- package/dist/src/commands/start-test.js +61 -0
- package/dist/src/commands/start-test.js.map +1 -1
- package/dist/src/commands/stream/StreamApp.js +127 -0
- package/dist/src/commands/stream/StreamApp.js.map +1 -0
- package/dist/src/commands/stream.js +43 -8
- package/dist/src/commands/stream.js.map +1 -1
- package/dist/src/commands/test/TestRunApp.js +183 -0
- package/dist/src/commands/test/TestRunApp.js.map +1 -0
- package/dist/src/commands/test.js +97 -0
- package/dist/src/commands/test.js.map +1 -1
- package/dist/src/lib/agent-orchestrator.js +5 -0
- package/dist/src/lib/agent-orchestrator.js.map +1 -1
- package/dist/src/lib/local-tools/ui/playwright-mcp.js +1 -1
- package/dist/src/lib/tui/ink/theme.js +21 -0
- package/dist/src/lib/tui/ink/theme.js.map +1 -0
- package/package.json +8 -5
- 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/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
package/dist/src/commands/ask.js
DELETED
|
@@ -1,991 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import ora from "ora";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import { createHttpClient } from "../lib/http.js";
|
|
6
|
-
import { connectForAsk, subscribeAsk, stopSession } from "../lib/socket.js";
|
|
7
|
-
import { registerUnifiedEventLogging, canonicalizeEventType, } from "../lib/events.js";
|
|
8
|
-
import { promptInBox, printDivider, formatStatusSegments, accentText, highlightText, printBanner, printUserBubble, printStatusBanner, } from "../lib/tui.js";
|
|
9
|
-
import { getAuthToken, getUserLastProject, setUserLastProject, } from "../lib/config.js";
|
|
10
|
-
import { clearAuthToken } from "../lib/config.js";
|
|
11
|
-
import { performInteractiveLogin } from "./login.js";
|
|
12
|
-
function formatResponseText(text) {
|
|
13
|
-
if (!text)
|
|
14
|
-
return text;
|
|
15
|
-
// Convert HTML-like tags to CLI-friendly formatting
|
|
16
|
-
let formatted = text
|
|
17
|
-
// Convert <list> to a simple list
|
|
18
|
-
.replace(/<list>/g, "")
|
|
19
|
-
.replace(/<\/list>/g, "")
|
|
20
|
-
// Convert <item> to bullet points
|
|
21
|
-
.replace(/<item>/g, "• ")
|
|
22
|
-
.replace(/<\/item>/g, "")
|
|
23
|
-
// Convert <strong> to bold
|
|
24
|
-
.replace(/<strong>/g, chalk.bold(""))
|
|
25
|
-
.replace(/<\/strong>/g, chalk.reset(""))
|
|
26
|
-
// Convert <em> to italic
|
|
27
|
-
.replace(/<em>/g, chalk.italic(""))
|
|
28
|
-
.replace(/<\/em>/g, chalk.reset(""))
|
|
29
|
-
// Convert <code> to monospace
|
|
30
|
-
.replace(/<code>/g, chalk.gray(""))
|
|
31
|
-
.replace(/<\/code>/g, chalk.reset(""))
|
|
32
|
-
// Convert line breaks to proper newlines
|
|
33
|
-
.replace(/\n/g, "\n");
|
|
34
|
-
return formatted;
|
|
35
|
-
}
|
|
36
|
-
export const ask = new Command("ask")
|
|
37
|
-
.description("Start interactive chat with TimetoTest AI agent")
|
|
38
|
-
.option("--project-id <id>", "Project ID to use for the conversation")
|
|
39
|
-
.option("--conversation-id <id>", "Continue existing conversation")
|
|
40
|
-
.action(async (opts) => {
|
|
41
|
-
try {
|
|
42
|
-
// Check authentication
|
|
43
|
-
const token = getAuthToken();
|
|
44
|
-
if (!token) {
|
|
45
|
-
console.log(chalk.yellow("You are not logged in. Opening browser to authenticate…"));
|
|
46
|
-
try {
|
|
47
|
-
await performInteractiveLogin();
|
|
48
|
-
}
|
|
49
|
-
catch (e) {
|
|
50
|
-
console.log(chalk.red(e?.message || "Login failed"));
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
let http = createHttpClient();
|
|
55
|
-
const drawIntro = () => {
|
|
56
|
-
console.clear();
|
|
57
|
-
printBanner();
|
|
58
|
-
console.log(chalk.white("Tips for getting started:"));
|
|
59
|
-
console.log(`${chalk.dim("1.")} ${chalk.white("Ask questions, inspect files, or run commands.")}`);
|
|
60
|
-
console.log(`${chalk.dim("2.")} ${chalk.white("Be specific for the clearest answers.")}`);
|
|
61
|
-
console.log(`${chalk.dim("3.")} ${chalk.white(`Share extra context with ${accentText("@path/to/file")}.`)}`);
|
|
62
|
-
console.log(`${chalk.dim("4.")} ${chalk.white(`${accentText("/help")} shows slash commands and shortcuts.`)}`);
|
|
63
|
-
console.log();
|
|
64
|
-
};
|
|
65
|
-
// Gemini-like intro: banner + tips, then single active input box (no duplicate)
|
|
66
|
-
drawIntro();
|
|
67
|
-
let conversationId = opts.conversationId;
|
|
68
|
-
let projectId;
|
|
69
|
-
if (opts.projectId !== undefined) {
|
|
70
|
-
const parsedProjectId = Number(opts.projectId);
|
|
71
|
-
if (!Number.isInteger(parsedProjectId)) {
|
|
72
|
-
console.log(chalk.red(`❌ Invalid project id '${opts.projectId}'. Please provide a numeric value.`));
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
projectId = parsedProjectId;
|
|
76
|
-
}
|
|
77
|
-
let sessionInfoWarningShown = false;
|
|
78
|
-
let lastKnownUser = "Unknown";
|
|
79
|
-
let lastKnownUserKey;
|
|
80
|
-
let lastKnownProject = projectId ? `Project ${projectId}` : "Not set";
|
|
81
|
-
const renderSessionSummary = async (refresh = true, retried = false) => {
|
|
82
|
-
if (refresh) {
|
|
83
|
-
try {
|
|
84
|
-
const userResp = await http.get("/api/v1/auth/me");
|
|
85
|
-
lastKnownUserKey =
|
|
86
|
-
userResp.data.email || String(userResp.data.user_id || "");
|
|
87
|
-
lastKnownUser =
|
|
88
|
-
userResp.data.email || userResp.data.display_name || "Unknown";
|
|
89
|
-
if (projectId) {
|
|
90
|
-
const projectResp = await http.get(`/api/v1/projects/${projectId}`);
|
|
91
|
-
lastKnownProject =
|
|
92
|
-
projectResp.data.name || `Project ${projectId}`;
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
lastKnownProject = "Not set";
|
|
96
|
-
}
|
|
97
|
-
sessionInfoWarningShown = false;
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
if (!retried) {
|
|
101
|
-
// Attempt to re-auth then retry once
|
|
102
|
-
const msg = chalk.gray("Re-authenticating to refresh session information…");
|
|
103
|
-
console.log(msg);
|
|
104
|
-
try {
|
|
105
|
-
await performInteractiveLogin();
|
|
106
|
-
http = createHttpClient();
|
|
107
|
-
await renderSessionSummary(true, true);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
catch (reauthErr) {
|
|
111
|
-
// fall through to warning message below
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (!sessionInfoWarningShown) {
|
|
115
|
-
console.log(chalk.gray("ℹ️ Unable to refresh session info from the API."));
|
|
116
|
-
sessionInfoWarningShown = true;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
console.log(`${chalk.dim("•")} ${chalk.gray("Signed in as")} ${chalk.white(lastKnownUser)}`);
|
|
121
|
-
console.log(`${chalk.dim("•")} ${chalk.gray("Project")} ${accentText(lastKnownProject)} ${chalk.gray("· use /switch to change")}`);
|
|
122
|
-
console.log();
|
|
123
|
-
};
|
|
124
|
-
let spinner = null;
|
|
125
|
-
let messageCount = 1;
|
|
126
|
-
let socket;
|
|
127
|
-
let userCancelled = false;
|
|
128
|
-
const sendAskStop = () => {
|
|
129
|
-
if (userCancelled)
|
|
130
|
-
return;
|
|
131
|
-
userCancelled = true;
|
|
132
|
-
try {
|
|
133
|
-
if (spinner) {
|
|
134
|
-
spinner.stop();
|
|
135
|
-
spinner = null;
|
|
136
|
-
}
|
|
137
|
-
console.log(chalk.yellow("🛑 Cancelled — the assistant has stopped. Type whenever you're ready."));
|
|
138
|
-
if (socket && conversationId) {
|
|
139
|
-
stopSession(socket, {
|
|
140
|
-
session_kind: "ask",
|
|
141
|
-
ids: { conversation_id: conversationId },
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
catch { }
|
|
146
|
-
};
|
|
147
|
-
// Function to switch projects
|
|
148
|
-
const switchProject = async (searchTerm) => {
|
|
149
|
-
try {
|
|
150
|
-
const fetchSpinner = ora("Fetching your projects...").start();
|
|
151
|
-
const projectsResp = await http.get("/api/v1/projects");
|
|
152
|
-
const projectsRaw = projectsResp.data;
|
|
153
|
-
const projects = Array.isArray(projectsRaw?.items)
|
|
154
|
-
? projectsRaw.items
|
|
155
|
-
: Array.isArray(projectsRaw)
|
|
156
|
-
? projectsRaw
|
|
157
|
-
: [];
|
|
158
|
-
fetchSpinner.succeed("Projects loaded!");
|
|
159
|
-
if (!projects || projects.length === 0) {
|
|
160
|
-
console.log(chalk.yellow("❌ No projects available."));
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const findProject = (query) => {
|
|
164
|
-
const normalized = query.trim().toLowerCase();
|
|
165
|
-
return projects.find((project) => {
|
|
166
|
-
if (!project)
|
|
167
|
-
return false;
|
|
168
|
-
const name = String(project.name || "").toLowerCase();
|
|
169
|
-
const idMatch = String(project.id || "") === query.trim();
|
|
170
|
-
return idMatch || name.includes(normalized);
|
|
171
|
-
});
|
|
172
|
-
};
|
|
173
|
-
let selectedProject;
|
|
174
|
-
if (searchTerm && searchTerm.trim().length > 0) {
|
|
175
|
-
selectedProject = findProject(searchTerm.trim());
|
|
176
|
-
if (!selectedProject) {
|
|
177
|
-
console.log(chalk.yellow(`No project matched "${searchTerm}". Showing the list instead.`));
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (!selectedProject) {
|
|
181
|
-
console.log();
|
|
182
|
-
printDivider("Available Projects");
|
|
183
|
-
projects.forEach((project, index) => {
|
|
184
|
-
const isCurrent = project.id === projectId;
|
|
185
|
-
const marker = isCurrent ? chalk.green("●") : chalk.gray("○");
|
|
186
|
-
console.log(`${marker} ${chalk.cyan(project.name)}`);
|
|
187
|
-
console.log(` ${chalk.gray(project.description || "No description")}`);
|
|
188
|
-
if (index < projects.length - 1)
|
|
189
|
-
console.log();
|
|
190
|
-
});
|
|
191
|
-
printDivider();
|
|
192
|
-
const { value: selection, aborted } = await promptInBox({
|
|
193
|
-
label: "Switch Project",
|
|
194
|
-
placeholder: "Start typing a project name or ID...",
|
|
195
|
-
hint: "Enter to switch · leave empty to cancel",
|
|
196
|
-
infoLines: [
|
|
197
|
-
chalk.gray("Matching is case-insensitive and accepts partial names."),
|
|
198
|
-
chalk.gray("Press Ctrl+C to cancel the switch."),
|
|
199
|
-
],
|
|
200
|
-
});
|
|
201
|
-
if (aborted || selection.length === 0) {
|
|
202
|
-
console.log(chalk.gray("Project switch cancelled."));
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
selectedProject = findProject(selection);
|
|
206
|
-
if (!selectedProject) {
|
|
207
|
-
console.log(chalk.red(`❌ Project "${selection}" not found.`));
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (!selectedProject) {
|
|
212
|
-
console.log(chalk.red("❌ Unable to determine project."));
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (selectedProject.id === projectId) {
|
|
216
|
-
console.log(chalk.yellow(`ℹ️ Already on project "${selectedProject.name}".`));
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
projectId = selectedProject.id;
|
|
220
|
-
lastKnownProject = selectedProject.name || `Project ${projectId}`;
|
|
221
|
-
conversationId = undefined;
|
|
222
|
-
messageCount = 1;
|
|
223
|
-
// Do not create a conversation yet; wait for the next user message
|
|
224
|
-
if (socket) {
|
|
225
|
-
try {
|
|
226
|
-
socket.removeAllListeners?.();
|
|
227
|
-
await socket.disconnect();
|
|
228
|
-
}
|
|
229
|
-
catch (disconnectError) {
|
|
230
|
-
console.log(chalk.gray(`ℹ️ Reconnecting socket: ${disconnectError?.message || disconnectError}`));
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (lastKnownUserKey && typeof projectId === "number") {
|
|
234
|
-
setUserLastProject(lastKnownUserKey, projectId);
|
|
235
|
-
}
|
|
236
|
-
await renderSessionSummary();
|
|
237
|
-
console.log(chalk.green(`✅ Switched to project: ${selectedProject.name}`));
|
|
238
|
-
console.log();
|
|
239
|
-
}
|
|
240
|
-
catch (error) {
|
|
241
|
-
console.log(chalk.red(`❌ Failed to switch project: ${error?.message || error}`));
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
const slashCommands = [];
|
|
245
|
-
const helpCommand = {
|
|
246
|
-
name: "help",
|
|
247
|
-
description: "Show available slash commands",
|
|
248
|
-
aliases: ["?", "commands", "h"],
|
|
249
|
-
run: async () => {
|
|
250
|
-
console.log(chalk.white("Slash commands:"));
|
|
251
|
-
slashCommands.forEach((cmd) => {
|
|
252
|
-
const aliasText = cmd.aliases?.length
|
|
253
|
-
? chalk.gray(` (aliases: ${cmd.aliases
|
|
254
|
-
.map((alias) => `/${alias}`)
|
|
255
|
-
.join(", ")})`)
|
|
256
|
-
: "";
|
|
257
|
-
console.log(` ${accentText(`/${cmd.name}`)} ${chalk.gray(cmd.description)}${aliasText}`);
|
|
258
|
-
});
|
|
259
|
-
console.log(chalk.gray("Prefix commands with '/' (for example, /switch or /summary)."));
|
|
260
|
-
console.log();
|
|
261
|
-
return "continue";
|
|
262
|
-
},
|
|
263
|
-
};
|
|
264
|
-
const summaryCommand = {
|
|
265
|
-
name: "summary",
|
|
266
|
-
description: "Display the latest session information",
|
|
267
|
-
aliases: ["session", "status"],
|
|
268
|
-
run: async () => {
|
|
269
|
-
await renderSessionSummary();
|
|
270
|
-
return "continue";
|
|
271
|
-
},
|
|
272
|
-
};
|
|
273
|
-
const switchCommand = {
|
|
274
|
-
name: "switch",
|
|
275
|
-
description: "Switch to a different project",
|
|
276
|
-
aliases: ["project", "s"],
|
|
277
|
-
run: async (args) => {
|
|
278
|
-
const query = args.join(" ");
|
|
279
|
-
await switchProject(query);
|
|
280
|
-
return "continue";
|
|
281
|
-
},
|
|
282
|
-
};
|
|
283
|
-
const clearCommand = {
|
|
284
|
-
name: "clear",
|
|
285
|
-
description: "Clear the screen and redraw the header",
|
|
286
|
-
aliases: ["cls"],
|
|
287
|
-
run: async () => {
|
|
288
|
-
drawIntro();
|
|
289
|
-
await renderSessionSummary(false);
|
|
290
|
-
return "continue";
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
const projectsCommand = {
|
|
294
|
-
name: "projects",
|
|
295
|
-
description: "List your projects",
|
|
296
|
-
aliases: ["list-projects", "proj"],
|
|
297
|
-
run: async () => {
|
|
298
|
-
try {
|
|
299
|
-
const resp = await http.get("/api/v1/projects");
|
|
300
|
-
const items = Array.isArray(resp.data?.items)
|
|
301
|
-
? resp.data.items
|
|
302
|
-
: Array.isArray(resp.data)
|
|
303
|
-
? resp.data
|
|
304
|
-
: [];
|
|
305
|
-
if (!items.length) {
|
|
306
|
-
console.log(chalk.yellow("No projects found."));
|
|
307
|
-
return "continue";
|
|
308
|
-
}
|
|
309
|
-
console.log();
|
|
310
|
-
printDivider("Projects");
|
|
311
|
-
items.forEach((p, i) => {
|
|
312
|
-
const isCurrent = p.id === projectId;
|
|
313
|
-
const marker = isCurrent ? chalk.green("●") : chalk.gray("○");
|
|
314
|
-
const name = p.name || `Project ${p.id}`;
|
|
315
|
-
const def = p.is_default ? chalk.cyan(" (default)") : "";
|
|
316
|
-
console.log(`${marker} ${chalk.cyan(name)}${def}`);
|
|
317
|
-
console.log(` ${chalk.gray(p.description || "No description")}`);
|
|
318
|
-
if (i < items.length - 1)
|
|
319
|
-
console.log();
|
|
320
|
-
});
|
|
321
|
-
printDivider();
|
|
322
|
-
}
|
|
323
|
-
catch (e) {
|
|
324
|
-
console.log(chalk.red(`❌ Failed to list projects: ${e?.message || e}`));
|
|
325
|
-
}
|
|
326
|
-
return "continue";
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
const testsCommand = {
|
|
330
|
-
name: "tests",
|
|
331
|
-
description: "List recent tests",
|
|
332
|
-
aliases: ["list-tests"],
|
|
333
|
-
run: async () => {
|
|
334
|
-
try {
|
|
335
|
-
const params = projectId ? `?project_id=${projectId}` : "";
|
|
336
|
-
const resp = await http.get(`/api/v1/my-tests${params}`);
|
|
337
|
-
const items = Array.isArray(resp.data?.items)
|
|
338
|
-
? resp.data.items
|
|
339
|
-
: Array.isArray(resp.data)
|
|
340
|
-
? resp.data
|
|
341
|
-
: [];
|
|
342
|
-
if (!items.length) {
|
|
343
|
-
console.log(chalk.yellow("No tests yet."));
|
|
344
|
-
return "continue";
|
|
345
|
-
}
|
|
346
|
-
console.log();
|
|
347
|
-
printDivider("Tests");
|
|
348
|
-
items.forEach((t, i) => {
|
|
349
|
-
const id = t.id;
|
|
350
|
-
const status = t.status || "";
|
|
351
|
-
const url = t.url || "";
|
|
352
|
-
const type = t.test_type || "";
|
|
353
|
-
console.log(`${chalk.gray("•")} ${chalk.white(`#${id}`)} ${chalk.gray(`(${type} · ${status})`)} ${chalk.cyan(url)}`);
|
|
354
|
-
if (i < items.length - 1)
|
|
355
|
-
console.log();
|
|
356
|
-
});
|
|
357
|
-
printDivider();
|
|
358
|
-
}
|
|
359
|
-
catch (e) {
|
|
360
|
-
console.log(chalk.red(`❌ Failed to list tests: ${e?.message || e}`));
|
|
361
|
-
}
|
|
362
|
-
return "continue";
|
|
363
|
-
},
|
|
364
|
-
};
|
|
365
|
-
const statusCommand = {
|
|
366
|
-
name: "status",
|
|
367
|
-
description: "Get test status: /status <testId>",
|
|
368
|
-
aliases: ["test-status"],
|
|
369
|
-
run: async (args) => {
|
|
370
|
-
const id = Number(args[0]);
|
|
371
|
-
if (!id || Number.isNaN(id)) {
|
|
372
|
-
console.log(chalk.yellow("Usage: /status <testId>"));
|
|
373
|
-
return "continue";
|
|
374
|
-
}
|
|
375
|
-
try {
|
|
376
|
-
const resp = await http.get(`/api/v1/tests/${id}/status`);
|
|
377
|
-
const s = resp.data;
|
|
378
|
-
console.log();
|
|
379
|
-
printDivider(`Test #${id} Status`);
|
|
380
|
-
console.log(formatStatusSegments([
|
|
381
|
-
{ label: "status", value: s.status, color: chalk.cyan },
|
|
382
|
-
{
|
|
383
|
-
label: "progress",
|
|
384
|
-
value: `${s.progress_percentage}%`,
|
|
385
|
-
color: chalk.green,
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
label: "stage",
|
|
389
|
-
value: s.current_stage || s.currentStage || "",
|
|
390
|
-
color: chalk.white,
|
|
391
|
-
},
|
|
392
|
-
]));
|
|
393
|
-
printDivider();
|
|
394
|
-
}
|
|
395
|
-
catch (e) {
|
|
396
|
-
console.log(chalk.red(`❌ Failed to get status: ${e?.message || e}`));
|
|
397
|
-
}
|
|
398
|
-
return "continue";
|
|
399
|
-
},
|
|
400
|
-
};
|
|
401
|
-
const reportCommand = {
|
|
402
|
-
name: "report",
|
|
403
|
-
description: "Generate a report: /report <testId> [html|json]",
|
|
404
|
-
aliases: ["make-report"],
|
|
405
|
-
run: async (args) => {
|
|
406
|
-
const id = Number(args[0]);
|
|
407
|
-
const fmt = (args[1] || "html").toLowerCase();
|
|
408
|
-
if (!id || Number.isNaN(id)) {
|
|
409
|
-
console.log(chalk.yellow("Usage: /report <testId> [html|json]"));
|
|
410
|
-
return "continue";
|
|
411
|
-
}
|
|
412
|
-
try {
|
|
413
|
-
const resp = await http.get(`/api/v1/reports/`, {
|
|
414
|
-
// axios get with params? route is POST /reports/, but it expects query params via function signature
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
catch (_) {
|
|
418
|
-
// Fallback to POST with query params as defined in backend
|
|
419
|
-
}
|
|
420
|
-
try {
|
|
421
|
-
const resp = await http.post(`/api/v1/reports/`, null, {
|
|
422
|
-
params: { test_id: id, report_format: fmt },
|
|
423
|
-
});
|
|
424
|
-
const r = resp.data;
|
|
425
|
-
console.log();
|
|
426
|
-
printDivider(`Report for Test #${id}`);
|
|
427
|
-
console.log(`${chalk.gray("report id")} ${chalk.white(r.id)}`);
|
|
428
|
-
if (r.file_path)
|
|
429
|
-
console.log(`${chalk.gray("file")} ${chalk.cyan(r.file_path)}`);
|
|
430
|
-
printDivider();
|
|
431
|
-
}
|
|
432
|
-
catch (e) {
|
|
433
|
-
console.log(chalk.red(`❌ Failed to generate report: ${e?.message || e}`));
|
|
434
|
-
}
|
|
435
|
-
return "continue";
|
|
436
|
-
},
|
|
437
|
-
};
|
|
438
|
-
const startTestCommand = {
|
|
439
|
-
name: "test",
|
|
440
|
-
description: "Start a test: /test <prompt> [ui|api] [docs:<openapi-url>]",
|
|
441
|
-
aliases: ["start-test", "start"],
|
|
442
|
-
run: async (args) => {
|
|
443
|
-
// Parse type and optional docs: url tokens; the rest is the prompt
|
|
444
|
-
let type = "ui";
|
|
445
|
-
let docsUrl;
|
|
446
|
-
const tokens = Array.isArray(args) ? [...args] : [];
|
|
447
|
-
const remaining = [];
|
|
448
|
-
for (const t of tokens) {
|
|
449
|
-
const low = (t || "").toLowerCase();
|
|
450
|
-
if (low === "ui" || low === "api") {
|
|
451
|
-
type = low;
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
if (low.startsWith("docs:")) {
|
|
455
|
-
const v = t.slice(5);
|
|
456
|
-
if (v && v.startsWith("http"))
|
|
457
|
-
docsUrl = v;
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
remaining.push(t);
|
|
461
|
-
}
|
|
462
|
-
let prompt = remaining.join(" ").trim();
|
|
463
|
-
if (!prompt) {
|
|
464
|
-
const { value, aborted } = await promptInBox({
|
|
465
|
-
label: "Start Test",
|
|
466
|
-
placeholder: "Describe what to test (prompt)",
|
|
467
|
-
hint: "Enter to confirm · Ctrl+C to cancel",
|
|
468
|
-
fullWidth: true,
|
|
469
|
-
});
|
|
470
|
-
if (aborted || !value.trim())
|
|
471
|
-
return "continue";
|
|
472
|
-
prompt = value.trim();
|
|
473
|
-
}
|
|
474
|
-
try {
|
|
475
|
-
const resp = await http.post(`/api/v1/start-test`, {
|
|
476
|
-
// No URL: let backend derive from project settings
|
|
477
|
-
url: undefined,
|
|
478
|
-
test_type: type,
|
|
479
|
-
project_id: projectId,
|
|
480
|
-
user_prompt: prompt,
|
|
481
|
-
test_metadata: {
|
|
482
|
-
docs_url: docsUrl,
|
|
483
|
-
surface: "cli",
|
|
484
|
-
},
|
|
485
|
-
});
|
|
486
|
-
const data = resp.data;
|
|
487
|
-
console.log();
|
|
488
|
-
printDivider("Test Started");
|
|
489
|
-
console.log(formatStatusSegments([
|
|
490
|
-
{ label: "id", value: String(data.test_id), color: chalk.white },
|
|
491
|
-
{ label: "status", value: data.status, color: chalk.cyan },
|
|
492
|
-
{
|
|
493
|
-
label: "project",
|
|
494
|
-
value: lastKnownProject,
|
|
495
|
-
color: highlightText,
|
|
496
|
-
},
|
|
497
|
-
]));
|
|
498
|
-
printDivider();
|
|
499
|
-
}
|
|
500
|
-
catch (e) {
|
|
501
|
-
console.log(chalk.red(`❌ Failed to start test: ${e?.message || e}`));
|
|
502
|
-
}
|
|
503
|
-
return "continue";
|
|
504
|
-
},
|
|
505
|
-
};
|
|
506
|
-
const conversationsCommand = {
|
|
507
|
-
name: "conversations",
|
|
508
|
-
description: "List your recent conversations",
|
|
509
|
-
aliases: ["convos", "sessions"],
|
|
510
|
-
run: async () => {
|
|
511
|
-
try {
|
|
512
|
-
const params = projectId ? `?project_id=${projectId}` : "";
|
|
513
|
-
const resp = await http.get(`/api/v1/ask/sessions${params}`);
|
|
514
|
-
const items = Array.isArray(resp.data?.items)
|
|
515
|
-
? resp.data.items
|
|
516
|
-
: Array.isArray(resp.data)
|
|
517
|
-
? resp.data
|
|
518
|
-
: [];
|
|
519
|
-
if (!items.length) {
|
|
520
|
-
console.log(chalk.yellow("No conversations yet."));
|
|
521
|
-
return "continue";
|
|
522
|
-
}
|
|
523
|
-
console.log();
|
|
524
|
-
printDivider("Conversations");
|
|
525
|
-
items.forEach((c, i) => {
|
|
526
|
-
const id = c.conversation_id || c.id;
|
|
527
|
-
const created = c.created_at || "";
|
|
528
|
-
const status = c.status || "";
|
|
529
|
-
const title = c.goal || c.title || "";
|
|
530
|
-
console.log(`${chalk.gray("•")} ${chalk.white(title || id)} ${chalk.gray(`(${status})`)}`);
|
|
531
|
-
if (created)
|
|
532
|
-
console.log(` ${chalk.gray(created)}`);
|
|
533
|
-
if (i < items.length - 1)
|
|
534
|
-
console.log();
|
|
535
|
-
});
|
|
536
|
-
printDivider();
|
|
537
|
-
}
|
|
538
|
-
catch (e) {
|
|
539
|
-
console.log(chalk.red(`❌ Failed to list conversations: ${e?.message || e}`));
|
|
540
|
-
}
|
|
541
|
-
return "continue";
|
|
542
|
-
},
|
|
543
|
-
};
|
|
544
|
-
const exitCommand = {
|
|
545
|
-
name: "exit",
|
|
546
|
-
description: "End the conversation",
|
|
547
|
-
aliases: ["quit", "bye"],
|
|
548
|
-
run: async () => "exit",
|
|
549
|
-
};
|
|
550
|
-
const logoutCommand = {
|
|
551
|
-
name: "logout",
|
|
552
|
-
description: "Log out and clear local credentials",
|
|
553
|
-
aliases: ["signout"],
|
|
554
|
-
run: async () => {
|
|
555
|
-
try {
|
|
556
|
-
clearAuthToken();
|
|
557
|
-
console.log(chalk.green("✅ Logged out. Credentials cleared."));
|
|
558
|
-
}
|
|
559
|
-
catch (e) {
|
|
560
|
-
console.log(chalk.red(`❌ Failed to logout: ${e?.message || String(e)}`));
|
|
561
|
-
}
|
|
562
|
-
return "exit";
|
|
563
|
-
},
|
|
564
|
-
};
|
|
565
|
-
const stopCommand = {
|
|
566
|
-
name: "stop",
|
|
567
|
-
description: "Stop the assistant for this conversation (same as ESC)",
|
|
568
|
-
aliases: ["cancel", "halt"],
|
|
569
|
-
run: async () => {
|
|
570
|
-
sendAskStop();
|
|
571
|
-
return "continue";
|
|
572
|
-
},
|
|
573
|
-
};
|
|
574
|
-
slashCommands.push(helpCommand, summaryCommand, switchCommand, clearCommand, testsCommand, statusCommand, reportCommand, startTestCommand, projectsCommand, stopCommand, conversationsCommand, logoutCommand, exitCommand);
|
|
575
|
-
const commandLookup = new Map();
|
|
576
|
-
slashCommands.forEach((command) => {
|
|
577
|
-
commandLookup.set(command.name, command);
|
|
578
|
-
command.aliases?.forEach((alias) => {
|
|
579
|
-
commandLookup.set(alias, command);
|
|
580
|
-
});
|
|
581
|
-
});
|
|
582
|
-
const executeSlashCommand = async (rawInput) => {
|
|
583
|
-
const trimmed = rawInput.trim();
|
|
584
|
-
if (!trimmed) {
|
|
585
|
-
console.log(chalk.gray("Type / to see the command list."));
|
|
586
|
-
return "continue";
|
|
587
|
-
}
|
|
588
|
-
const [keyword, ...rawArgs] = trimmed.split(/\s+/);
|
|
589
|
-
const command = commandLookup.get(keyword.toLowerCase());
|
|
590
|
-
if (!command) {
|
|
591
|
-
console.log(chalk.yellow(`Unknown command: /${keyword}`));
|
|
592
|
-
console.log(chalk.gray(`Type / to see available commands.`));
|
|
593
|
-
return "continue";
|
|
594
|
-
}
|
|
595
|
-
return await command.run(rawArgs);
|
|
596
|
-
};
|
|
597
|
-
// Load session summary without creating conversation yet
|
|
598
|
-
const summarySpinner = ora("Loading session info...").start();
|
|
599
|
-
// Auto-select last used project for this user, else default
|
|
600
|
-
try {
|
|
601
|
-
const me = await http.get("/api/v1/auth/me");
|
|
602
|
-
lastKnownUserKey = me.data?.email || String(me.data?.user_id || "");
|
|
603
|
-
if (!projectId && lastKnownUserKey) {
|
|
604
|
-
const remembered = getUserLastProject(lastKnownUserKey);
|
|
605
|
-
if (remembered) {
|
|
606
|
-
projectId = remembered;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
// Fetch user's projects and pick the default if present; otherwise the most recent
|
|
610
|
-
const projectsResp = await http.get("/api/v1/projects");
|
|
611
|
-
const projectsRaw = projectsResp.data;
|
|
612
|
-
const projects = Array.isArray(projectsRaw?.items)
|
|
613
|
-
? projectsRaw.items
|
|
614
|
-
: Array.isArray(projectsRaw)
|
|
615
|
-
? projectsRaw
|
|
616
|
-
: [];
|
|
617
|
-
if (!projectId && projects && projects.length > 0) {
|
|
618
|
-
const defaultProj = projects.find((p) => p?.is_default === true);
|
|
619
|
-
const selected = defaultProj || projects[0];
|
|
620
|
-
if (selected?.id) {
|
|
621
|
-
projectId = selected.id;
|
|
622
|
-
lastKnownProject = selected.name || `Project ${projectId}`;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
// If we loaded a remembered project, populate the name proactively
|
|
626
|
-
if (projectId &&
|
|
627
|
-
(!lastKnownProject || lastKnownProject === "Not set")) {
|
|
628
|
-
try {
|
|
629
|
-
const p = await http.get(`/api/v1/projects/${projectId}`);
|
|
630
|
-
lastKnownProject = p.data?.name || `Project ${projectId}`;
|
|
631
|
-
}
|
|
632
|
-
catch { }
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
catch (_) {
|
|
636
|
-
// non-fatal; continue without preselecting
|
|
637
|
-
}
|
|
638
|
-
await renderSessionSummary();
|
|
639
|
-
summarySpinner.stop();
|
|
640
|
-
printStatusBanner("Assistant ready. Type /help to see available commands.", "success");
|
|
641
|
-
const attachSocketHandlers = (socketInstance) => {
|
|
642
|
-
registerUnifiedEventLogging(socketInstance, (line) => console.log(line));
|
|
643
|
-
socketInstance.onAny?.((eventType, data) => {
|
|
644
|
-
const t = canonicalizeEventType(eventType) || eventType;
|
|
645
|
-
if (userCancelled)
|
|
646
|
-
return;
|
|
647
|
-
if (t === "thinking_started") {
|
|
648
|
-
if (spinner)
|
|
649
|
-
spinner.text = chalk.cyan("🤔 Thinking...");
|
|
650
|
-
}
|
|
651
|
-
else if (t === "thinking_stopped") {
|
|
652
|
-
if (spinner && spinner.text?.includes("Thinking"))
|
|
653
|
-
spinner.text = "Waiting for assistant...";
|
|
654
|
-
}
|
|
655
|
-
else if (t === "agent_thought") {
|
|
656
|
-
const msg = data?.data?.message || data?.data?.content || "Thinking...";
|
|
657
|
-
if (spinner)
|
|
658
|
-
spinner.text = chalk.cyan(`💭 ${msg}`);
|
|
659
|
-
}
|
|
660
|
-
else if (t === "tool_screenshot") {
|
|
661
|
-
const url = data?.data?.image_url || data?.data?.screenshot?.image_url;
|
|
662
|
-
if (url)
|
|
663
|
-
console.log(chalk.gray(`🖼️ Live screenshot: ${url}`));
|
|
664
|
-
}
|
|
665
|
-
else if (t === "tool_start") {
|
|
666
|
-
const message = data.data?.message || `Running ${data.data?.tool || "tool"}...`;
|
|
667
|
-
if (spinner)
|
|
668
|
-
spinner.text = chalk.blue(`⚙️ ${message}`);
|
|
669
|
-
}
|
|
670
|
-
else if (t === "tool_result") {
|
|
671
|
-
// Tool completion intentionally silent to reduce noise.
|
|
672
|
-
}
|
|
673
|
-
else if (t === "session_completed") {
|
|
674
|
-
if (spinner) {
|
|
675
|
-
spinner.stop();
|
|
676
|
-
spinner = null;
|
|
677
|
-
}
|
|
678
|
-
console.log();
|
|
679
|
-
printDivider("Assistant");
|
|
680
|
-
const summary = data.data?.summary || "No response";
|
|
681
|
-
console.log(formatResponseText(summary));
|
|
682
|
-
printDivider();
|
|
683
|
-
console.log();
|
|
684
|
-
}
|
|
685
|
-
else if (t === "session_error") {
|
|
686
|
-
if (spinner) {
|
|
687
|
-
spinner.fail("Something went wrong");
|
|
688
|
-
spinner = null;
|
|
689
|
-
}
|
|
690
|
-
printDivider("Assistant Error");
|
|
691
|
-
console.log(chalk.red(`❌ ${data.data?.error || "An unexpected issue occurred"}`));
|
|
692
|
-
printDivider();
|
|
693
|
-
console.log();
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
};
|
|
697
|
-
const establishSocket = (conversation) => {
|
|
698
|
-
const newSocket = connectForAsk();
|
|
699
|
-
attachSocketHandlers(newSocket);
|
|
700
|
-
subscribeAsk(newSocket, conversation);
|
|
701
|
-
return newSocket;
|
|
702
|
-
};
|
|
703
|
-
if (conversationId) {
|
|
704
|
-
socket = establishSocket(conversationId);
|
|
705
|
-
}
|
|
706
|
-
// Main conversation loop
|
|
707
|
-
const workspacePath = process.cwd();
|
|
708
|
-
const homeDir = os.homedir();
|
|
709
|
-
const displayPath = workspacePath.startsWith(homeDir)
|
|
710
|
-
? `~${workspacePath.slice(homeDir.length)}`
|
|
711
|
-
: workspacePath;
|
|
712
|
-
while (true) {
|
|
713
|
-
// Show input box for user message
|
|
714
|
-
const statusLine = formatStatusSegments([
|
|
715
|
-
{ label: "path", value: displayPath, color: accentText },
|
|
716
|
-
{ label: "project", value: lastKnownProject, color: highlightText },
|
|
717
|
-
]);
|
|
718
|
-
// Removed usage line per request
|
|
719
|
-
// Removed extra hint to keep UI clean
|
|
720
|
-
const getSlashSuggestions = (input) => {
|
|
721
|
-
const q = (input || "").replace(/^\//, "").toLowerCase();
|
|
722
|
-
const priority = {
|
|
723
|
-
test: 0,
|
|
724
|
-
switch: 1,
|
|
725
|
-
status: 2,
|
|
726
|
-
};
|
|
727
|
-
const items = slashCommands
|
|
728
|
-
.map((c) => ({
|
|
729
|
-
label: `/${c.name}`,
|
|
730
|
-
value: c.name,
|
|
731
|
-
description: c.description,
|
|
732
|
-
p: priority[c.name] ?? 100,
|
|
733
|
-
}))
|
|
734
|
-
.sort((a, b) => a.p - b.p || a.value.localeCompare(b.value));
|
|
735
|
-
if (!q)
|
|
736
|
-
return items;
|
|
737
|
-
return items
|
|
738
|
-
.filter((i) => i.value.toLowerCase().startsWith(q) ||
|
|
739
|
-
i.label.toLowerCase().includes(q))
|
|
740
|
-
.sort((a, b) => a.p - b.p || a.value.localeCompare(b.value));
|
|
741
|
-
};
|
|
742
|
-
const { value, aborted } = await promptInBox({
|
|
743
|
-
placeholder: "Type your message or @path/to/file",
|
|
744
|
-
footerLines: [
|
|
745
|
-
statusLine,
|
|
746
|
-
chalk.gray("Press ESC to stop the assistant"),
|
|
747
|
-
],
|
|
748
|
-
fullWidth: true,
|
|
749
|
-
getSuggestions: getSlashSuggestions,
|
|
750
|
-
renderSubmitted: (submitted) => {
|
|
751
|
-
try {
|
|
752
|
-
printUserBubble(submitted);
|
|
753
|
-
}
|
|
754
|
-
catch { }
|
|
755
|
-
},
|
|
756
|
-
});
|
|
757
|
-
if (aborted) {
|
|
758
|
-
console.log(chalk.gray("Session cancelled."));
|
|
759
|
-
break;
|
|
760
|
-
}
|
|
761
|
-
const userInput = value.trim();
|
|
762
|
-
const normalizedInput = userInput.toLowerCase();
|
|
763
|
-
if (!userInput) {
|
|
764
|
-
console.log(chalk.yellow("Please enter a message or type /exit to leave."));
|
|
765
|
-
continue;
|
|
766
|
-
}
|
|
767
|
-
// printUserBubble(userInput);
|
|
768
|
-
if (normalizedInput === "exit" || normalizedInput === "quit") {
|
|
769
|
-
console.log(chalk.gray("Goodbye! 👋"));
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
const legacyCommand = {
|
|
773
|
-
help: "help",
|
|
774
|
-
commands: "help",
|
|
775
|
-
"?": "help",
|
|
776
|
-
summary: "summary",
|
|
777
|
-
status: "summary",
|
|
778
|
-
switch: "switch",
|
|
779
|
-
clear: "clear",
|
|
780
|
-
cls: "clear",
|
|
781
|
-
}[normalizedInput];
|
|
782
|
-
if (legacyCommand) {
|
|
783
|
-
console.log(chalk.gray("Tip: commands work best with '/'. Running it for you now..."));
|
|
784
|
-
const outcome = await executeSlashCommand(legacyCommand);
|
|
785
|
-
if (outcome === "exit") {
|
|
786
|
-
console.log(chalk.gray("Goodbye! 👋"));
|
|
787
|
-
break;
|
|
788
|
-
}
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
if (userInput.startsWith("/")) {
|
|
792
|
-
const outcome = await executeSlashCommand(userInput.slice(1));
|
|
793
|
-
if (outcome === "exit") {
|
|
794
|
-
console.log(chalk.gray("Goodbye! 👋"));
|
|
795
|
-
break;
|
|
796
|
-
}
|
|
797
|
-
continue;
|
|
798
|
-
}
|
|
799
|
-
userCancelled = false;
|
|
800
|
-
// Create conversation and establish socket on first message
|
|
801
|
-
if (!conversationId) {
|
|
802
|
-
spinner = ora("Creating conversation...").start();
|
|
803
|
-
try {
|
|
804
|
-
const sessionResp = await http.post("/api/v1/ask/sessions", {
|
|
805
|
-
question: userInput,
|
|
806
|
-
project_id: projectId,
|
|
807
|
-
surface: "cli",
|
|
808
|
-
});
|
|
809
|
-
conversationId = sessionResp.data.conversation_id;
|
|
810
|
-
projectId = sessionResp.data.project_id;
|
|
811
|
-
if (lastKnownUserKey && typeof projectId === "number") {
|
|
812
|
-
setUserLastProject(lastKnownUserKey, projectId);
|
|
813
|
-
}
|
|
814
|
-
// Establish socket connection
|
|
815
|
-
socket = establishSocket(conversationId);
|
|
816
|
-
spinner.text = "Waiting for assistant...";
|
|
817
|
-
}
|
|
818
|
-
catch (e) {
|
|
819
|
-
const status = e?.response?.status;
|
|
820
|
-
if (status === 401) {
|
|
821
|
-
spinner.text = "Session expired. Re-authenticating...";
|
|
822
|
-
await performInteractiveLogin();
|
|
823
|
-
http = createHttpClient();
|
|
824
|
-
spinner.text = "Creating conversation...";
|
|
825
|
-
const sessionResp = await http.post("/api/v1/ask/sessions", {
|
|
826
|
-
question: userInput,
|
|
827
|
-
project_id: projectId,
|
|
828
|
-
surface: "cli",
|
|
829
|
-
});
|
|
830
|
-
conversationId = sessionResp.data.conversation_id;
|
|
831
|
-
projectId = sessionResp.data.project_id;
|
|
832
|
-
if (lastKnownUserKey && typeof projectId === "number") {
|
|
833
|
-
setUserLastProject(lastKnownUserKey, projectId);
|
|
834
|
-
}
|
|
835
|
-
// Establish socket connection
|
|
836
|
-
socket = establishSocket(conversationId);
|
|
837
|
-
spinner.text = "Waiting for assistant...";
|
|
838
|
-
}
|
|
839
|
-
else {
|
|
840
|
-
spinner.fail("Unable to create conversation");
|
|
841
|
-
throw e;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
else {
|
|
846
|
-
// Send question to existing conversation
|
|
847
|
-
spinner = ora("Sending...").start();
|
|
848
|
-
try {
|
|
849
|
-
await http.post(`/api/v1/ask/sessions/${conversationId}/message`, {
|
|
850
|
-
message: userInput,
|
|
851
|
-
project_id: projectId,
|
|
852
|
-
surface: "cli",
|
|
853
|
-
});
|
|
854
|
-
spinner.text = "Waiting for assistant...";
|
|
855
|
-
}
|
|
856
|
-
catch (e) {
|
|
857
|
-
const status = e?.response?.status;
|
|
858
|
-
if (status === 401) {
|
|
859
|
-
spinner.text = "Session expired. Re-authenticating...";
|
|
860
|
-
await performInteractiveLogin();
|
|
861
|
-
http = createHttpClient();
|
|
862
|
-
spinner.text = "Sending...";
|
|
863
|
-
await http.post(`/api/v1/ask/sessions/${conversationId}/message`, {
|
|
864
|
-
message: userInput,
|
|
865
|
-
project_id: projectId,
|
|
866
|
-
surface: "cli",
|
|
867
|
-
});
|
|
868
|
-
spinner.text = "Waiting for assistant...";
|
|
869
|
-
}
|
|
870
|
-
else {
|
|
871
|
-
throw e;
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
// Create a promise that resolves when agent responds
|
|
876
|
-
const waitForResponse = new Promise((resolve) => {
|
|
877
|
-
const responseHandler = (data) => {
|
|
878
|
-
// Remove the listener once we get a response
|
|
879
|
-
socket.off("session_completed", responseHandler);
|
|
880
|
-
socket.off("session_error", errorHandler);
|
|
881
|
-
cleanupEsc();
|
|
882
|
-
resolve();
|
|
883
|
-
};
|
|
884
|
-
const errorHandler = (data) => {
|
|
885
|
-
// Remove the listener once we get an error
|
|
886
|
-
socket.off("session_completed", responseHandler);
|
|
887
|
-
socket.off("session_error", errorHandler);
|
|
888
|
-
cleanupEsc();
|
|
889
|
-
resolve();
|
|
890
|
-
};
|
|
891
|
-
// Listen for agent response
|
|
892
|
-
socket.on("session_completed", responseHandler);
|
|
893
|
-
socket.on("session_error", errorHandler);
|
|
894
|
-
// Also allow ESC to stop the ask session
|
|
895
|
-
let escArmed = true;
|
|
896
|
-
const onKeypress = (_str, key) => {
|
|
897
|
-
try {
|
|
898
|
-
if (!escArmed)
|
|
899
|
-
return;
|
|
900
|
-
if (key?.name === "escape") {
|
|
901
|
-
escArmed = false;
|
|
902
|
-
sendAskStop();
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
catch { }
|
|
906
|
-
};
|
|
907
|
-
const cleanupEsc = () => {
|
|
908
|
-
try {
|
|
909
|
-
process.stdin.removeListener("keypress", onKeypress);
|
|
910
|
-
}
|
|
911
|
-
catch { }
|
|
912
|
-
};
|
|
913
|
-
try {
|
|
914
|
-
import("node:readline")
|
|
915
|
-
.then((readline) => {
|
|
916
|
-
try {
|
|
917
|
-
readline.emitKeypressEvents(process.stdin);
|
|
918
|
-
if (process.stdin.isTTY)
|
|
919
|
-
process.stdin.setRawMode(true);
|
|
920
|
-
process.stdin.on("keypress", onKeypress);
|
|
921
|
-
}
|
|
922
|
-
catch { }
|
|
923
|
-
})
|
|
924
|
-
.catch(() => { });
|
|
925
|
-
}
|
|
926
|
-
catch { }
|
|
927
|
-
});
|
|
928
|
-
let messageDelivered = false;
|
|
929
|
-
try {
|
|
930
|
-
// Wait for the agent response before continuing
|
|
931
|
-
await waitForResponse;
|
|
932
|
-
messageDelivered = true;
|
|
933
|
-
}
|
|
934
|
-
catch (error) {
|
|
935
|
-
if (spinner) {
|
|
936
|
-
spinner.fail();
|
|
937
|
-
spinner = null;
|
|
938
|
-
}
|
|
939
|
-
const status = error?.response?.status;
|
|
940
|
-
if (status === 401) {
|
|
941
|
-
console.log(chalk.yellow("Session expired. Re-authenticating…"));
|
|
942
|
-
try {
|
|
943
|
-
await performInteractiveLogin();
|
|
944
|
-
}
|
|
945
|
-
catch (loginError) {
|
|
946
|
-
console.log(chalk.red(loginError?.message || "Re-authentication failed."));
|
|
947
|
-
break;
|
|
948
|
-
}
|
|
949
|
-
http = createHttpClient();
|
|
950
|
-
spinner = ora("Sending...").start();
|
|
951
|
-
try {
|
|
952
|
-
await http.post(`/api/v1/ask/sessions/${conversationId}/message`, {
|
|
953
|
-
message: userInput,
|
|
954
|
-
project_id: projectId,
|
|
955
|
-
surface: "cli",
|
|
956
|
-
});
|
|
957
|
-
spinner.text = "Waiting for assistant...";
|
|
958
|
-
// Wait for response even after re-auth
|
|
959
|
-
await waitForResponse;
|
|
960
|
-
messageDelivered = true;
|
|
961
|
-
}
|
|
962
|
-
catch (err2) {
|
|
963
|
-
if (spinner) {
|
|
964
|
-
spinner.fail();
|
|
965
|
-
spinner = null;
|
|
966
|
-
}
|
|
967
|
-
console.log(chalk.red(`❌ Error sending message: ${err2?.message || err2}`));
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
else {
|
|
971
|
-
console.log(chalk.red(`❌ Error sending message: ${error?.message || error}`));
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
if (messageDelivered) {
|
|
975
|
-
messageCount += 1;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
// Clean up
|
|
979
|
-
if (spinner) {
|
|
980
|
-
spinner.stop();
|
|
981
|
-
}
|
|
982
|
-
if (socket) {
|
|
983
|
-
await socket.disconnect();
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
catch (error) {
|
|
987
|
-
console.error(chalk.red(`❌ Error: ${error.message}`));
|
|
988
|
-
process.exit(1);
|
|
989
|
-
}
|
|
990
|
-
});
|
|
991
|
-
//# sourceMappingURL=ask.js.map
|