swiftroutercli 3.0.1 ā 4.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/dist/api/client.js +37 -1
- package/dist/index.js +24 -5
- package/dist/ui/Chat.js +63 -4
- package/package.json +1 -1
- package/src/api/client.ts +44 -1
- package/src/index.ts +25 -5
- package/src/ui/Chat.tsx +74 -4
package/dist/api/client.js
CHANGED
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
import { createParser } from "eventsource-parser";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
function getHierarchicalAgentsContext() {
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
const pathsToRead = [];
|
|
8
|
+
// 1. Global
|
|
9
|
+
pathsToRead.push(path.join(os.homedir(), ".swiftrouter-cli", "AGENTS.md"));
|
|
10
|
+
// 2. Repo Root
|
|
11
|
+
let currentDir = cwd;
|
|
12
|
+
let repoRoot = null;
|
|
13
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
14
|
+
if (fs.existsSync(path.join(currentDir, ".git"))) {
|
|
15
|
+
repoRoot = currentDir;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
currentDir = path.dirname(currentDir);
|
|
19
|
+
}
|
|
20
|
+
if (repoRoot) {
|
|
21
|
+
pathsToRead.push(path.join(repoRoot, "AGENTS.md"));
|
|
22
|
+
}
|
|
23
|
+
// 3. Local CWD
|
|
24
|
+
pathsToRead.push(path.join(cwd, "AGENTS.md"));
|
|
25
|
+
// Deduplicate
|
|
26
|
+
const uniquePaths = [...new Set(pathsToRead)];
|
|
27
|
+
let agentsContent = "";
|
|
28
|
+
for (const p of uniquePaths) {
|
|
29
|
+
if (fs.existsSync(p)) {
|
|
30
|
+
try {
|
|
31
|
+
agentsContent += `\n--- Context from ${p} ---\n` + fs.readFileSync(p, "utf-8") + "\n";
|
|
32
|
+
}
|
|
33
|
+
catch (e) { }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return agentsContent;
|
|
37
|
+
}
|
|
4
38
|
function getWorkspaceContext() {
|
|
5
39
|
const cwd = process.cwd();
|
|
6
40
|
let context = `Current Workspace: ${cwd}\n`;
|
|
7
|
-
const filesToRead = ["README.md", "
|
|
41
|
+
const filesToRead = ["README.md", "package.json"];
|
|
8
42
|
for (const file of filesToRead) {
|
|
9
43
|
const filePath = path.join(cwd, file);
|
|
10
44
|
if (fs.existsSync(filePath)) {
|
|
@@ -15,6 +49,8 @@ function getWorkspaceContext() {
|
|
|
15
49
|
catch (e) { }
|
|
16
50
|
}
|
|
17
51
|
}
|
|
52
|
+
// Append Hierarchical AGENTS.md
|
|
53
|
+
context += getHierarchicalAgentsContext();
|
|
18
54
|
return context;
|
|
19
55
|
}
|
|
20
56
|
export async function fetchModels(config) {
|
package/dist/index.js
CHANGED
|
@@ -19,16 +19,23 @@ async function ensureConfig() {
|
|
|
19
19
|
if (existingConfig && existingConfig.apiKey && existingConfig.baseUrl) {
|
|
20
20
|
return existingConfig;
|
|
21
21
|
}
|
|
22
|
+
let logoRendered = false;
|
|
22
23
|
try {
|
|
23
24
|
const logoPath = path.join(__dirname, "..", "assets", "logo.png");
|
|
24
25
|
if (fs.existsSync(logoPath)) {
|
|
25
|
-
|
|
26
|
+
const smallLogo = await terminalImage.file(logoPath, { height: 3 });
|
|
27
|
+
// Print logo inline with welcome text
|
|
28
|
+
process.stdout.write(smallLogo.trimEnd() + " ");
|
|
29
|
+
console.log(chalk.cyan.bold("Welcome to SwiftRouterCLI!"));
|
|
30
|
+
logoRendered = true;
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
33
|
catch (e) {
|
|
29
34
|
// Ignore if logo cannot be rendered
|
|
30
35
|
}
|
|
31
|
-
|
|
36
|
+
if (!logoRendered) {
|
|
37
|
+
console.log(chalk.cyan.bold("\nš Welcome to SwiftRouterCLI!"));
|
|
38
|
+
}
|
|
32
39
|
console.log(chalk.gray("It looks like this is your first time. Let's get you set up.\n"));
|
|
33
40
|
const rl = readline.createInterface({
|
|
34
41
|
input: process.stdin,
|
|
@@ -48,7 +55,7 @@ async function ensureConfig() {
|
|
|
48
55
|
program
|
|
49
56
|
.name("swiftroutercli")
|
|
50
57
|
.description("CLI for SwiftRouter AI Gateway")
|
|
51
|
-
.version("
|
|
58
|
+
.version("4.0.1");
|
|
52
59
|
program
|
|
53
60
|
.command("config")
|
|
54
61
|
.description("Manually configure the CLI with your SwiftRouter API Key and Base URL")
|
|
@@ -96,13 +103,15 @@ program
|
|
|
96
103
|
.description("Start an interactive chat session")
|
|
97
104
|
.argument("[prompt]", "Initial prompt to start the chat")
|
|
98
105
|
.option("-m, --model <model>", "Model to use", DEFAULT_MODEL)
|
|
106
|
+
.option("-a, --approval-mode <mode>", "AI assistant's permission mode (suggest, auto-edit, full-auto)", "suggest")
|
|
107
|
+
.option("-q, --quiet", "Run in headless CI/CD mode without interactive TUI rendering", false)
|
|
99
108
|
.action(async (prompt, options) => {
|
|
100
109
|
const config = await ensureConfig();
|
|
101
110
|
if (!prompt) {
|
|
102
|
-
startChat(config, options.model, "");
|
|
111
|
+
startChat(config, options.model, "", options.approvalMode, options.quiet);
|
|
103
112
|
}
|
|
104
113
|
else {
|
|
105
|
-
startChat(config, options.model, prompt);
|
|
114
|
+
startChat(config, options.model, prompt, options.approvalMode, options.quiet);
|
|
106
115
|
}
|
|
107
116
|
});
|
|
108
117
|
program
|
|
@@ -140,4 +149,14 @@ program
|
|
|
140
149
|
console.log(chalk.gray("You were not logged in."));
|
|
141
150
|
}
|
|
142
151
|
});
|
|
152
|
+
// Default action: if no subcommand is given, launch chat (just like `codex`)
|
|
153
|
+
program
|
|
154
|
+
.argument("[prompt]", "Initial prompt to start chat directly")
|
|
155
|
+
.option("-m, --model <model>", "Model to use", DEFAULT_MODEL)
|
|
156
|
+
.option("-a, --approval-mode <mode>", "AI assistant's permission mode (suggest, auto-edit, full-auto)", "suggest")
|
|
157
|
+
.option("-q, --quiet", "Run in headless CI/CD mode without interactive TUI rendering", false)
|
|
158
|
+
.action(async (prompt, options) => {
|
|
159
|
+
const config = await ensureConfig();
|
|
160
|
+
startChat(config, options.model, prompt || "", options.approvalMode, options.quiet);
|
|
161
|
+
});
|
|
143
162
|
program.parse();
|
package/dist/ui/Chat.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { render, Box, Text, useInput, Static } from "ink";
|
|
3
3
|
import { streamChatCompletion, fetchModels } from "../api/client.js";
|
|
4
|
+
import chalk from "chalk";
|
|
4
5
|
import { loadHistory, saveHistory } from "../config.js";
|
|
5
6
|
import { marked } from "marked";
|
|
6
7
|
import TerminalRenderer from "marked-terminal";
|
|
@@ -10,7 +11,7 @@ marked.setOptions({
|
|
|
10
11
|
// @ts-ignore
|
|
11
12
|
renderer: new TerminalRenderer()
|
|
12
13
|
});
|
|
13
|
-
const Chat = ({ config, model, initialPrompt }) => {
|
|
14
|
+
const Chat = ({ config, model, initialPrompt, approvalMode }) => {
|
|
14
15
|
const [messages, setMessages] = useState([
|
|
15
16
|
{ id: 0, role: "user", content: initialPrompt },
|
|
16
17
|
]);
|
|
@@ -40,7 +41,27 @@ const Chat = ({ config, model, initialPrompt }) => {
|
|
|
40
41
|
]);
|
|
41
42
|
const bashMatch = streamingContent.match(/```(?:bash|sh)\n([\s\S]*?)\n```/);
|
|
42
43
|
if (bashMatch) {
|
|
43
|
-
|
|
44
|
+
const command = bashMatch[1].trim();
|
|
45
|
+
if (approvalMode === "full-auto") {
|
|
46
|
+
setIsExecuting(true);
|
|
47
|
+
const dockerCmd = `docker run --rm -v "${process.cwd()}":/workspace -w /workspace node:20 bash -c "${command.replace(/"/g, '\\"')}"`;
|
|
48
|
+
setMessages((prev) => [
|
|
49
|
+
...prev,
|
|
50
|
+
{ id: prev.length, role: "system", content: `[Full-Auto Sandboxed] Executing: ${command}` }
|
|
51
|
+
]);
|
|
52
|
+
exec(dockerCmd, (err, stdout, stderr) => {
|
|
53
|
+
const output = err ? stderr : stdout;
|
|
54
|
+
setMessages((prev) => [
|
|
55
|
+
...prev,
|
|
56
|
+
{ id: prev.length, role: "system", content: `Sandbox Output:\n${output || "Done"}` }
|
|
57
|
+
]);
|
|
58
|
+
setIsExecuting(false);
|
|
59
|
+
// Recursive automation could go here in V5
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
setPendingCommand(command);
|
|
64
|
+
}
|
|
44
65
|
}
|
|
45
66
|
setStreamingContent("");
|
|
46
67
|
setIsStreaming(false);
|
|
@@ -182,6 +203,44 @@ const Chat = ({ config, model, initialPrompt }) => {
|
|
|
182
203
|
history.length,
|
|
183
204
|
" Prompts Saved"))))));
|
|
184
205
|
};
|
|
185
|
-
export function startChat(config, model, prompt) {
|
|
186
|
-
|
|
206
|
+
export function startChat(config, model, prompt, approvalMode = "suggest", quiet = false) {
|
|
207
|
+
if (quiet) {
|
|
208
|
+
if (!prompt) {
|
|
209
|
+
console.error(chalk.red("Error: --quiet requires an initial prompt to be passed (e.g. swiftroutercli chat -q \"hello\")"));
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
let fullResponse = "";
|
|
213
|
+
streamChatCompletion(config, model, [{ role: "user", content: prompt }], (text) => {
|
|
214
|
+
process.stdout.write(text);
|
|
215
|
+
fullResponse += text;
|
|
216
|
+
}, () => {
|
|
217
|
+
console.log("\n");
|
|
218
|
+
// If full-auto is requested in quiet mode, we would parse bash blocks and execute them here.
|
|
219
|
+
// For now, headless simply streams the raw text response for CI pipelines.
|
|
220
|
+
if (approvalMode === "full-auto") {
|
|
221
|
+
const bashMatch = fullResponse.match(/```(?:bash|sh)\n([\s\S]*?)\n```/);
|
|
222
|
+
if (bashMatch) {
|
|
223
|
+
const command = bashMatch[1].trim();
|
|
224
|
+
console.log(chalk.yellow(`\n[Full-Auto] Executing headless command: ${command}`));
|
|
225
|
+
exec(command, { cwd: process.cwd() }, (err, stdout, stderr) => {
|
|
226
|
+
if (err) {
|
|
227
|
+
console.error(chalk.red(`Error: ${stderr}`));
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
console.log(stdout);
|
|
232
|
+
process.exit(0);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
process.exit(0);
|
|
239
|
+
}, (err) => {
|
|
240
|
+
console.error(chalk.red(`\nError: ${err.message}`));
|
|
241
|
+
process.exit(1);
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
render(React.createElement(Chat, { config: config, model: model, initialPrompt: prompt, approvalMode: approvalMode }));
|
|
187
246
|
}
|
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -2,11 +2,50 @@ import { Config, loadConfig } from "../config.js";
|
|
|
2
2
|
import { createParser } from "eventsource-parser";
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import path from "path";
|
|
5
|
+
import os from "os";
|
|
6
|
+
|
|
7
|
+
function getHierarchicalAgentsContext(): string {
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const pathsToRead: string[] = [];
|
|
10
|
+
|
|
11
|
+
// 1. Global
|
|
12
|
+
pathsToRead.push(path.join(os.homedir(), ".swiftrouter-cli", "AGENTS.md"));
|
|
13
|
+
|
|
14
|
+
// 2. Repo Root
|
|
15
|
+
let currentDir = cwd;
|
|
16
|
+
let repoRoot: string | null = null;
|
|
17
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
18
|
+
if (fs.existsSync(path.join(currentDir, ".git"))) {
|
|
19
|
+
repoRoot = currentDir;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
currentDir = path.dirname(currentDir);
|
|
23
|
+
}
|
|
24
|
+
if (repoRoot) {
|
|
25
|
+
pathsToRead.push(path.join(repoRoot, "AGENTS.md"));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 3. Local CWD
|
|
29
|
+
pathsToRead.push(path.join(cwd, "AGENTS.md"));
|
|
30
|
+
|
|
31
|
+
// Deduplicate
|
|
32
|
+
const uniquePaths = [...new Set(pathsToRead)];
|
|
33
|
+
|
|
34
|
+
let agentsContent = "";
|
|
35
|
+
for (const p of uniquePaths) {
|
|
36
|
+
if (fs.existsSync(p)) {
|
|
37
|
+
try {
|
|
38
|
+
agentsContent += `\n--- Context from ${p} ---\n` + fs.readFileSync(p, "utf-8") + "\n";
|
|
39
|
+
} catch (e) { }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return agentsContent;
|
|
43
|
+
}
|
|
5
44
|
|
|
6
45
|
function getWorkspaceContext(): string {
|
|
7
46
|
const cwd = process.cwd();
|
|
8
47
|
let context = `Current Workspace: ${cwd}\n`;
|
|
9
|
-
const filesToRead = ["README.md", "
|
|
48
|
+
const filesToRead = ["README.md", "package.json"];
|
|
10
49
|
for (const file of filesToRead) {
|
|
11
50
|
const filePath = path.join(cwd, file);
|
|
12
51
|
if (fs.existsSync(filePath)) {
|
|
@@ -16,6 +55,10 @@ function getWorkspaceContext(): string {
|
|
|
16
55
|
} catch (e) { }
|
|
17
56
|
}
|
|
18
57
|
}
|
|
58
|
+
|
|
59
|
+
// Append Hierarchical AGENTS.md
|
|
60
|
+
context += getHierarchicalAgentsContext();
|
|
61
|
+
|
|
19
62
|
return context;
|
|
20
63
|
}
|
|
21
64
|
|
package/src/index.ts
CHANGED
|
@@ -23,16 +23,23 @@ async function ensureConfig(): Promise<Config> {
|
|
|
23
23
|
return existingConfig;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
let logoRendered = false;
|
|
26
27
|
try {
|
|
27
28
|
const logoPath = path.join(__dirname, "..", "assets", "logo.png");
|
|
28
29
|
if (fs.existsSync(logoPath)) {
|
|
29
|
-
|
|
30
|
+
const smallLogo = await terminalImage.file(logoPath, { height: 3 });
|
|
31
|
+
// Print logo inline with welcome text
|
|
32
|
+
process.stdout.write(smallLogo.trimEnd() + " ");
|
|
33
|
+
console.log(chalk.cyan.bold("Welcome to SwiftRouterCLI!"));
|
|
34
|
+
logoRendered = true;
|
|
30
35
|
}
|
|
31
36
|
} catch (e) {
|
|
32
37
|
// Ignore if logo cannot be rendered
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
if (!logoRendered) {
|
|
41
|
+
console.log(chalk.cyan.bold("\nš Welcome to SwiftRouterCLI!"));
|
|
42
|
+
}
|
|
36
43
|
console.log(chalk.gray("It looks like this is your first time. Let's get you set up.\n"));
|
|
37
44
|
|
|
38
45
|
const rl = readline.createInterface({
|
|
@@ -61,7 +68,7 @@ async function ensureConfig(): Promise<Config> {
|
|
|
61
68
|
program
|
|
62
69
|
.name("swiftroutercli")
|
|
63
70
|
.description("CLI for SwiftRouter AI Gateway")
|
|
64
|
-
.version("
|
|
71
|
+
.version("4.0.1");
|
|
65
72
|
|
|
66
73
|
program
|
|
67
74
|
.command("config")
|
|
@@ -112,13 +119,15 @@ program
|
|
|
112
119
|
.description("Start an interactive chat session")
|
|
113
120
|
.argument("[prompt]", "Initial prompt to start the chat")
|
|
114
121
|
.option("-m, --model <model>", "Model to use", DEFAULT_MODEL)
|
|
122
|
+
.option("-a, --approval-mode <mode>", "AI assistant's permission mode (suggest, auto-edit, full-auto)", "suggest")
|
|
123
|
+
.option("-q, --quiet", "Run in headless CI/CD mode without interactive TUI rendering", false)
|
|
115
124
|
.action(async (prompt, options) => {
|
|
116
125
|
const config = await ensureConfig();
|
|
117
126
|
|
|
118
127
|
if (!prompt) {
|
|
119
|
-
startChat(config, options.model, "");
|
|
128
|
+
startChat(config, options.model, "", options.approvalMode, options.quiet);
|
|
120
129
|
} else {
|
|
121
|
-
startChat(config, options.model, prompt);
|
|
130
|
+
startChat(config, options.model, prompt, options.approvalMode, options.quiet);
|
|
122
131
|
}
|
|
123
132
|
});
|
|
124
133
|
|
|
@@ -159,4 +168,15 @@ program
|
|
|
159
168
|
}
|
|
160
169
|
});
|
|
161
170
|
|
|
171
|
+
// Default action: if no subcommand is given, launch chat (just like `codex`)
|
|
172
|
+
program
|
|
173
|
+
.argument("[prompt]", "Initial prompt to start chat directly")
|
|
174
|
+
.option("-m, --model <model>", "Model to use", DEFAULT_MODEL)
|
|
175
|
+
.option("-a, --approval-mode <mode>", "AI assistant's permission mode (suggest, auto-edit, full-auto)", "suggest")
|
|
176
|
+
.option("-q, --quiet", "Run in headless CI/CD mode without interactive TUI rendering", false)
|
|
177
|
+
.action(async (prompt, options) => {
|
|
178
|
+
const config = await ensureConfig();
|
|
179
|
+
startChat(config, options.model, prompt || "", options.approvalMode, options.quiet);
|
|
180
|
+
});
|
|
181
|
+
|
|
162
182
|
program.parse();
|
package/src/ui/Chat.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { render, Box, Text, useInput, Static } from "ink";
|
|
3
3
|
import { streamChatCompletion, fetchModels } from "../api/client.js";
|
|
4
|
+
import chalk from "chalk";
|
|
4
5
|
import { Config, loadHistory, saveHistory } from "../config.js";
|
|
5
6
|
import { marked } from "marked";
|
|
6
7
|
import TerminalRenderer from "marked-terminal";
|
|
@@ -21,9 +22,10 @@ interface ChatProps {
|
|
|
21
22
|
config: Config;
|
|
22
23
|
model: string;
|
|
23
24
|
initialPrompt: string;
|
|
25
|
+
approvalMode: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt }) => {
|
|
28
|
+
const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt, approvalMode }) => {
|
|
27
29
|
const [messages, setMessages] = useState<{ id: number; role: string; content: string }[]>([
|
|
28
30
|
{ id: 0, role: "user", content: initialPrompt },
|
|
29
31
|
]);
|
|
@@ -63,7 +65,28 @@ const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt }) => {
|
|
|
63
65
|
|
|
64
66
|
const bashMatch = streamingContent.match(/```(?:bash|sh)\n([\s\S]*?)\n```/);
|
|
65
67
|
if (bashMatch) {
|
|
66
|
-
|
|
68
|
+
const command = bashMatch[1].trim();
|
|
69
|
+
if (approvalMode === "full-auto") {
|
|
70
|
+
setIsExecuting(true);
|
|
71
|
+
const dockerCmd = `docker run --rm -v "${process.cwd()}":/workspace -w /workspace node:20 bash -c "${command.replace(/"/g, '\\"')}"`;
|
|
72
|
+
|
|
73
|
+
setMessages((prev) => [
|
|
74
|
+
...prev,
|
|
75
|
+
{ id: prev.length, role: "system", content: `[Full-Auto Sandboxed] Executing: ${command}` }
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
exec(dockerCmd, (err, stdout, stderr) => {
|
|
79
|
+
const output = err ? stderr : stdout;
|
|
80
|
+
setMessages((prev) => [
|
|
81
|
+
...prev,
|
|
82
|
+
{ id: prev.length, role: "system", content: `Sandbox Output:\n${output || "Done"}` }
|
|
83
|
+
]);
|
|
84
|
+
setIsExecuting(false);
|
|
85
|
+
// Recursive automation could go here in V5
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
setPendingCommand(command);
|
|
89
|
+
}
|
|
67
90
|
}
|
|
68
91
|
|
|
69
92
|
setStreamingContent("");
|
|
@@ -251,6 +274,53 @@ const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt }) => {
|
|
|
251
274
|
);
|
|
252
275
|
};
|
|
253
276
|
|
|
254
|
-
export function startChat(config: Config, model: string, prompt: string) {
|
|
255
|
-
|
|
277
|
+
export function startChat(config: Config, model: string, prompt: string, approvalMode: string = "suggest", quiet: boolean = false) {
|
|
278
|
+
if (quiet) {
|
|
279
|
+
if (!prompt) {
|
|
280
|
+
console.error(chalk.red("Error: --quiet requires an initial prompt to be passed (e.g. swiftroutercli chat -q \"hello\")"));
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let fullResponse = "";
|
|
285
|
+
streamChatCompletion(
|
|
286
|
+
config,
|
|
287
|
+
model,
|
|
288
|
+
[{ role: "user", content: prompt }],
|
|
289
|
+
(text) => {
|
|
290
|
+
process.stdout.write(text);
|
|
291
|
+
fullResponse += text;
|
|
292
|
+
},
|
|
293
|
+
() => {
|
|
294
|
+
console.log("\n");
|
|
295
|
+
|
|
296
|
+
// If full-auto is requested in quiet mode, we would parse bash blocks and execute them here.
|
|
297
|
+
// For now, headless simply streams the raw text response for CI pipelines.
|
|
298
|
+
if (approvalMode === "full-auto") {
|
|
299
|
+
const bashMatch = fullResponse.match(/```(?:bash|sh)\n([\s\S]*?)\n```/);
|
|
300
|
+
if (bashMatch) {
|
|
301
|
+
const command = bashMatch[1].trim();
|
|
302
|
+
console.log(chalk.yellow(`\n[Full-Auto] Executing headless command: ${command}`));
|
|
303
|
+
exec(command, { cwd: process.cwd() }, (err, stdout, stderr) => {
|
|
304
|
+
if (err) {
|
|
305
|
+
console.error(chalk.red(`Error: ${stderr}`));
|
|
306
|
+
process.exit(1);
|
|
307
|
+
} else {
|
|
308
|
+
console.log(stdout);
|
|
309
|
+
process.exit(0);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
process.exit(0);
|
|
316
|
+
},
|
|
317
|
+
(err) => {
|
|
318
|
+
console.error(chalk.red(`\nError: ${err.message}`));
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
render(<Chat config={config} model={model} initialPrompt={prompt} approvalMode={approvalMode} />);
|
|
256
326
|
}
|