shark-ai 0.0.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/CHANGELOG.md +63 -0
- package/LICENSE +21 -0
- package/README.en.md +349 -0
- package/README.md +349 -0
- package/dist/bin/shark.d.ts +1 -0
- package/dist/bin/shark.js +1854 -0
- package/dist/bin/shark.js.map +1 -0
- package/dist/chunk-B7PNFPUX.js +538 -0
- package/dist/chunk-B7PNFPUX.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,1854 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
import {
|
|
4
|
+
ConfigManager,
|
|
5
|
+
colors,
|
|
6
|
+
configCommand,
|
|
7
|
+
loginCommand,
|
|
8
|
+
tokenStorage,
|
|
9
|
+
tui
|
|
10
|
+
} from "../chunk-B7PNFPUX.js";
|
|
11
|
+
|
|
12
|
+
// src/core/error/crash-handler.ts
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import os from "os";
|
|
16
|
+
var CrashHandler = class _CrashHandler {
|
|
17
|
+
static instance;
|
|
18
|
+
logDir;
|
|
19
|
+
constructor() {
|
|
20
|
+
this.logDir = path.join(os.homedir(), ".shark", "logs");
|
|
21
|
+
}
|
|
22
|
+
static getInstance() {
|
|
23
|
+
if (!_CrashHandler.instance) {
|
|
24
|
+
_CrashHandler.instance = new _CrashHandler();
|
|
25
|
+
}
|
|
26
|
+
return _CrashHandler.instance;
|
|
27
|
+
}
|
|
28
|
+
init() {
|
|
29
|
+
process.on("uncaughtException", (error) => this.handleError(error, "Uncaught Exception"));
|
|
30
|
+
process.on("unhandledRejection", (reason) => this.handleError(reason, "Unhandled Rejection"));
|
|
31
|
+
}
|
|
32
|
+
handleError(error, type) {
|
|
33
|
+
try {
|
|
34
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
35
|
+
const logFile = path.join(this.logDir, `crash-${timestamp}.log`);
|
|
36
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
37
|
+
const stackTrace = error instanceof Error ? error.stack : "No stack trace available";
|
|
38
|
+
const logContent = `
|
|
39
|
+
[${(/* @__PURE__ */ new Date()).toISOString()}] ${type}
|
|
40
|
+
--------------------------------------------------
|
|
41
|
+
Message: ${errorMessage}
|
|
42
|
+
Stack:
|
|
43
|
+
${stackTrace}
|
|
44
|
+
--------------------------------------------------
|
|
45
|
+
System: ${os.platform()} ${os.release()} ${os.arch()}
|
|
46
|
+
Node: ${process.version}
|
|
47
|
+
`;
|
|
48
|
+
if (!fs.existsSync(this.logDir)) {
|
|
49
|
+
fs.mkdirSync(this.logDir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
fs.writeFileSync(logFile, logContent, "utf-8");
|
|
52
|
+
console.error("\n");
|
|
53
|
+
console.error(colors.error("\u{1F4A5} Whoops! Shark CLI crashed unexpectedly."));
|
|
54
|
+
console.error(colors.dim(` Details have been saved to: ${logFile}`));
|
|
55
|
+
console.error(colors.dim(" Please report this issue so we can fix it."));
|
|
56
|
+
console.error(colors.error(` Error: ${errorMessage}`));
|
|
57
|
+
console.error("\n");
|
|
58
|
+
} catch (filesysError) {
|
|
59
|
+
console.error("Fatal Error: Failed to write crash log.", filesysError);
|
|
60
|
+
console.error("Original Error:", error);
|
|
61
|
+
} finally {
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var crashHandler = CrashHandler.getInstance();
|
|
67
|
+
|
|
68
|
+
// src/bin/shark.ts
|
|
69
|
+
import { Command as Command5 } from "commander";
|
|
70
|
+
|
|
71
|
+
// src/commands/init.ts
|
|
72
|
+
import { Command } from "commander";
|
|
73
|
+
|
|
74
|
+
// src/core/workflow/workflow-manager.ts
|
|
75
|
+
import fs2 from "fs/promises";
|
|
76
|
+
import path2 from "path";
|
|
77
|
+
|
|
78
|
+
// src/core/workflow/shark-workflow.schema.ts
|
|
79
|
+
import { z } from "zod";
|
|
80
|
+
var TechStackEnum = z.enum([
|
|
81
|
+
"react",
|
|
82
|
+
"nextjs",
|
|
83
|
+
"angular",
|
|
84
|
+
"vue",
|
|
85
|
+
"node-ts",
|
|
86
|
+
"python",
|
|
87
|
+
"dotnet",
|
|
88
|
+
"java",
|
|
89
|
+
"unknown"
|
|
90
|
+
]);
|
|
91
|
+
var WorkflowStageEnum = z.enum([
|
|
92
|
+
"business_analysis",
|
|
93
|
+
"specification",
|
|
94
|
+
"architecture",
|
|
95
|
+
"development",
|
|
96
|
+
"verification",
|
|
97
|
+
"deployment"
|
|
98
|
+
]);
|
|
99
|
+
var StageStatusEnum = z.enum([
|
|
100
|
+
"pending",
|
|
101
|
+
"in_progress",
|
|
102
|
+
"completed",
|
|
103
|
+
"failed",
|
|
104
|
+
"skipped"
|
|
105
|
+
]);
|
|
106
|
+
var WorkflowSchema = z.object({
|
|
107
|
+
projectId: z.string().uuid(),
|
|
108
|
+
projectName: z.string().min(1),
|
|
109
|
+
techStack: TechStackEnum.default("unknown"),
|
|
110
|
+
currentStage: WorkflowStageEnum.default("business_analysis"),
|
|
111
|
+
stageStatus: StageStatusEnum.default("pending"),
|
|
112
|
+
lastUpdated: z.string().datetime(),
|
|
113
|
+
// ISO 8601
|
|
114
|
+
conversationId: z.string().optional(),
|
|
115
|
+
conversations: z.record(z.string(), z.string()).optional(),
|
|
116
|
+
// agentType -> conversationId
|
|
117
|
+
artifacts: z.array(z.string()).default([]),
|
|
118
|
+
// Extensible metadata bag
|
|
119
|
+
metadata: z.record(z.unknown()).optional()
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// src/core/workflow/workflow-manager.ts
|
|
123
|
+
var WorkflowManager = class _WorkflowManager {
|
|
124
|
+
static instance;
|
|
125
|
+
filename = "shark-workflow.json";
|
|
126
|
+
tmpFilename = ".shark-workflow.tmp";
|
|
127
|
+
constructor() {
|
|
128
|
+
}
|
|
129
|
+
static getInstance() {
|
|
130
|
+
if (!_WorkflowManager.instance) {
|
|
131
|
+
_WorkflowManager.instance = new _WorkflowManager();
|
|
132
|
+
}
|
|
133
|
+
return _WorkflowManager.instance;
|
|
134
|
+
}
|
|
135
|
+
getFilePath() {
|
|
136
|
+
return path2.join(process.cwd(), this.filename);
|
|
137
|
+
}
|
|
138
|
+
getTmpFilePath() {
|
|
139
|
+
return path2.join(process.cwd(), this.tmpFilename);
|
|
140
|
+
}
|
|
141
|
+
async save(state) {
|
|
142
|
+
const parsed = WorkflowSchema.safeParse(state);
|
|
143
|
+
if (!parsed.success) {
|
|
144
|
+
throw new Error(`Invalid workflow state: ${parsed.error.message}`);
|
|
145
|
+
}
|
|
146
|
+
const filePath = this.getFilePath();
|
|
147
|
+
const tmpPath = this.getTmpFilePath();
|
|
148
|
+
const data = JSON.stringify(parsed.data, null, 2);
|
|
149
|
+
try {
|
|
150
|
+
await fs2.writeFile(tmpPath, data, "utf-8");
|
|
151
|
+
await fs2.rename(tmpPath, filePath);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw new Error(`Failed to save workflow state atomically: ${error.message}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async load() {
|
|
157
|
+
const filePath = this.getFilePath();
|
|
158
|
+
try {
|
|
159
|
+
try {
|
|
160
|
+
await fs2.access(filePath);
|
|
161
|
+
} catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
165
|
+
const json = JSON.parse(content);
|
|
166
|
+
const parsed = WorkflowSchema.safeParse(json);
|
|
167
|
+
if (parsed.success) {
|
|
168
|
+
return parsed.data;
|
|
169
|
+
} else {
|
|
170
|
+
console.warn(colors.warning(`\u26A0\uFE0F Corrupted workflow file detected: ${parsed.error.message}`));
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.warn(colors.warning(`\u26A0\uFE0F Failed to load workflow state: ${error.message}`));
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Helper to get current state or default if none exists
|
|
179
|
+
async getOrInitState() {
|
|
180
|
+
return await this.load();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var workflowManager = WorkflowManager.getInstance();
|
|
184
|
+
|
|
185
|
+
// src/commands/init.ts
|
|
186
|
+
import { randomUUID } from "crypto";
|
|
187
|
+
var initAction = async () => {
|
|
188
|
+
tui.intro("Shark Project Initialization");
|
|
189
|
+
const existingState = await workflowManager.load();
|
|
190
|
+
if (existingState) {
|
|
191
|
+
tui.log.info(colors.dim("----------------------------------------"));
|
|
192
|
+
tui.log.info(`\u{1F988} ${colors.primary("Welcome back to Shark CLI!")}`);
|
|
193
|
+
tui.log.message(` Project: ${colors.white(existingState.projectName)}`);
|
|
194
|
+
tui.log.message(` Stage: ${colors.secondary(existingState.currentStage)}`);
|
|
195
|
+
tui.log.message(` Updated: ${colors.dim(new Date(existingState.lastUpdated).toLocaleString())}`);
|
|
196
|
+
tui.log.info(colors.dim("----------------------------------------"));
|
|
197
|
+
const action = await tui.select({
|
|
198
|
+
message: "An existing project was detected. What would you like to do?",
|
|
199
|
+
options: [
|
|
200
|
+
{ value: "resume", label: "\u{1F680} Resume Work" },
|
|
201
|
+
{ value: "overwrite", label: "\u26A0\uFE0F Overwrite (Start Fresh)" },
|
|
202
|
+
{ value: "exit", label: "\u274C Exit" }
|
|
203
|
+
]
|
|
204
|
+
});
|
|
205
|
+
if (tui.isCancel(action) || action === "exit") {
|
|
206
|
+
tui.outro("See you later! \u{1F44B}");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (action === "resume") {
|
|
210
|
+
tui.log.success(`Resuming work on ${colors.primary(existingState.projectName)}...`);
|
|
211
|
+
tui.outro(`To continue, run: "shark agent" (Context loaded)`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const projectName = await tui.text({
|
|
216
|
+
message: "What is the name of your project?",
|
|
217
|
+
placeholder: "e.g. My Awesome App",
|
|
218
|
+
validate: (value) => {
|
|
219
|
+
if (!value) return "Project name is required";
|
|
220
|
+
if (value.trim().length < 2) return "Project name must be at least 2 characters";
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
if (tui.isCancel(projectName)) {
|
|
224
|
+
tui.outro("Initialization cancelled.");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const techStackOptions = TechStackEnum.options.map((stack) => ({
|
|
228
|
+
value: stack,
|
|
229
|
+
label: stack === "node-ts" ? "Node.js (TypeScript)" : stack === "nextjs" ? "Next.js" : stack.charAt(0).toUpperCase() + stack.slice(1)
|
|
230
|
+
}));
|
|
231
|
+
const techStack = await tui.select({
|
|
232
|
+
message: "Select your technology stack:",
|
|
233
|
+
options: techStackOptions
|
|
234
|
+
});
|
|
235
|
+
if (tui.isCancel(techStack)) {
|
|
236
|
+
tui.outro("Initialization cancelled.");
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const spinner = tui.spinner();
|
|
240
|
+
spinner.start("Initializing project workflow...");
|
|
241
|
+
try {
|
|
242
|
+
const newState = {
|
|
243
|
+
projectId: randomUUID(),
|
|
244
|
+
projectName,
|
|
245
|
+
techStack,
|
|
246
|
+
currentStage: "business_analysis",
|
|
247
|
+
stageStatus: "pending",
|
|
248
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
249
|
+
artifacts: [],
|
|
250
|
+
metadata: {
|
|
251
|
+
initializedBy: "shark-cli",
|
|
252
|
+
version: "0.0.1"
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
await workflowManager.save(newState);
|
|
256
|
+
spinner.stop("Project workflow created!");
|
|
257
|
+
tui.log.success(`Project ${colors.primary(projectName)} initialized successfully.`);
|
|
258
|
+
tui.log.message(`Your Project ID: ${colors.dim(newState.projectId)}`);
|
|
259
|
+
tui.outro('Ready to start! Run "shark agent" to begin analyzing requirements.');
|
|
260
|
+
} catch (error) {
|
|
261
|
+
spinner.stop("Initialization failed.", 1);
|
|
262
|
+
tui.log.error(error.message);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
var initCommand = new Command("init").description("Initialize a new Shark project").action(initAction);
|
|
267
|
+
|
|
268
|
+
// src/core/api/stackspot-client.ts
|
|
269
|
+
var STACKSPOT_AGENT_API_BASE = "https://genai-inference-app.stackspot.com";
|
|
270
|
+
|
|
271
|
+
// src/core/debug/file-logger.ts
|
|
272
|
+
import fs3 from "fs";
|
|
273
|
+
import path3 from "path";
|
|
274
|
+
var FileLogger = class {
|
|
275
|
+
static logPath = path3.resolve(process.cwd(), "shark-debug.log");
|
|
276
|
+
static enabled = true;
|
|
277
|
+
// Enabled by default for this debugging session
|
|
278
|
+
static init() {
|
|
279
|
+
try {
|
|
280
|
+
fs3.writeFileSync(this.logPath, `--- Shark CLI Debug Log Started at ${(/* @__PURE__ */ new Date()).toISOString()} ---
|
|
281
|
+
`);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
console.error("Failed to initialize debug log:", e);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
static log(category, message, data) {
|
|
287
|
+
if (!this.enabled) return;
|
|
288
|
+
try {
|
|
289
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
290
|
+
let logEntry = `[${timestamp}] [${category.toUpperCase()}] ${message}
|
|
291
|
+
`;
|
|
292
|
+
if (data !== void 0) {
|
|
293
|
+
if (typeof data === "object") {
|
|
294
|
+
logEntry += JSON.stringify(data, null, 2) + "\n";
|
|
295
|
+
} else {
|
|
296
|
+
logEntry += String(data) + "\n";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
logEntry += "-".repeat(40) + "\n";
|
|
300
|
+
fs3.appendFileSync(this.logPath, logEntry);
|
|
301
|
+
} catch (e) {
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// src/core/api/sse-client.ts
|
|
307
|
+
var SSEClient = class {
|
|
308
|
+
/**
|
|
309
|
+
* Streams agent response using Server-Sent Events.
|
|
310
|
+
*
|
|
311
|
+
* @param url - The SSE endpoint URL
|
|
312
|
+
* @param requestPayload - The request payload to POST
|
|
313
|
+
* @param headers - Request headers (including Authorization)
|
|
314
|
+
* @param callbacks - Event callbacks for chunks, completion, and errors
|
|
315
|
+
*/
|
|
316
|
+
async streamAgentResponse(url, requestPayload, headers, callbacks = {}) {
|
|
317
|
+
const { onChunk, onComplete, onError } = callbacks;
|
|
318
|
+
FileLogger.log("SSE", `Starting Request to ${url}`, {
|
|
319
|
+
headers,
|
|
320
|
+
payload: requestPayload
|
|
321
|
+
});
|
|
322
|
+
try {
|
|
323
|
+
const response = await fetch(url, {
|
|
324
|
+
method: "POST",
|
|
325
|
+
headers: {
|
|
326
|
+
...headers,
|
|
327
|
+
"Content-Type": "application/json"
|
|
328
|
+
},
|
|
329
|
+
body: JSON.stringify(requestPayload)
|
|
330
|
+
});
|
|
331
|
+
FileLogger.log("SSE", `Response Status: ${response.status} ${response.statusText}`);
|
|
332
|
+
const responseHeaders = {};
|
|
333
|
+
response.headers.forEach((value, key) => {
|
|
334
|
+
responseHeaders[key] = value;
|
|
335
|
+
});
|
|
336
|
+
FileLogger.log("SSE", "Response Headers", responseHeaders);
|
|
337
|
+
if (!response.ok) {
|
|
338
|
+
const errorText = await response.text();
|
|
339
|
+
FileLogger.log("SSE", "Response Error Body", errorText);
|
|
340
|
+
throw new Error(`SSE request failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
341
|
+
}
|
|
342
|
+
if (!response.body) {
|
|
343
|
+
throw new Error("Response body is null");
|
|
344
|
+
}
|
|
345
|
+
const reader = response.body.getReader();
|
|
346
|
+
const decoder = new TextDecoder();
|
|
347
|
+
let buffer = "";
|
|
348
|
+
let fullMessage = "";
|
|
349
|
+
let metadata = {};
|
|
350
|
+
while (true) {
|
|
351
|
+
const { done, value } = await reader.read();
|
|
352
|
+
if (done) {
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
buffer += decoder.decode(value, { stream: true });
|
|
356
|
+
const lines = buffer.split("\n");
|
|
357
|
+
buffer = lines.pop() || "";
|
|
358
|
+
for (const line of lines) {
|
|
359
|
+
if (line.startsWith("data:")) {
|
|
360
|
+
const data = line.slice(5).trim();
|
|
361
|
+
if (data === "[DONE]") {
|
|
362
|
+
FileLogger.log("SSE", "Stream Complete [DONE]", { fullMessage, metadata });
|
|
363
|
+
if (onComplete) {
|
|
364
|
+
onComplete(fullMessage, metadata);
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const parsed = JSON.parse(data);
|
|
370
|
+
const chunk = parsed.message || parsed.content || data;
|
|
371
|
+
fullMessage += chunk;
|
|
372
|
+
if (parsed.conversation_id) {
|
|
373
|
+
metadata.conversation_id = parsed.conversation_id;
|
|
374
|
+
}
|
|
375
|
+
if (onChunk) {
|
|
376
|
+
onChunk(chunk);
|
|
377
|
+
}
|
|
378
|
+
} catch (parseError) {
|
|
379
|
+
fullMessage += data;
|
|
380
|
+
if (onChunk) {
|
|
381
|
+
onChunk(data);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
FileLogger.log("SSE", "Stream Ended Naturally", { fullMessage, metadata });
|
|
388
|
+
if (onComplete) {
|
|
389
|
+
onComplete(fullMessage, metadata);
|
|
390
|
+
}
|
|
391
|
+
} catch (error) {
|
|
392
|
+
FileLogger.log("SSE", "Stream Error", error);
|
|
393
|
+
if (onError) {
|
|
394
|
+
onError(error instanceof Error ? error : new Error(String(error)));
|
|
395
|
+
} else {
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
var sseClient = new SSEClient();
|
|
402
|
+
|
|
403
|
+
// src/core/agents/agent-response-parser.ts
|
|
404
|
+
import { z as z2 } from "zod";
|
|
405
|
+
var AgentActionSchema = z2.object({
|
|
406
|
+
type: z2.enum(["create_file", "modify_file", "delete_file", "talk_with_user", "list_files", "read_file", "search_file", "run_command", "use_mcp_tool"]),
|
|
407
|
+
path: z2.string().nullable().optional(),
|
|
408
|
+
// Nullable for strict mode combatibility
|
|
409
|
+
content: z2.string().nullable().optional(),
|
|
410
|
+
target_content: z2.string().nullable().optional(),
|
|
411
|
+
command: z2.string().nullable().optional(),
|
|
412
|
+
tool_name: z2.string().nullable().optional(),
|
|
413
|
+
tool_args: z2.string().nullable().optional()
|
|
414
|
+
// JSON string argument
|
|
415
|
+
});
|
|
416
|
+
var AgentCommandSchema = z2.object({
|
|
417
|
+
command: z2.string(),
|
|
418
|
+
description: z2.string(),
|
|
419
|
+
critical: z2.boolean()
|
|
420
|
+
});
|
|
421
|
+
var AgentResponseSchema = z2.object({
|
|
422
|
+
actions: z2.array(AgentActionSchema),
|
|
423
|
+
commands: z2.array(AgentCommandSchema).optional(),
|
|
424
|
+
summary: z2.string().optional(),
|
|
425
|
+
// Legacy fields handling for smooth transition/fallback
|
|
426
|
+
message: z2.string().optional(),
|
|
427
|
+
conversation_id: z2.string().optional()
|
|
428
|
+
});
|
|
429
|
+
function parseAgentResponse(rawResponse) {
|
|
430
|
+
FileLogger.log("PARSER", "Parsing Agent Response", { rawType: typeof rawResponse });
|
|
431
|
+
let parsedObj = {};
|
|
432
|
+
let conversation_id;
|
|
433
|
+
if (typeof rawResponse === "string") {
|
|
434
|
+
FileLogger.log("PARSER", "Type String", { length: rawResponse.length });
|
|
435
|
+
try {
|
|
436
|
+
parsedObj = extractFirstJson(rawResponse);
|
|
437
|
+
} catch (e) {
|
|
438
|
+
FileLogger.log("PARSER", "String Parse Failed", { error: e.message });
|
|
439
|
+
return {
|
|
440
|
+
actions: [{
|
|
441
|
+
type: "talk_with_user",
|
|
442
|
+
content: rawResponse,
|
|
443
|
+
path: ""
|
|
444
|
+
}],
|
|
445
|
+
message: rawResponse
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
} else if (typeof rawResponse === "object" && rawResponse !== null) {
|
|
449
|
+
const anyResp = rawResponse;
|
|
450
|
+
conversation_id = anyResp.conversation_id;
|
|
451
|
+
FileLogger.log("PARSER", "Type Object", {
|
|
452
|
+
hasContent: !!anyResp.content,
|
|
453
|
+
hasMessage: !!anyResp.message,
|
|
454
|
+
messageType: typeof anyResp.message
|
|
455
|
+
});
|
|
456
|
+
const stringContent = anyResp.content || anyResp.message;
|
|
457
|
+
if (stringContent && typeof stringContent === "string") {
|
|
458
|
+
try {
|
|
459
|
+
const parsedInside = extractFirstJson(stringContent);
|
|
460
|
+
if (typeof parsedInside === "object" && parsedInside !== null) {
|
|
461
|
+
parsedObj = parsedInside;
|
|
462
|
+
FileLogger.log("PARSER", "Inner JSON Parsed", { keys: Object.keys(parsedObj) });
|
|
463
|
+
} else {
|
|
464
|
+
parsedObj = rawResponse;
|
|
465
|
+
FileLogger.log("PARSER", "Inner JSON was primitive");
|
|
466
|
+
}
|
|
467
|
+
} catch (e) {
|
|
468
|
+
parsedObj = rawResponse;
|
|
469
|
+
FileLogger.log("PARSER", "Inner JSON Parse Error - treating as raw", { error: e.message });
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
parsedObj = rawResponse;
|
|
473
|
+
}
|
|
474
|
+
if (!parsedObj.actions) {
|
|
475
|
+
parsedObj = rawResponse;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (!parsedObj.actions) {
|
|
479
|
+
FileLogger.log("PARSER", "No Actions Found - Constructing Default");
|
|
480
|
+
return {
|
|
481
|
+
conversation_id,
|
|
482
|
+
actions: [{
|
|
483
|
+
type: "talk_with_user",
|
|
484
|
+
content: parsedObj.message || JSON.stringify(parsedObj),
|
|
485
|
+
path: ""
|
|
486
|
+
}],
|
|
487
|
+
message: parsedObj.message
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
const result = {
|
|
491
|
+
actions: parsedObj.actions,
|
|
492
|
+
commands: parsedObj.commands || [],
|
|
493
|
+
summary: parsedObj.summary || "",
|
|
494
|
+
conversation_id,
|
|
495
|
+
message: parsedObj.summary || "Agent Action"
|
|
496
|
+
// Backward compatibility
|
|
497
|
+
};
|
|
498
|
+
FileLogger.log("PARSER", "Final Result Constructed", { actionCount: result.actions.length });
|
|
499
|
+
try {
|
|
500
|
+
return AgentResponseSchema.parse(result);
|
|
501
|
+
} catch (e) {
|
|
502
|
+
FileLogger.log("PARSER", "Schema Validation Failed", { error: e.message });
|
|
503
|
+
throw e;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function extractFirstJson(str) {
|
|
507
|
+
try {
|
|
508
|
+
return JSON.parse(str);
|
|
509
|
+
} catch (e) {
|
|
510
|
+
const firstOpen = str.indexOf("{");
|
|
511
|
+
if (firstOpen === -1) throw e;
|
|
512
|
+
let balance = 0;
|
|
513
|
+
let inString = false;
|
|
514
|
+
let escape = false;
|
|
515
|
+
for (let i = firstOpen; i < str.length; i++) {
|
|
516
|
+
const char = str[i];
|
|
517
|
+
if (escape) {
|
|
518
|
+
escape = false;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (char === "\\") {
|
|
522
|
+
escape = true;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (char === '"') {
|
|
526
|
+
inString = !inString;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (!inString) {
|
|
530
|
+
if (char === "{") balance++;
|
|
531
|
+
else if (char === "}") {
|
|
532
|
+
balance--;
|
|
533
|
+
if (balance === 0) {
|
|
534
|
+
const potentialJson = str.substring(firstOpen, i + 1);
|
|
535
|
+
try {
|
|
536
|
+
return JSON.parse(potentialJson);
|
|
537
|
+
} catch (innerE) {
|
|
538
|
+
throw e;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
throw e;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/core/workflow/conversation-manager.ts
|
|
549
|
+
var ConversationManager = class _ConversationManager {
|
|
550
|
+
static instance;
|
|
551
|
+
constructor() {
|
|
552
|
+
}
|
|
553
|
+
static getInstance() {
|
|
554
|
+
if (!_ConversationManager.instance) {
|
|
555
|
+
_ConversationManager.instance = new _ConversationManager();
|
|
556
|
+
}
|
|
557
|
+
return _ConversationManager.instance;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Saves a conversation ID for a specific agent type.
|
|
561
|
+
*
|
|
562
|
+
* @param agentType - The type of agent (e.g., 'business_analyst', 'architect')
|
|
563
|
+
* @param conversationId - The conversation ID from the agent response
|
|
564
|
+
*/
|
|
565
|
+
async saveConversationId(agentType, conversationId) {
|
|
566
|
+
const state = await workflowManager.load();
|
|
567
|
+
if (!state) {
|
|
568
|
+
throw new Error('No workflow state found. Please run "shark init" first.');
|
|
569
|
+
}
|
|
570
|
+
if (!state.conversations) {
|
|
571
|
+
state.conversations = {};
|
|
572
|
+
}
|
|
573
|
+
state.conversations[agentType] = conversationId;
|
|
574
|
+
await workflowManager.save(state);
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Retrieves the conversation ID for a specific agent type.
|
|
578
|
+
*
|
|
579
|
+
* @param agentType - The type of agent
|
|
580
|
+
* @returns The conversation ID, or undefined if none exists
|
|
581
|
+
*/
|
|
582
|
+
async getConversationId(agentType) {
|
|
583
|
+
const state = await workflowManager.load();
|
|
584
|
+
if (!state || !state.conversations) {
|
|
585
|
+
return void 0;
|
|
586
|
+
}
|
|
587
|
+
return state.conversations[agentType];
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Clears the conversation ID for a specific agent type.
|
|
591
|
+
*
|
|
592
|
+
* @param agentType - The type of agent
|
|
593
|
+
*/
|
|
594
|
+
async clearConversationId(agentType) {
|
|
595
|
+
const state = await workflowManager.load();
|
|
596
|
+
if (!state || !state.conversations) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
delete state.conversations[agentType];
|
|
600
|
+
await workflowManager.save(state);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Clears all conversation IDs.
|
|
604
|
+
* Useful when transitioning to a new project or resetting state.
|
|
605
|
+
*/
|
|
606
|
+
async clearAllConversations() {
|
|
607
|
+
const state = await workflowManager.load();
|
|
608
|
+
if (!state) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
state.conversations = {};
|
|
612
|
+
await workflowManager.save(state);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
var conversationManager = ConversationManager.getInstance();
|
|
616
|
+
|
|
617
|
+
// src/core/auth/get-active-realm.ts
|
|
618
|
+
async function getActiveRealm() {
|
|
619
|
+
const configManager = ConfigManager.getInstance();
|
|
620
|
+
const config = configManager.getConfig();
|
|
621
|
+
const realm = config.activeRealm;
|
|
622
|
+
if (!realm) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
'No active authentication found.\nPlease run "shark login" first to authenticate.'
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
return realm;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/core/agents/business-analyst-agent.ts
|
|
631
|
+
var AGENT_TYPE = "business_analyst";
|
|
632
|
+
var AGENT_ID = process.env.STACKSPOT_BA_AGENT_ID || "01KEJ95G304TNNAKGH5XNEEBVD";
|
|
633
|
+
async function runBusinessAnalystAgent(prompt, options = {}) {
|
|
634
|
+
const { agentId, onChunk, onComplete } = options;
|
|
635
|
+
const realm = await getActiveRealm();
|
|
636
|
+
const token = await tokenStorage.getToken(realm);
|
|
637
|
+
if (!token) {
|
|
638
|
+
throw new Error(`No authentication token found for realm '${realm}'. Please run 'shark login'.`);
|
|
639
|
+
}
|
|
640
|
+
const existingConversationId = await conversationManager.getConversationId(AGENT_TYPE);
|
|
641
|
+
const requestPayload = {
|
|
642
|
+
user_prompt: prompt,
|
|
643
|
+
streaming: true,
|
|
644
|
+
stackspot_knowledge: false,
|
|
645
|
+
// Use agent's configured KS instead
|
|
646
|
+
return_ks_in_response: true,
|
|
647
|
+
deep_search_ks: false,
|
|
648
|
+
conversation_id: existingConversationId
|
|
649
|
+
};
|
|
650
|
+
const finalAgentId = agentId || AGENT_ID;
|
|
651
|
+
const agentUrl = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${finalAgentId}/chat`;
|
|
652
|
+
const headers = {
|
|
653
|
+
"Authorization": `Bearer ${token}`,
|
|
654
|
+
"Content-Type": "application/json"
|
|
655
|
+
};
|
|
656
|
+
let fullMessage = "";
|
|
657
|
+
let rawResponse = {};
|
|
658
|
+
await sseClient.streamAgentResponse(
|
|
659
|
+
agentUrl,
|
|
660
|
+
requestPayload,
|
|
661
|
+
headers,
|
|
662
|
+
{
|
|
663
|
+
onChunk: (chunk) => {
|
|
664
|
+
fullMessage += chunk;
|
|
665
|
+
if (onChunk) {
|
|
666
|
+
onChunk(chunk);
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
onComplete: async (message) => {
|
|
670
|
+
rawResponse = {
|
|
671
|
+
message: message || fullMessage,
|
|
672
|
+
conversation_id: existingConversationId
|
|
673
|
+
// Will be updated if new one provided
|
|
674
|
+
};
|
|
675
|
+
},
|
|
676
|
+
onError: (error) => {
|
|
677
|
+
throw error;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
const parsedResponse = parseAgentResponse(rawResponse);
|
|
682
|
+
if (parsedResponse.conversation_id) {
|
|
683
|
+
await conversationManager.saveConversationId(AGENT_TYPE, parsedResponse.conversation_id);
|
|
684
|
+
}
|
|
685
|
+
if (onComplete) {
|
|
686
|
+
onComplete(parsedResponse);
|
|
687
|
+
}
|
|
688
|
+
return parsedResponse;
|
|
689
|
+
}
|
|
690
|
+
async function interactiveBusinessAnalyst() {
|
|
691
|
+
tui.intro("\u{1F3AF} Business Analyst Agent");
|
|
692
|
+
const prompt = await tui.text({
|
|
693
|
+
message: "Describe your project idea",
|
|
694
|
+
placeholder: "E.g., I want to build a task management app for teams...",
|
|
695
|
+
validate: (value) => {
|
|
696
|
+
if (!value || value.length < 10) return "Please provide a detailed description (at least 10 characters)";
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
if (tui.isCancel(prompt)) {
|
|
700
|
+
tui.outro("Cancelled");
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const spinner = tui.spinner();
|
|
704
|
+
spinner.start("\u{1F4AC} Business Analyst is thinking...");
|
|
705
|
+
let responseText = "";
|
|
706
|
+
try {
|
|
707
|
+
await runBusinessAnalystAgent(prompt, {
|
|
708
|
+
onChunk: (chunk) => {
|
|
709
|
+
responseText += chunk;
|
|
710
|
+
try {
|
|
711
|
+
if (responseText.trim().startsWith("{")) {
|
|
712
|
+
spinner.message(colors.dim("Receiving structured data..."));
|
|
713
|
+
} else {
|
|
714
|
+
spinner.message(colors.dim("Thinking..."));
|
|
715
|
+
}
|
|
716
|
+
} catch (e) {
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
onComplete: async (response) => {
|
|
720
|
+
spinner.stop("Response received");
|
|
721
|
+
if (response.summary) {
|
|
722
|
+
tui.log.info(colors.italic(response.summary));
|
|
723
|
+
}
|
|
724
|
+
if (response.actions && response.actions.length > 0) {
|
|
725
|
+
for (const action of response.actions) {
|
|
726
|
+
if (action.type === "talk_with_user") {
|
|
727
|
+
tui.log.info(colors.green("\u{1F916} BA Agent:"));
|
|
728
|
+
console.log(action.content);
|
|
729
|
+
} else {
|
|
730
|
+
tui.log.warning(`
|
|
731
|
+
\u{1F916} Agent wants to ${action.type}: ${colors.bold(action.path || "unknown")}`);
|
|
732
|
+
if (action.content) {
|
|
733
|
+
console.log(colors.dim("--- Content Preview ---"));
|
|
734
|
+
console.log(action.content.substring(0, 300) + (action.content.length > 300 ? "..." : ""));
|
|
735
|
+
console.log(colors.dim("-----------------------"));
|
|
736
|
+
}
|
|
737
|
+
const confirm = await tui.confirm({
|
|
738
|
+
message: `Allow agent to ${action.type} '${action.path}'?`,
|
|
739
|
+
active: "Yes",
|
|
740
|
+
inactive: "No"
|
|
741
|
+
});
|
|
742
|
+
if (confirm) {
|
|
743
|
+
tui.log.success(`\u2705 Action executed: ${action.path} created.`);
|
|
744
|
+
} else {
|
|
745
|
+
tui.log.error("\u274C Action denied.");
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (response.tokens) {
|
|
751
|
+
tui.log.info(`Tokens used: ${response.tokens.output || 0}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
tui.outro("Session complete");
|
|
756
|
+
} catch (error) {
|
|
757
|
+
spinner.stop("\u274C Error", 1);
|
|
758
|
+
tui.log.error(error.message);
|
|
759
|
+
throw error;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/core/agents/specification-agent.ts
|
|
764
|
+
import fs5 from "fs";
|
|
765
|
+
|
|
766
|
+
// src/core/agents/agent-tools.ts
|
|
767
|
+
import fs4 from "fs";
|
|
768
|
+
import path4 from "path";
|
|
769
|
+
import fg from "fast-glob";
|
|
770
|
+
function handleListFiles(dirPath) {
|
|
771
|
+
try {
|
|
772
|
+
const fullPath = path4.resolve(process.cwd(), dirPath);
|
|
773
|
+
if (!fs4.existsSync(fullPath)) return `Error: Directory ${dirPath} does not exist.`;
|
|
774
|
+
const items = fs4.readdirSync(fullPath, { withFileTypes: true });
|
|
775
|
+
return items.map((item) => {
|
|
776
|
+
return `${item.isDirectory() ? "[DIR]" : "[FILE]"} ${item.name}`;
|
|
777
|
+
}).join("\n");
|
|
778
|
+
} catch (e) {
|
|
779
|
+
return `Error listing files: ${e.message}`;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function handleReadFile(filePath) {
|
|
783
|
+
try {
|
|
784
|
+
const fullPath = path4.resolve(process.cwd(), filePath);
|
|
785
|
+
if (!fs4.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
|
|
786
|
+
const stats = fs4.statSync(fullPath);
|
|
787
|
+
if (stats.size > 100 * 1024) return `Error: File too large to read (${stats.size} bytes). Limit is 100KB.`;
|
|
788
|
+
return fs4.readFileSync(fullPath, "utf-8");
|
|
789
|
+
} catch (e) {
|
|
790
|
+
return `Error reading file: ${e.message}`;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function handleSearchFile(pattern) {
|
|
794
|
+
try {
|
|
795
|
+
const entries = fg.sync(pattern, { dot: true });
|
|
796
|
+
if (entries.length === 0) return "No files found matching pattern.";
|
|
797
|
+
return entries.slice(0, 50).join("\n");
|
|
798
|
+
} catch (e) {
|
|
799
|
+
return `Error searching files: ${e.message}`;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
function startSmartReplace(filePath, newContent, targetContent, tui2) {
|
|
803
|
+
if (!fs4.existsSync(filePath)) {
|
|
804
|
+
tui2.log.error(`\u274C File not found for modification: ${filePath}`);
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
const currentFileContent = fs4.readFileSync(filePath, "utf-8");
|
|
808
|
+
if (!currentFileContent.includes(targetContent)) {
|
|
809
|
+
tui2.log.error(`\u274C Target content not found in ${filePath}. Modification aborted.`);
|
|
810
|
+
console.log(colors.dim("--- Target Content Expected ---"));
|
|
811
|
+
console.log(targetContent.substring(0, 200) + "...");
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
const occurrences = currentFileContent.split(targetContent).length - 1;
|
|
815
|
+
if (occurrences > 1) {
|
|
816
|
+
tui2.log.error(`\u274C Ambiguous target: Found ${occurrences} occurrences in ${filePath}. Modification aborted.`);
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
const updatedContent = currentFileContent.replace(targetContent, newContent);
|
|
820
|
+
fs4.writeFileSync(filePath, updatedContent);
|
|
821
|
+
tui2.log.success(`\u2705 Smart Replace Applied: ${filePath}`);
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
async function handleRunCommand(command) {
|
|
825
|
+
const { spawn } = await import("child_process");
|
|
826
|
+
try {
|
|
827
|
+
tui.log.info(`\u{1F4BB} Executing: ${colors.dim(command)}`);
|
|
828
|
+
return new Promise((resolve) => {
|
|
829
|
+
const child = spawn(command, {
|
|
830
|
+
shell: true,
|
|
831
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
832
|
+
cwd: process.cwd()
|
|
833
|
+
});
|
|
834
|
+
let stdout = "";
|
|
835
|
+
let stderr = "";
|
|
836
|
+
const timer = setTimeout(() => {
|
|
837
|
+
child.kill();
|
|
838
|
+
resolve(`Error: Command timed out after 5 minutes.
|
|
839
|
+
Output so far:
|
|
840
|
+
${stdout}
|
|
841
|
+
${stderr}`);
|
|
842
|
+
}, 5 * 60 * 1e3);
|
|
843
|
+
child.stdout.on("data", (data) => {
|
|
844
|
+
const chunk = data.toString();
|
|
845
|
+
stdout += chunk;
|
|
846
|
+
});
|
|
847
|
+
child.stderr.on("data", (data) => {
|
|
848
|
+
stderr += data.toString();
|
|
849
|
+
});
|
|
850
|
+
child.on("close", (code) => {
|
|
851
|
+
clearTimeout(timer);
|
|
852
|
+
if (code === 0) {
|
|
853
|
+
resolve(stdout.trim() || "Command executed successfully (no output).");
|
|
854
|
+
} else {
|
|
855
|
+
resolve(`Command failed with exit code ${code}.
|
|
856
|
+
STDERR:
|
|
857
|
+
${stderr}
|
|
858
|
+
STDOUT:
|
|
859
|
+
${stdout}`);
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
child.on("error", (err) => {
|
|
863
|
+
clearTimeout(timer);
|
|
864
|
+
resolve(`Error executing command: ${err.message}`);
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
} catch (e) {
|
|
868
|
+
return `Error launching command: ${e.message}`;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/core/agents/specification-agent.ts
|
|
873
|
+
var AGENT_TYPE2 = "specification_agent";
|
|
874
|
+
var AGENT_ID2 = process.env.STACKSPOT_SPEC_AGENT_ID || "01KEPXTX37FTB4N672TZST4SGP";
|
|
875
|
+
async function interactiveSpecificationAgent(options = {}) {
|
|
876
|
+
FileLogger.init();
|
|
877
|
+
tui.intro("\u{1F3D7}\uFE0F Specification Agent");
|
|
878
|
+
let briefingContent = "";
|
|
879
|
+
let briefingPath = options.briefingPath;
|
|
880
|
+
if (!briefingPath) {
|
|
881
|
+
const files = fs5.readdirSync(process.cwd());
|
|
882
|
+
const defaultBriefing = files.find((f) => f.endsWith("_briefing.md"));
|
|
883
|
+
briefingPath = await tui.text({
|
|
884
|
+
message: "Path to Briefing file (Leave empty to skip)",
|
|
885
|
+
initialValue: defaultBriefing || "",
|
|
886
|
+
placeholder: "e.g., todo-list_briefing.md",
|
|
887
|
+
validate: (val) => {
|
|
888
|
+
if (val && !fs5.existsSync(val)) return "File not found";
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
if (tui.isCancel(briefingPath)) return;
|
|
893
|
+
if (briefingPath) {
|
|
894
|
+
try {
|
|
895
|
+
briefingContent = fs5.readFileSync(briefingPath, "utf-8");
|
|
896
|
+
tui.log.info(`Loaded briefing: ${colors.bold(briefingPath)}`);
|
|
897
|
+
} catch (e) {
|
|
898
|
+
tui.log.error(`Failed to read briefing: ${e}`);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
} else {
|
|
902
|
+
tui.log.info("Skipping briefing file. Starting fresh or exploring existing project.");
|
|
903
|
+
}
|
|
904
|
+
const initialPrompt = briefingContent ? `
|
|
905
|
+
Tenho o seguinte Documento de Briefing de Neg\xF3cio.
|
|
906
|
+
Por favor, analise-o e inicie o processo de defini\xE7\xE3o da Especifica\xE7\xE3o T\xE9cnica.
|
|
907
|
+
|
|
908
|
+
---
|
|
909
|
+
${briefingContent}
|
|
910
|
+
---
|
|
911
|
+
`.trim() : "Gostaria de ajuda com uma especifica\xE7\xE3o t\xE9cnica ou explorar este projeto.";
|
|
912
|
+
await runSpecLoop(initialPrompt, options.agentId);
|
|
913
|
+
}
|
|
914
|
+
async function runSpecLoop(initialMessage, overrideAgentId) {
|
|
915
|
+
let nextPrompt = initialMessage;
|
|
916
|
+
let keepGoing = true;
|
|
917
|
+
while (keepGoing) {
|
|
918
|
+
const spinner = tui.spinner();
|
|
919
|
+
spinner.start("\u{1F3D7}\uFE0F Specification Agent is thinking...");
|
|
920
|
+
let responseText = "";
|
|
921
|
+
let lastResponse = null;
|
|
922
|
+
try {
|
|
923
|
+
lastResponse = await callSpecAgentApi(nextPrompt, (chunk) => {
|
|
924
|
+
responseText += chunk;
|
|
925
|
+
try {
|
|
926
|
+
if (responseText.trim().startsWith("{")) {
|
|
927
|
+
spinner.message(colors.dim("Receiving structured data..."));
|
|
928
|
+
} else {
|
|
929
|
+
spinner.message(colors.dim("Thinking..."));
|
|
930
|
+
}
|
|
931
|
+
} catch (e) {
|
|
932
|
+
}
|
|
933
|
+
}, overrideAgentId);
|
|
934
|
+
spinner.stop("Response received");
|
|
935
|
+
if (lastResponse && lastResponse.actions) {
|
|
936
|
+
let executionResults = "";
|
|
937
|
+
let waitingForUser = false;
|
|
938
|
+
for (const action of lastResponse.actions) {
|
|
939
|
+
if (action.type === "talk_with_user") {
|
|
940
|
+
tui.log.info(colors.primary("\u{1F916} Architect:"));
|
|
941
|
+
console.log(action.content);
|
|
942
|
+
waitingForUser = true;
|
|
943
|
+
} else if (action.type === "list_files") {
|
|
944
|
+
tui.log.info(`\u{1F4C2} List files in: ${colors.bold(action.path || ".")}`);
|
|
945
|
+
const result = handleListFiles(action.path || ".");
|
|
946
|
+
executionResults += `[Action list_files(${action.path}) Result]:
|
|
947
|
+
${result}
|
|
948
|
+
|
|
949
|
+
`;
|
|
950
|
+
tui.log.info(colors.dim(`Files listed.`));
|
|
951
|
+
} else if (action.type === "read_file") {
|
|
952
|
+
tui.log.info(`\u{1F4D6} Read file: ${colors.bold(action.path || "")}`);
|
|
953
|
+
const result = handleReadFile(action.path || "");
|
|
954
|
+
executionResults += `[Action read_file(${action.path}) Result]:
|
|
955
|
+
${result}
|
|
956
|
+
|
|
957
|
+
`;
|
|
958
|
+
tui.log.info(colors.dim(`File read.`));
|
|
959
|
+
} else if (action.type === "search_file") {
|
|
960
|
+
tui.log.info(`\u{1F50D} Search file: ${colors.bold(action.path || "")}`);
|
|
961
|
+
const result = handleSearchFile(action.path || "");
|
|
962
|
+
executionResults += `[Action search_file(${action.path}) Result]:
|
|
963
|
+
${result}
|
|
964
|
+
|
|
965
|
+
`;
|
|
966
|
+
tui.log.info(colors.dim(`Files found.`));
|
|
967
|
+
} else if (["create_file", "modify_file", "delete_file"].includes(action.type)) {
|
|
968
|
+
tui.log.warning(`
|
|
969
|
+
\u{1F916} Agent wants to ${action.type}: ${colors.bold(action.path || "unknown")}`);
|
|
970
|
+
if (action.content) {
|
|
971
|
+
console.log(colors.dim("--- Content Preview ---"));
|
|
972
|
+
console.log(action.content.substring(0, 300) + "...");
|
|
973
|
+
console.log(colors.dim("-----------------------"));
|
|
974
|
+
}
|
|
975
|
+
const confirm = await tui.confirm({
|
|
976
|
+
message: `Approve ${action.type}?`,
|
|
977
|
+
active: "Yes",
|
|
978
|
+
inactive: "No"
|
|
979
|
+
});
|
|
980
|
+
if (confirm) {
|
|
981
|
+
if (action.path) {
|
|
982
|
+
try {
|
|
983
|
+
if (action.type === "create_file") {
|
|
984
|
+
fs5.writeFileSync(action.path, action.content || "");
|
|
985
|
+
tui.log.success(`\u2705 Created: ${action.path}`);
|
|
986
|
+
executionResults += `[Action create_file(${action.path})]: Success
|
|
987
|
+
|
|
988
|
+
`;
|
|
989
|
+
} else if (action.type === "modify_file") {
|
|
990
|
+
if (action.target_content) {
|
|
991
|
+
const success = startSmartReplace(action.path, action.content || "", action.target_content, tui);
|
|
992
|
+
executionResults += `[Action modify_file(${action.path})]: ${success ? "Success" : "Failed"}
|
|
993
|
+
|
|
994
|
+
`;
|
|
995
|
+
} else {
|
|
996
|
+
fs5.writeFileSync(action.path, action.content || "");
|
|
997
|
+
tui.log.success(`\u2705 Overwritten: ${action.path}`);
|
|
998
|
+
executionResults += `[Action modify_file(${action.path})]: Success (Overwrite)
|
|
999
|
+
|
|
1000
|
+
`;
|
|
1001
|
+
}
|
|
1002
|
+
} else if (action.type === "delete_file") {
|
|
1003
|
+
fs5.unlinkSync(action.path);
|
|
1004
|
+
tui.log.success(`\u2705 Deleted: ${action.path}`);
|
|
1005
|
+
executionResults += `[Action delete_file(${action.path})]: Success
|
|
1006
|
+
|
|
1007
|
+
`;
|
|
1008
|
+
}
|
|
1009
|
+
} catch (e) {
|
|
1010
|
+
tui.log.error(`\u274C Failed: ${e.message}`);
|
|
1011
|
+
executionResults += `[Action ${action.type}(${action.path})]: Error: ${e.message}
|
|
1012
|
+
|
|
1013
|
+
`;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
} else {
|
|
1017
|
+
tui.log.error("\u274C Action denied.");
|
|
1018
|
+
executionResults += `[Action ${action.type}]: User Denied
|
|
1019
|
+
|
|
1020
|
+
`;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (executionResults) {
|
|
1025
|
+
if (waitingForUser) {
|
|
1026
|
+
const userReply = await tui.text({
|
|
1027
|
+
message: "Your answer",
|
|
1028
|
+
placeholder: "Type your answer..."
|
|
1029
|
+
});
|
|
1030
|
+
if (tui.isCancel(userReply)) {
|
|
1031
|
+
keepGoing = false;
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
nextPrompt = `${executionResults}
|
|
1035
|
+
|
|
1036
|
+
User Reply: ${userReply}`;
|
|
1037
|
+
tui.log.info(colors.dim("Auto-replying with tool results..."));
|
|
1038
|
+
} else {
|
|
1039
|
+
nextPrompt = executionResults;
|
|
1040
|
+
FileLogger.log("SYSTEM", "Auto-replying with Tool Results", { length: executionResults.length });
|
|
1041
|
+
tui.log.info(colors.dim("Auto-replying with tool results..."));
|
|
1042
|
+
}
|
|
1043
|
+
} else if (waitingForUser) {
|
|
1044
|
+
const userReply = await tui.text({
|
|
1045
|
+
message: "Your answer",
|
|
1046
|
+
placeholder: "Type your answer..."
|
|
1047
|
+
});
|
|
1048
|
+
if (tui.isCancel(userReply)) {
|
|
1049
|
+
keepGoing = false;
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
nextPrompt = userReply;
|
|
1053
|
+
} else {
|
|
1054
|
+
tui.log.warning("No actions taken.");
|
|
1055
|
+
keepGoing = false;
|
|
1056
|
+
}
|
|
1057
|
+
} else {
|
|
1058
|
+
tui.log.warning("No actions received from agent.");
|
|
1059
|
+
keepGoing = false;
|
|
1060
|
+
}
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
spinner.stop("Error");
|
|
1063
|
+
tui.log.error(error.message);
|
|
1064
|
+
keepGoing = false;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
async function callSpecAgentApi(prompt, onChunk, agentId) {
|
|
1069
|
+
const realm = await getActiveRealm();
|
|
1070
|
+
const token = await tokenStorage.getToken(realm);
|
|
1071
|
+
if (!token) throw new Error("Not logged in");
|
|
1072
|
+
const conversationId = await conversationManager.getConversationId(AGENT_TYPE2);
|
|
1073
|
+
const payload = {
|
|
1074
|
+
user_prompt: prompt,
|
|
1075
|
+
streaming: true,
|
|
1076
|
+
stackspot_knowledge: false,
|
|
1077
|
+
return_ks_in_response: true,
|
|
1078
|
+
use_conversation: true,
|
|
1079
|
+
conversation_id: conversationId
|
|
1080
|
+
};
|
|
1081
|
+
const finalId = agentId || AGENT_ID2;
|
|
1082
|
+
const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${finalId}/chat`;
|
|
1083
|
+
let fullMsg = "";
|
|
1084
|
+
let raw = {};
|
|
1085
|
+
FileLogger.log("AGENT", "Calling Agent API", {
|
|
1086
|
+
agentId: finalId,
|
|
1087
|
+
conversationId,
|
|
1088
|
+
prompt: prompt.substring(0, 500)
|
|
1089
|
+
// Log summary of prompt
|
|
1090
|
+
});
|
|
1091
|
+
await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
|
|
1092
|
+
onChunk: (c) => {
|
|
1093
|
+
fullMsg += c;
|
|
1094
|
+
onChunk(c);
|
|
1095
|
+
},
|
|
1096
|
+
onComplete: (msg, metadata) => {
|
|
1097
|
+
const returnedId = metadata?.conversation_id;
|
|
1098
|
+
FileLogger.log("AGENT", "Response Complete", {
|
|
1099
|
+
conversationId,
|
|
1100
|
+
returnedId,
|
|
1101
|
+
messageLength: msg?.length
|
|
1102
|
+
});
|
|
1103
|
+
raw = {
|
|
1104
|
+
message: msg || fullMsg,
|
|
1105
|
+
conversation_id: returnedId || conversationId
|
|
1106
|
+
};
|
|
1107
|
+
},
|
|
1108
|
+
onError: (e) => {
|
|
1109
|
+
throw e;
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
const parsed = parseAgentResponse(raw);
|
|
1113
|
+
if (parsed.conversation_id) {
|
|
1114
|
+
await conversationManager.saveConversationId(AGENT_TYPE2, parsed.conversation_id);
|
|
1115
|
+
}
|
|
1116
|
+
return parsed;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/commands/scan.ts
|
|
1120
|
+
import { Command as Command2 } from "commander";
|
|
1121
|
+
|
|
1122
|
+
// src/core/agents/scan-agent.ts
|
|
1123
|
+
import fs6 from "fs";
|
|
1124
|
+
import path5 from "path";
|
|
1125
|
+
var AGENT_TYPE3 = "scan_agent";
|
|
1126
|
+
var AGENT_ID3 = process.env.STACKSPOT_SCAN_AGENT_ID || "01KEQ9AHWB550J2244YBH3QATN";
|
|
1127
|
+
async function interactiveScanAgent(options = {}) {
|
|
1128
|
+
FileLogger.init();
|
|
1129
|
+
tui.intro("\u{1F575}\uFE0F\u200D\u2642\uFE0F Scan Agent");
|
|
1130
|
+
const config = ConfigManager.getInstance().getConfig();
|
|
1131
|
+
const language = config.language || "English";
|
|
1132
|
+
const projectRoot = process.cwd();
|
|
1133
|
+
let outputFile;
|
|
1134
|
+
if (options.output) {
|
|
1135
|
+
outputFile = path5.resolve(process.cwd(), options.output);
|
|
1136
|
+
} else {
|
|
1137
|
+
const outputDir = path5.resolve(projectRoot, "_sharkrc");
|
|
1138
|
+
if (!fs6.existsSync(outputDir)) {
|
|
1139
|
+
const stat = fs6.existsSync(outputDir) ? fs6.statSync(outputDir) : null;
|
|
1140
|
+
if (stat && stat.isFile()) {
|
|
1141
|
+
tui.log.warning(`Warning: '_sharkrc' exists as a file. Using '_bmad/project-context' instead to avoid overwrite.`);
|
|
1142
|
+
const fallbackDir = path5.resolve(projectRoot, "_bmad/project-context");
|
|
1143
|
+
if (!fs6.existsSync(fallbackDir)) fs6.mkdirSync(fallbackDir, { recursive: true });
|
|
1144
|
+
outputFile = path5.join(fallbackDir, "project-context.md");
|
|
1145
|
+
} else {
|
|
1146
|
+
fs6.mkdirSync(outputDir, { recursive: true });
|
|
1147
|
+
outputFile = path5.join(outputDir, "project-context.md");
|
|
1148
|
+
}
|
|
1149
|
+
} else {
|
|
1150
|
+
fs6.mkdirSync(outputDir, { recursive: true });
|
|
1151
|
+
outputFile = path5.join(outputDir, "project-context.md");
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
tui.log.info(`Scanning project at: ${colors.bold(projectRoot)}`);
|
|
1155
|
+
tui.log.info(`Output targeted at: ${colors.bold(outputFile)}`);
|
|
1156
|
+
tui.log.info(`Language: ${colors.bold(language)}`);
|
|
1157
|
+
const configFileRelative = path5.relative(projectRoot, outputFile);
|
|
1158
|
+
const superPrompt = `
|
|
1159
|
+
You are the **Scan Agent**, an expert software architect and analyst.
|
|
1160
|
+
Your mission is to explore this project's codebase and generate a comprehensive context file that will be used by other AI agents (specifically a Developer Agent) to understand how to work on this project.
|
|
1161
|
+
|
|
1162
|
+
**Goal**: Create a markdown file at: \`${configFileRelative}\`.
|
|
1163
|
+
|
|
1164
|
+
**LANGUAGE INSTRUCTION**:
|
|
1165
|
+
You MUST write the content of the \`project-context.md\` file in **${language}**.
|
|
1166
|
+
Also, strictly interact with tools using the appropriate payload.
|
|
1167
|
+
|
|
1168
|
+
**Instructions**:
|
|
1169
|
+
1. **Analyze Structure**: Use \`list_files\` to understand the root directory and key subdirectories (src, tools, config, etc.).
|
|
1170
|
+
2. **Identify Tech Stack**: Use \`read_file\` or \`search_file\` on key manifests (package.json, pom.xml, go.mod, Dockerfile, etc.) to determine languages, frameworks, and versions.
|
|
1171
|
+
3. **Map Architecture**: Infer the architectural pattern (Monolith? Microservices? Clean Architecture?) based on folder structure and key files.
|
|
1172
|
+
4. **Document Key Paths**: Identify where source code, tests, and configs live.
|
|
1173
|
+
|
|
1174
|
+
**Output Format** (Markdown):
|
|
1175
|
+
The final action MUST be \`create_file\` (or \`modify_file\`) for the target file with the following structure:
|
|
1176
|
+
|
|
1177
|
+
# Project Context
|
|
1178
|
+
|
|
1179
|
+
## Overview
|
|
1180
|
+
[Brief description of what this project seems to be]
|
|
1181
|
+
|
|
1182
|
+
## Tech Stack
|
|
1183
|
+
- **Language**: [e.g. TypeScript]
|
|
1184
|
+
- **Framework**: [e.g. React, Express, NestJS]
|
|
1185
|
+
- **Build Tool**: [e.g. Vite, Webpack]
|
|
1186
|
+
- **Database**: [e.g. PostgreSQL, Prisma] (if detected)
|
|
1187
|
+
|
|
1188
|
+
## Architecture
|
|
1189
|
+
[Description of the folder structure and architectural patterns detected]
|
|
1190
|
+
|
|
1191
|
+
## Key Locations
|
|
1192
|
+
- **Source**: [path/to/src]
|
|
1193
|
+
- **Tests**: [path/to/tests]
|
|
1194
|
+
- **Config**: [path/to/config]
|
|
1195
|
+
|
|
1196
|
+
## Commands
|
|
1197
|
+
[List of discovered npm scripts or makefile commands for dev, build, test]
|
|
1198
|
+
|
|
1199
|
+
---
|
|
1200
|
+
|
|
1201
|
+
**Rules**:
|
|
1202
|
+
- Do NOT guess. If you are unsure, check the file.
|
|
1203
|
+
- Be concise.
|
|
1204
|
+
- Focus on FACTS that a Developer Agent needs to know to write code correcty.
|
|
1205
|
+
- Start by listing the root directory.
|
|
1206
|
+
`.trim();
|
|
1207
|
+
await runScanLoop(superPrompt, outputFile);
|
|
1208
|
+
}
|
|
1209
|
+
async function runScanLoop(initialPrompt, targetPath) {
|
|
1210
|
+
let nextPrompt = initialPrompt;
|
|
1211
|
+
let keepGoing = true;
|
|
1212
|
+
let stepCount = 0;
|
|
1213
|
+
const MAX_STEPS = 15;
|
|
1214
|
+
while (keepGoing && stepCount < MAX_STEPS) {
|
|
1215
|
+
stepCount++;
|
|
1216
|
+
const spinner = tui.spinner();
|
|
1217
|
+
spinner.start(`\u{1F575}\uFE0F\u200D\u2642\uFE0F Scan Agent analyzing (Step ${stepCount}/${MAX_STEPS})...`);
|
|
1218
|
+
let responseText = "";
|
|
1219
|
+
let lastResponse = null;
|
|
1220
|
+
try {
|
|
1221
|
+
lastResponse = await callScanAgentApi(nextPrompt, (chunk) => {
|
|
1222
|
+
responseText += chunk;
|
|
1223
|
+
});
|
|
1224
|
+
spinner.stop("Step complete");
|
|
1225
|
+
if (lastResponse && lastResponse.actions) {
|
|
1226
|
+
let executionResults = "";
|
|
1227
|
+
let fileCreated = false;
|
|
1228
|
+
for (const action of lastResponse.actions) {
|
|
1229
|
+
if (action.type === "list_files") {
|
|
1230
|
+
tui.log.info(`\u{1F4C2} Scanning dir: ${colors.bold(action.path || ".")}`);
|
|
1231
|
+
const result = handleListFiles(action.path || ".");
|
|
1232
|
+
executionResults += `[Action list_files(${action.path}) Result]:
|
|
1233
|
+
${result}
|
|
1234
|
+
|
|
1235
|
+
`;
|
|
1236
|
+
} else if (action.type === "read_file") {
|
|
1237
|
+
tui.log.info(`\u{1F4D6} Reading file: ${colors.bold(action.path || "")}`);
|
|
1238
|
+
const result = handleReadFile(action.path || "");
|
|
1239
|
+
executionResults += `[Action read_file(${action.path}) Result]:
|
|
1240
|
+
${result}
|
|
1241
|
+
|
|
1242
|
+
`;
|
|
1243
|
+
} else if (action.type === "search_file") {
|
|
1244
|
+
tui.log.info(`\u{1F50D} Searching: ${colors.bold(action.path || "")}`);
|
|
1245
|
+
const result = handleSearchFile(action.path || "");
|
|
1246
|
+
executionResults += `[Action search_file(${action.path}) Result]:
|
|
1247
|
+
${result}
|
|
1248
|
+
|
|
1249
|
+
`;
|
|
1250
|
+
} else if (action.type === "create_file" || action.type === "modify_file") {
|
|
1251
|
+
const resolvedActionPath = path5.resolve(action.path || "");
|
|
1252
|
+
const resolvedTargetPath = path5.resolve(targetPath);
|
|
1253
|
+
let isTarget = resolvedActionPath === resolvedTargetPath;
|
|
1254
|
+
if (!isTarget && path5.basename(action.path || "") === "project-context.md") {
|
|
1255
|
+
tui.log.warning(`Agent targeted '${action.path}' but we enforce '${path5.relative(process.cwd(), targetPath)}'. Redirecting write.`);
|
|
1256
|
+
isTarget = true;
|
|
1257
|
+
action.path = targetPath;
|
|
1258
|
+
}
|
|
1259
|
+
if (isTarget) {
|
|
1260
|
+
const finalPath = targetPath;
|
|
1261
|
+
if (action.type === "create_file") {
|
|
1262
|
+
fs6.writeFileSync(finalPath, action.content || "");
|
|
1263
|
+
tui.log.success(`\u2705 Generated Context: ${finalPath}`);
|
|
1264
|
+
fileCreated = true;
|
|
1265
|
+
} else {
|
|
1266
|
+
fs6.writeFileSync(finalPath, action.content || "");
|
|
1267
|
+
tui.log.success(`\u2705 Updated Context: ${finalPath}`);
|
|
1268
|
+
fileCreated = true;
|
|
1269
|
+
}
|
|
1270
|
+
executionResults += `[Action ${action.type}]: Success. Task Completed.
|
|
1271
|
+
`;
|
|
1272
|
+
} else {
|
|
1273
|
+
tui.log.warning(`Agent wants to write to unexpected file: ${action.path}`);
|
|
1274
|
+
executionResults += `[Action ${action.type}]: Skipped (Scan Agent only writes context file)
|
|
1275
|
+
`;
|
|
1276
|
+
}
|
|
1277
|
+
} else if (action.type === "talk_with_user") {
|
|
1278
|
+
tui.log.info(colors.primary("\u{1F916} Scan Agent asks:"));
|
|
1279
|
+
console.log(action.content);
|
|
1280
|
+
const reply = await tui.text({ message: "Agent needs input:", placeholder: "Reply..." });
|
|
1281
|
+
executionResults += `[User Reply]: ${reply}
|
|
1282
|
+
`;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
if (fileCreated) {
|
|
1286
|
+
tui.log.success("\u2728 Scan completed successfully!");
|
|
1287
|
+
keepGoing = false;
|
|
1288
|
+
} else {
|
|
1289
|
+
nextPrompt = executionResults;
|
|
1290
|
+
FileLogger.log("SCAN", "Auto-replying with results", { length: executionResults.length });
|
|
1291
|
+
}
|
|
1292
|
+
} else {
|
|
1293
|
+
if (stepCount > 1) {
|
|
1294
|
+
tui.log.warning("Scan Agent stopped without actions.");
|
|
1295
|
+
keepGoing = false;
|
|
1296
|
+
} else {
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
spinner.stop("Error");
|
|
1301
|
+
tui.log.error(error.message);
|
|
1302
|
+
keepGoing = false;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
async function callScanAgentApi(prompt, onChunk) {
|
|
1307
|
+
const realm = await getActiveRealm();
|
|
1308
|
+
const token = await tokenStorage.getToken(realm);
|
|
1309
|
+
if (!token) throw new Error("Not logged in");
|
|
1310
|
+
let conversationId = await conversationManager.getConversationId(AGENT_TYPE3);
|
|
1311
|
+
const payload = {
|
|
1312
|
+
user_prompt: prompt,
|
|
1313
|
+
streaming: true,
|
|
1314
|
+
stackspot_knowledge: false,
|
|
1315
|
+
return_ks_in_response: true,
|
|
1316
|
+
use_conversation: true,
|
|
1317
|
+
conversation_id: conversationId
|
|
1318
|
+
};
|
|
1319
|
+
const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${AGENT_ID3}/chat`;
|
|
1320
|
+
let fullMsg = "";
|
|
1321
|
+
let raw = {};
|
|
1322
|
+
FileLogger.log("SCAN", "Calling API", { promptLength: prompt.length });
|
|
1323
|
+
await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
|
|
1324
|
+
onChunk: (c) => {
|
|
1325
|
+
fullMsg += c;
|
|
1326
|
+
onChunk(c);
|
|
1327
|
+
},
|
|
1328
|
+
onComplete: (msg, metadata) => {
|
|
1329
|
+
const returnedId = metadata?.conversation_id;
|
|
1330
|
+
raw = {
|
|
1331
|
+
message: msg || fullMsg,
|
|
1332
|
+
conversation_id: returnedId || conversationId
|
|
1333
|
+
};
|
|
1334
|
+
},
|
|
1335
|
+
onError: (e) => {
|
|
1336
|
+
throw e;
|
|
1337
|
+
}
|
|
1338
|
+
});
|
|
1339
|
+
const parsed = parseAgentResponse(raw);
|
|
1340
|
+
if (parsed.conversation_id) {
|
|
1341
|
+
await conversationManager.saveConversationId(AGENT_TYPE3, parsed.conversation_id);
|
|
1342
|
+
}
|
|
1343
|
+
return parsed;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// src/commands/scan.ts
|
|
1347
|
+
var scanCommand = new Command2("scan").description("Analyze the project and generate context documentation").option("-o, --output <path>", "Output file path (default: _bmad/project-context/project-context.md)").option("--depth <level>", "Scan depth (quick, deep, exhaustive)", "quick").action(async (options) => {
|
|
1348
|
+
try {
|
|
1349
|
+
await interactiveScanAgent(options);
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
console.error("Error during scan:", error.message);
|
|
1352
|
+
process.exit(1);
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
// src/commands/dev.ts
|
|
1357
|
+
import { Command as Command3 } from "commander";
|
|
1358
|
+
|
|
1359
|
+
// src/core/agents/developer-agent.ts
|
|
1360
|
+
import fs7 from "fs";
|
|
1361
|
+
import path6 from "path";
|
|
1362
|
+
var AGENT_TYPE4 = "developer_agent";
|
|
1363
|
+
var AGENT_ID4 = process.env.STACKSPOT_DEV_AGENT_ID || "01KEQCGJ65YENRA4QBXVN1YFFX";
|
|
1364
|
+
async function interactiveDeveloperAgent(options = {}) {
|
|
1365
|
+
FileLogger.init();
|
|
1366
|
+
tui.intro("\u{1F988} Shark Dev Agent");
|
|
1367
|
+
if (AGENT_ID4 === "PENDING_CONFIGURATION") {
|
|
1368
|
+
tui.log.error("\u274C STACKSPOT_DEV_AGENT_ID not configured in .env");
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
const projectRoot = process.cwd();
|
|
1372
|
+
let contextContent = "";
|
|
1373
|
+
const defaultContextPath = path6.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
1374
|
+
const specificContextPath = options.context ? path6.resolve(projectRoot, options.context) : defaultContextPath;
|
|
1375
|
+
if (fs7.existsSync(specificContextPath)) {
|
|
1376
|
+
try {
|
|
1377
|
+
contextContent = fs7.readFileSync(specificContextPath, "utf-8");
|
|
1378
|
+
tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path6.relative(projectRoot, specificContextPath))}`);
|
|
1379
|
+
} catch (e) {
|
|
1380
|
+
tui.log.warning(`Failed to read context file: ${e}`);
|
|
1381
|
+
}
|
|
1382
|
+
} else {
|
|
1383
|
+
tui.log.warning(`\u26A0\uFE0F No context file found. Agent will run without pre-loaded context.`);
|
|
1384
|
+
}
|
|
1385
|
+
let nextPrompt = options.task || "I'm ready to help. What's the task?";
|
|
1386
|
+
if (contextContent) {
|
|
1387
|
+
nextPrompt += `
|
|
1388
|
+
|
|
1389
|
+
--- PROJECT CONTEXT ---
|
|
1390
|
+
${contextContent}
|
|
1391
|
+
-----------------------`;
|
|
1392
|
+
}
|
|
1393
|
+
let keepGoing = true;
|
|
1394
|
+
const spinner = tui.spinner();
|
|
1395
|
+
while (keepGoing) {
|
|
1396
|
+
try {
|
|
1397
|
+
spinner.start("Waiting for Agent...");
|
|
1398
|
+
let lastResponse = null;
|
|
1399
|
+
await callDevAgentApi(nextPrompt, (chunk) => {
|
|
1400
|
+
if (!lastResponse) {
|
|
1401
|
+
}
|
|
1402
|
+
}).then((resp) => {
|
|
1403
|
+
lastResponse = resp;
|
|
1404
|
+
});
|
|
1405
|
+
spinner.stop("Response received");
|
|
1406
|
+
if (lastResponse && lastResponse.actions) {
|
|
1407
|
+
const response = lastResponse;
|
|
1408
|
+
let executionResults = "";
|
|
1409
|
+
let waitingForUser = false;
|
|
1410
|
+
for (const action of response.actions) {
|
|
1411
|
+
if (action.type === "talk_with_user") {
|
|
1412
|
+
tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
|
|
1413
|
+
console.log(action.content);
|
|
1414
|
+
waitingForUser = true;
|
|
1415
|
+
} else if (action.type === "list_files") {
|
|
1416
|
+
tui.log.info(`\u{1F4C2} Scanning dir: ${colors.dim(action.path || ".")}`);
|
|
1417
|
+
const result = handleListFiles(action.path || ".");
|
|
1418
|
+
executionResults += `[Action list_files(${action.path}) Result]:
|
|
1419
|
+
${result}
|
|
1420
|
+
|
|
1421
|
+
`;
|
|
1422
|
+
} else if (action.type === "read_file") {
|
|
1423
|
+
tui.log.info(`\u{1F4D6} Reading: ${colors.dim(action.path || "")}`);
|
|
1424
|
+
const result = handleReadFile(action.path || "");
|
|
1425
|
+
executionResults += `[Action read_file(${action.path}) Result]:
|
|
1426
|
+
${result}
|
|
1427
|
+
|
|
1428
|
+
`;
|
|
1429
|
+
} else if (action.type === "search_file") {
|
|
1430
|
+
tui.log.info(`\u{1F50D} Searching: ${colors.dim(action.path || "")}`);
|
|
1431
|
+
const result = handleSearchFile(action.path || "");
|
|
1432
|
+
executionResults += `[Action search_file(${action.path}) Result]:
|
|
1433
|
+
${result}
|
|
1434
|
+
|
|
1435
|
+
`;
|
|
1436
|
+
} else if (action.type === "run_command") {
|
|
1437
|
+
const cmd = action.command || "";
|
|
1438
|
+
tui.log.info(`\u{1F4BB} Executing: ${colors.dim(cmd)}`);
|
|
1439
|
+
const confirm = await tui.confirm({
|
|
1440
|
+
message: `Execute command: ${cmd}?`,
|
|
1441
|
+
active: "Yes",
|
|
1442
|
+
inactive: "No"
|
|
1443
|
+
});
|
|
1444
|
+
if (confirm) {
|
|
1445
|
+
const result = await handleRunCommand(cmd);
|
|
1446
|
+
executionResults += `[Action run_command(${cmd}) Result]:
|
|
1447
|
+
${result}
|
|
1448
|
+
|
|
1449
|
+
`;
|
|
1450
|
+
} else {
|
|
1451
|
+
executionResults += `[Action run_command]: User blocked execution.
|
|
1452
|
+
|
|
1453
|
+
`;
|
|
1454
|
+
}
|
|
1455
|
+
} else if (["create_file", "modify_file"].includes(action.type)) {
|
|
1456
|
+
const isCreate = action.type === "create_file";
|
|
1457
|
+
const filePath = action.path || "";
|
|
1458
|
+
tui.log.warning(`
|
|
1459
|
+
\u{1F916} Agent wants to ${isCreate ? "CREATE" : "MODIFY"}: ${colors.bold(filePath)}`);
|
|
1460
|
+
if (action.content) {
|
|
1461
|
+
console.log(colors.dim("--- Content ---\n") + action.content.substring(0, 200) + "...\n" + colors.dim("---------------"));
|
|
1462
|
+
}
|
|
1463
|
+
const confirm = await tui.confirm({
|
|
1464
|
+
message: `Approve changes to ${filePath}?`,
|
|
1465
|
+
active: "Yes",
|
|
1466
|
+
inactive: "No"
|
|
1467
|
+
});
|
|
1468
|
+
if (confirm) {
|
|
1469
|
+
if (filePath) {
|
|
1470
|
+
const targetPath = path6.resolve(projectRoot, filePath);
|
|
1471
|
+
const dir = path6.dirname(targetPath);
|
|
1472
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
1473
|
+
if (isCreate) {
|
|
1474
|
+
fs7.writeFileSync(targetPath, action.content || "");
|
|
1475
|
+
tui.log.success(`\u2705 Created: ${filePath}`);
|
|
1476
|
+
executionResults += `[Action create_file(${filePath})]: Success
|
|
1477
|
+
|
|
1478
|
+
`;
|
|
1479
|
+
} else {
|
|
1480
|
+
if (action.target_content) {
|
|
1481
|
+
const success = startSmartReplace(filePath, action.content || "", action.target_content, tui);
|
|
1482
|
+
executionResults += `[Action modify_file(${filePath})]: ${success ? "Success" : "Failed"}
|
|
1483
|
+
|
|
1484
|
+
`;
|
|
1485
|
+
} else {
|
|
1486
|
+
tui.log.error("\u274C Missing target_content for modification.");
|
|
1487
|
+
executionResults += `[Action modify_file]: Failed. Missing target_content.
|
|
1488
|
+
|
|
1489
|
+
`;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
} else {
|
|
1494
|
+
tui.log.error("\u274C Denied.");
|
|
1495
|
+
executionResults += `[Action ${action.type}]: User Denied.
|
|
1496
|
+
|
|
1497
|
+
`;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (executionResults) {
|
|
1502
|
+
if (waitingForUser) {
|
|
1503
|
+
const userReply = await tui.text({ message: "Your answer:" });
|
|
1504
|
+
if (tui.isCancel(userReply)) {
|
|
1505
|
+
keepGoing = false;
|
|
1506
|
+
break;
|
|
1507
|
+
}
|
|
1508
|
+
nextPrompt = `${executionResults}
|
|
1509
|
+
|
|
1510
|
+
User Reply: ${userReply}`;
|
|
1511
|
+
} else {
|
|
1512
|
+
nextPrompt = executionResults;
|
|
1513
|
+
tui.log.info(colors.dim("Sending tool results to agent..."));
|
|
1514
|
+
}
|
|
1515
|
+
} else if (waitingForUser) {
|
|
1516
|
+
const userReply = await tui.text({ message: "Your answer:" });
|
|
1517
|
+
if (tui.isCancel(userReply)) {
|
|
1518
|
+
keepGoing = false;
|
|
1519
|
+
break;
|
|
1520
|
+
}
|
|
1521
|
+
nextPrompt = userReply;
|
|
1522
|
+
} else {
|
|
1523
|
+
if (response.message) {
|
|
1524
|
+
tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
|
|
1525
|
+
console.log(response.message);
|
|
1526
|
+
const userReply = await tui.text({ message: "Your answer:" });
|
|
1527
|
+
if (tui.isCancel(userReply)) {
|
|
1528
|
+
keepGoing = false;
|
|
1529
|
+
} else {
|
|
1530
|
+
nextPrompt = userReply;
|
|
1531
|
+
}
|
|
1532
|
+
} else {
|
|
1533
|
+
tui.log.warning("Agent took no actions.");
|
|
1534
|
+
keepGoing = false;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
} else {
|
|
1538
|
+
tui.log.warning("Invalid response from agent.");
|
|
1539
|
+
keepGoing = false;
|
|
1540
|
+
}
|
|
1541
|
+
} catch (e) {
|
|
1542
|
+
spinner.stop("Error");
|
|
1543
|
+
tui.log.error(e.message);
|
|
1544
|
+
keepGoing = false;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
tui.outro("\u{1F44B} Shark Dev Session Ended");
|
|
1548
|
+
}
|
|
1549
|
+
async function callDevAgentApi(prompt, onChunk) {
|
|
1550
|
+
const realm = await getActiveRealm();
|
|
1551
|
+
const token = await tokenStorage.getToken(realm);
|
|
1552
|
+
if (!token) throw new Error("Not logged in. Run shark login.");
|
|
1553
|
+
const conversationId = await conversationManager.getConversationId(AGENT_TYPE4);
|
|
1554
|
+
const payload = {
|
|
1555
|
+
user_prompt: prompt,
|
|
1556
|
+
streaming: true,
|
|
1557
|
+
use_conversation: true,
|
|
1558
|
+
conversation_id: conversationId,
|
|
1559
|
+
stackspot_knowledge: false
|
|
1560
|
+
// Dev Agent focuses on project context
|
|
1561
|
+
};
|
|
1562
|
+
const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${AGENT_ID4}/chat`;
|
|
1563
|
+
let fullMsg = "";
|
|
1564
|
+
let raw = {};
|
|
1565
|
+
await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
|
|
1566
|
+
onChunk: (c) => {
|
|
1567
|
+
fullMsg += c;
|
|
1568
|
+
onChunk(c);
|
|
1569
|
+
},
|
|
1570
|
+
onComplete: (msg, metadata) => {
|
|
1571
|
+
const returnedId = metadata?.conversation_id;
|
|
1572
|
+
raw = {
|
|
1573
|
+
message: msg || fullMsg,
|
|
1574
|
+
conversation_id: returnedId || conversationId
|
|
1575
|
+
};
|
|
1576
|
+
},
|
|
1577
|
+
onError: (e) => {
|
|
1578
|
+
throw e;
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
const parsed = parseAgentResponse(raw);
|
|
1582
|
+
if (parsed.conversation_id) {
|
|
1583
|
+
await conversationManager.saveConversationId(AGENT_TYPE4, parsed.conversation_id);
|
|
1584
|
+
}
|
|
1585
|
+
return parsed;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// src/commands/dev.ts
|
|
1589
|
+
var devCommand = new Command3("dev").description("Starts the Shark Developer Agent (Shark Dev)").option("-t, --task <type>", "Initial task description").option("-c, --context <path>", "Path to custom context file").action(async (options) => {
|
|
1590
|
+
await interactiveDeveloperAgent(options);
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
// src/commands/qa.ts
|
|
1594
|
+
import { Command as Command4 } from "commander";
|
|
1595
|
+
|
|
1596
|
+
// src/core/agents/qa-agent.ts
|
|
1597
|
+
import fs8 from "fs";
|
|
1598
|
+
import path7 from "path";
|
|
1599
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1600
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1601
|
+
var AGENT_TYPE5 = "qa_agent";
|
|
1602
|
+
var AGENT_ID5 = process.env.STACKSPOT_QA_AGENT_ID || "01KEQFJZ3Q3JER11NH22HEZX9X";
|
|
1603
|
+
var ChromeDevToolsClient = class {
|
|
1604
|
+
client = null;
|
|
1605
|
+
transport = null;
|
|
1606
|
+
async connect() {
|
|
1607
|
+
if (this.client) return;
|
|
1608
|
+
try {
|
|
1609
|
+
this.transport = new StdioClientTransport({
|
|
1610
|
+
command: "npx",
|
|
1611
|
+
args: ["-y", "chrome-devtools-mcp@latest"]
|
|
1612
|
+
});
|
|
1613
|
+
this.client = new Client({
|
|
1614
|
+
name: "shark-qa-client",
|
|
1615
|
+
version: "1.0.0"
|
|
1616
|
+
}, {
|
|
1617
|
+
capabilities: {}
|
|
1618
|
+
});
|
|
1619
|
+
await this.client.connect(this.transport);
|
|
1620
|
+
tui.log.success("\u{1F50C} Connected to Chrome DevTools MCP");
|
|
1621
|
+
} catch (e) {
|
|
1622
|
+
tui.log.error(`Failed to connect to Chrome MCP: ${e.message}`);
|
|
1623
|
+
throw e;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
async callTool(name, args) {
|
|
1627
|
+
if (!this.client) await this.connect();
|
|
1628
|
+
try {
|
|
1629
|
+
const result = await this.client.callTool({
|
|
1630
|
+
name,
|
|
1631
|
+
arguments: args
|
|
1632
|
+
});
|
|
1633
|
+
return result;
|
|
1634
|
+
} catch (e) {
|
|
1635
|
+
return { isError: true, content: [{ type: "text", text: `MCP Error: ${e.message}` }] };
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
async close() {
|
|
1639
|
+
if (this.transport) {
|
|
1640
|
+
await this.transport.close();
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
var mcpClient = new ChromeDevToolsClient();
|
|
1645
|
+
async function runQAAgent(options) {
|
|
1646
|
+
if (!AGENT_ID5) {
|
|
1647
|
+
tui.log.error("\u274C STACKSPOT_QA_AGENT_ID not configured.");
|
|
1648
|
+
tui.log.info("Please run: set STACKSPOT_QA_AGENT_ID=<your-id>");
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
await mcpClient.connect();
|
|
1652
|
+
tui.intro("\u{1F988} Shark QA Agent");
|
|
1653
|
+
tui.log.info("Connecting to Chrome DevTools...");
|
|
1654
|
+
const realm = await getActiveRealm();
|
|
1655
|
+
const token = await tokenStorage.getToken(realm);
|
|
1656
|
+
if (!token) {
|
|
1657
|
+
tui.log.error('Authentication required. Run "shark login".');
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
let projectContext = "";
|
|
1661
|
+
try {
|
|
1662
|
+
const contextPath = path7.join(process.cwd(), "_sharkrc", "project-context.md");
|
|
1663
|
+
if (fs8.existsSync(contextPath)) {
|
|
1664
|
+
projectContext = fs8.readFileSync(contextPath, "utf-8");
|
|
1665
|
+
tui.log.info(`\u{1F4D8} Context loaded from: _sharkrc/project-context.md`);
|
|
1666
|
+
}
|
|
1667
|
+
} catch (e) {
|
|
1668
|
+
}
|
|
1669
|
+
let userMessage = `CONTEXTO DO PROJETO:
|
|
1670
|
+
${projectContext}
|
|
1671
|
+
|
|
1672
|
+
`;
|
|
1673
|
+
if (options.initialUrl) {
|
|
1674
|
+
userMessage += `URL ALVO: ${options.initialUrl}
|
|
1675
|
+
`;
|
|
1676
|
+
}
|
|
1677
|
+
if (options.scenario) {
|
|
1678
|
+
userMessage += `CEN\xC1RIO DE TESTE: ${options.scenario}
|
|
1679
|
+
`;
|
|
1680
|
+
} else {
|
|
1681
|
+
userMessage += `Por favor, aguarde instru\xE7\xF5es do usu\xE1rio.`;
|
|
1682
|
+
}
|
|
1683
|
+
let keepRunning = true;
|
|
1684
|
+
while (keepRunning) {
|
|
1685
|
+
const spinner = tui.spinner();
|
|
1686
|
+
spinner.start("\u{1F916} Shark QA is thinking...");
|
|
1687
|
+
let agentResponseText = "";
|
|
1688
|
+
let agentResponse = null;
|
|
1689
|
+
try {
|
|
1690
|
+
const existingConversationId = await conversationManager.getConversationId(AGENT_TYPE5);
|
|
1691
|
+
await sseClient.streamAgentResponse(
|
|
1692
|
+
`https://genai-inference-app.stackspot.com/v1/agent/${AGENT_ID5}/chat`,
|
|
1693
|
+
{
|
|
1694
|
+
user_prompt: userMessage,
|
|
1695
|
+
streaming: true,
|
|
1696
|
+
use_conversation: true,
|
|
1697
|
+
conversation_id: existingConversationId
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
"Authorization": `Bearer ${token}`
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
onChunk: (chunk) => {
|
|
1704
|
+
agentResponseText += chunk;
|
|
1705
|
+
if (agentResponseText.length > 10 && agentResponseText.trim().startsWith("{")) {
|
|
1706
|
+
spinner.message("Receiving structured plan...");
|
|
1707
|
+
}
|
|
1708
|
+
},
|
|
1709
|
+
onComplete: (fullText, metadata) => {
|
|
1710
|
+
try {
|
|
1711
|
+
if (metadata?.conversation_id) {
|
|
1712
|
+
conversationManager.saveConversationId(AGENT_TYPE5, metadata.conversation_id);
|
|
1713
|
+
}
|
|
1714
|
+
agentResponse = parseAgentResponse(fullText || agentResponseText);
|
|
1715
|
+
} catch (e) {
|
|
1716
|
+
tui.log.error(`Parse Error: ${e.message}`);
|
|
1717
|
+
agentResponse = { actions: [], summary: "Error parsing response", message: fullText };
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
);
|
|
1722
|
+
spinner.stop("Response Received");
|
|
1723
|
+
} catch (error) {
|
|
1724
|
+
spinner.stop("Communication Error", 1);
|
|
1725
|
+
tui.log.error(error.message);
|
|
1726
|
+
keepRunning = false;
|
|
1727
|
+
break;
|
|
1728
|
+
}
|
|
1729
|
+
if (!agentResponse) continue;
|
|
1730
|
+
if (agentResponse.summary) {
|
|
1731
|
+
tui.log.info(colors.primary(`\u{1F4CB} Plan: ${agentResponse.summary}`));
|
|
1732
|
+
}
|
|
1733
|
+
if (agentResponse.actions.length === 0) {
|
|
1734
|
+
const reply = await tui.text({
|
|
1735
|
+
message: "\u{1F916} Shark QA:",
|
|
1736
|
+
placeholder: "Your reply..."
|
|
1737
|
+
});
|
|
1738
|
+
if (tui.isCancel(reply)) {
|
|
1739
|
+
keepRunning = false;
|
|
1740
|
+
} else {
|
|
1741
|
+
userMessage = reply;
|
|
1742
|
+
}
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
for (const action of agentResponse.actions) {
|
|
1746
|
+
tui.log.info(colors.dim(`Executing: ${action.type}`));
|
|
1747
|
+
let result = "";
|
|
1748
|
+
try {
|
|
1749
|
+
switch (action.type) {
|
|
1750
|
+
case "talk_with_user":
|
|
1751
|
+
const reply = await tui.text({
|
|
1752
|
+
message: `\u{1F916} ${action.content}`
|
|
1753
|
+
});
|
|
1754
|
+
if (tui.isCancel(reply)) keepRunning = false;
|
|
1755
|
+
else result = reply;
|
|
1756
|
+
break;
|
|
1757
|
+
case "use_mcp_tool":
|
|
1758
|
+
if (action.tool_name) {
|
|
1759
|
+
tui.log.info(`\u{1F527} MCP Tool: ${colors.bold(action.tool_name)}`);
|
|
1760
|
+
let args = {};
|
|
1761
|
+
try {
|
|
1762
|
+
args = typeof action.tool_args === "string" ? JSON.parse(action.tool_args) : action.tool_args || {};
|
|
1763
|
+
} catch (e) {
|
|
1764
|
+
tui.log.warning("Failed to parse tool_args, using empty object");
|
|
1765
|
+
}
|
|
1766
|
+
const mcpResult = await mcpClient.callTool(action.tool_name, args);
|
|
1767
|
+
result = JSON.stringify(mcpResult);
|
|
1768
|
+
tui.log.success(`Result: ${result.substring(0, 100)}...`);
|
|
1769
|
+
}
|
|
1770
|
+
break;
|
|
1771
|
+
case "create_file":
|
|
1772
|
+
if (action.path && action.content) {
|
|
1773
|
+
const fullPath = path7.resolve(process.cwd(), action.path);
|
|
1774
|
+
fs8.writeFileSync(fullPath, action.content);
|
|
1775
|
+
tui.log.success(`File created: ${action.path}`);
|
|
1776
|
+
result = "File created successfully.";
|
|
1777
|
+
}
|
|
1778
|
+
break;
|
|
1779
|
+
case "read_file":
|
|
1780
|
+
result = handleReadFile(action.path || "");
|
|
1781
|
+
break;
|
|
1782
|
+
case "run_command":
|
|
1783
|
+
const confirm = await tui.confirm({ message: `Run command: ${action.command}?` });
|
|
1784
|
+
if (confirm && action.command) {
|
|
1785
|
+
result = await handleRunCommand(action.command);
|
|
1786
|
+
} else {
|
|
1787
|
+
result = "Command execution denied by user.";
|
|
1788
|
+
}
|
|
1789
|
+
break;
|
|
1790
|
+
default:
|
|
1791
|
+
result = `Action ${action.type} not fully implemented in local client.`;
|
|
1792
|
+
}
|
|
1793
|
+
} catch (e) {
|
|
1794
|
+
result = `Error executing ${action.type}: ${e.message}`;
|
|
1795
|
+
tui.log.error(result);
|
|
1796
|
+
}
|
|
1797
|
+
userMessage = `[Action ${action.type} Result]:
|
|
1798
|
+
${result}
|
|
1799
|
+
|
|
1800
|
+
`;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
await mcpClient.close();
|
|
1804
|
+
tui.outro("\u{1F988} Shark QA Session Ended");
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// src/commands/qa.ts
|
|
1808
|
+
var qaCommand = new Command4("qa").description("Start the Shark QA Agent to test web applications").option("--url <url>", "Initial URL to test").option("--scenario <scenario>", "Scenario description or test case to execute").action(async (options) => {
|
|
1809
|
+
try {
|
|
1810
|
+
await runQAAgent({
|
|
1811
|
+
initialUrl: options.url,
|
|
1812
|
+
scenario: options.scenario
|
|
1813
|
+
});
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
console.error("Failed to run QA Agent:", error.message);
|
|
1816
|
+
process.exit(1);
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
// src/bin/shark.ts
|
|
1821
|
+
crashHandler.init();
|
|
1822
|
+
var program = new Command5();
|
|
1823
|
+
program.name("shark").description("Shark CLI: AI-Native Collaborative Development Tool").version("0.0.1");
|
|
1824
|
+
program.addCommand(loginCommand);
|
|
1825
|
+
program.addCommand(initCommand);
|
|
1826
|
+
program.addCommand(scanCommand);
|
|
1827
|
+
program.addCommand(devCommand);
|
|
1828
|
+
program.addCommand(qaCommand);
|
|
1829
|
+
program.command("ba").description("Start Business Analyst Agent interactive session").option("--id <agent_id>", "Override Agent ID").action(async (options) => {
|
|
1830
|
+
try {
|
|
1831
|
+
await interactiveBusinessAnalyst();
|
|
1832
|
+
} catch (error) {
|
|
1833
|
+
console.error("Error:", error.message);
|
|
1834
|
+
process.exit(1);
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
program.command("spec").description("Start Specification Agent interactive session").option("--id <agent_id>", "Override Agent ID").option("--briefing <path>", "Path to briefing file").action(async (options) => {
|
|
1838
|
+
try {
|
|
1839
|
+
await interactiveSpecificationAgent({
|
|
1840
|
+
agentId: options.id,
|
|
1841
|
+
briefingPath: options.briefing
|
|
1842
|
+
});
|
|
1843
|
+
} catch (error) {
|
|
1844
|
+
console.error("Error:", error.message);
|
|
1845
|
+
process.exit(1);
|
|
1846
|
+
}
|
|
1847
|
+
});
|
|
1848
|
+
program.command("config").description("Manage global configuration").action(configCommand.action);
|
|
1849
|
+
process.on("unhandledRejection", (err) => {
|
|
1850
|
+
console.error(colors.error("\u274C Unhandled Error:"), err);
|
|
1851
|
+
process.exit(1);
|
|
1852
|
+
});
|
|
1853
|
+
program.parse(process.argv);
|
|
1854
|
+
//# sourceMappingURL=shark.js.map
|