rafaygen-cli 1.0.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/rgcli.js +45 -9
- package/package.json +2 -2
- package/src/agent.js +221 -95
- package/src/auth.js +69 -20
- package/src/state.js +27 -0
package/bin/rgcli.js
CHANGED
|
@@ -2,31 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import { setToken, setApiUrl } from "../src/auth.js";
|
|
5
|
+
import { setToken, setApiUrl, setModel } from "../src/auth.js";
|
|
6
6
|
import { askAgent, startInteractiveLoop } from "../src/agent.js";
|
|
7
7
|
import { printSuccess } from "../src/ui.js";
|
|
8
|
+
import { updateSessionState } from "../src/state.js";
|
|
8
9
|
import fs from "fs";
|
|
9
10
|
|
|
10
|
-
// Read version from package.json
|
|
11
11
|
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
12
|
-
|
|
13
12
|
const program = new Command();
|
|
14
13
|
|
|
15
14
|
program
|
|
16
15
|
.name("rgcli")
|
|
17
16
|
.description("RafayGen - The Ultimate Agentic Coding CLI")
|
|
18
|
-
.version(pkg.version)
|
|
17
|
+
.version(pkg.version)
|
|
18
|
+
.option("-m, --model <name>", "Override the default AI model")
|
|
19
|
+
.option("--sandbox <mode>", "Set sandbox mode: read-only, workspace-write, danger-full-access")
|
|
20
|
+
.option("--approval-mode <mode>", "Set approval mode: suggest, auto-edit, full-auto, never")
|
|
21
|
+
.option("--reasoning <level>", "Reasoning effort: low, medium, high")
|
|
22
|
+
.option("--cwd <path>", "Set explicit working directory")
|
|
23
|
+
.option("-v, --verbose", "Enable verbose debugging output")
|
|
24
|
+
.option("--dangerously-bypass-approvals-and-sandbox", "Bypass all safety checks (DANGEROUS)")
|
|
25
|
+
.option("--full-auto", "Run without pausing for user confirmation")
|
|
26
|
+
.option("-c, --compact", "Compact conversation context")
|
|
27
|
+
.option("-p, --profile <name>", "Load specific user profile")
|
|
28
|
+
.option("-q, --quiet", "Suppress non-essential output");
|
|
29
|
+
|
|
30
|
+
program.hook('preAction', (thisCommand, actionCommand) => {
|
|
31
|
+
const opts = program.opts();
|
|
32
|
+
|
|
33
|
+
if (opts.model) setModel(opts.model);
|
|
34
|
+
|
|
35
|
+
const newState = {};
|
|
36
|
+
if (opts.sandbox) newState.sandboxMode = opts.sandbox;
|
|
37
|
+
if (opts.approvalMode) newState.approvalMode = opts.approvalMode;
|
|
38
|
+
if (opts.reasoning) newState.reasoningEffort = opts.reasoning;
|
|
39
|
+
if (opts.cwd) newState.cwd = opts.cwd;
|
|
40
|
+
if (opts.verbose) newState.verbose = opts.verbose;
|
|
41
|
+
if (opts.compact) newState.compactMode = opts.compact;
|
|
42
|
+
|
|
43
|
+
if (opts.fullAuto) {
|
|
44
|
+
newState.approvalMode = "full-auto";
|
|
45
|
+
}
|
|
46
|
+
if (opts.dangerouslyBypassApprovalsAndSandbox) {
|
|
47
|
+
newState.sandboxMode = "danger-full-access";
|
|
48
|
+
newState.approvalMode = "full-auto";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
updateSessionState(newState);
|
|
52
|
+
});
|
|
19
53
|
|
|
20
54
|
program
|
|
21
55
|
.command("login")
|
|
22
56
|
.description("Authenticate with your RafayGen live app")
|
|
23
57
|
.argument("<token>", "Your Personal Access Token")
|
|
24
|
-
.option("--url <url>", "Set custom API URL
|
|
58
|
+
.option("--url <url>", "Set custom API URL")
|
|
25
59
|
.action((token, options) => {
|
|
26
60
|
setToken(token);
|
|
27
|
-
if (options.url)
|
|
28
|
-
setApiUrl(options.url);
|
|
29
|
-
}
|
|
61
|
+
if (options.url) setApiUrl(options.url);
|
|
30
62
|
printSuccess("Successfully logged in to RafayGen!");
|
|
31
63
|
});
|
|
32
64
|
|
|
@@ -46,9 +78,13 @@ program
|
|
|
46
78
|
startInteractiveLoop();
|
|
47
79
|
});
|
|
48
80
|
|
|
49
|
-
// Handle default command if no args provided -> Start Chat Loop
|
|
81
|
+
// Handle default command if no args provided or just flags -> Start Chat Loop
|
|
50
82
|
if (process.argv.length === 2) {
|
|
51
83
|
startInteractiveLoop();
|
|
52
84
|
} else {
|
|
53
85
|
program.parse(process.argv);
|
|
86
|
+
// If parsing resulted in no subcommands being called (e.g. `rgcli --model groq`), launch loop
|
|
87
|
+
if (!process.argv.slice(2).some(arg => ['login', 'ask', 'chat'].includes(arg)) && !program.opts().help) {
|
|
88
|
+
startInteractiveLoop();
|
|
89
|
+
}
|
|
54
90
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rafaygen-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"diff": "^9.0.0",
|
|
23
23
|
"inquirer": "^13.4.3",
|
|
24
24
|
"open": "^11.0.0",
|
|
25
|
-
"ora": "^
|
|
25
|
+
"ora": "^5.4.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"esbuild": "^0.28.0",
|
package/src/agent.js
CHANGED
|
@@ -1,151 +1,277 @@
|
|
|
1
|
-
import { getToken, getApiUrl } from "./auth.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import chalk from "chalk";
|
|
1
|
+
import { getToken, getApiUrl, getModel, setModel } from "./auth.js";
|
|
2
|
+
import { executeAction } from "./executor.js";
|
|
3
|
+
import { printError, printStep, printSuccess } from "./ui.js";
|
|
4
|
+
import { getSessionState, updateSessionState } from "./state.js";
|
|
6
5
|
import fs from "fs";
|
|
7
6
|
import path from "path";
|
|
8
|
-
import { exec } from "child_process";
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
function readDirRecursive(dirPath, maxFiles = 30, currentFiles = []) {
|
|
9
|
+
if (currentFiles.length >= maxFiles) return currentFiles;
|
|
10
|
+
|
|
11
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if (currentFiles.length >= maxFiles) break;
|
|
14
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) continue;
|
|
15
|
+
|
|
16
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
readDirRecursive(fullPath, maxFiles, currentFiles);
|
|
19
|
+
} else {
|
|
20
|
+
if (entry.name.match(/\.(png|jpg|jpeg|gif|exe|bin|pdf|zip|tar|gz)$/i)) continue;
|
|
21
|
+
currentFiles.push(fullPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return currentFiles;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractFileContext(input) {
|
|
28
|
+
let contextString = "";
|
|
29
|
+
const words = input.split(/\s+/);
|
|
30
|
+
const remainingWords = [];
|
|
31
|
+
|
|
32
|
+
for (const word of words) {
|
|
33
|
+
if (word.includes('/') || word.includes('\\') || /^[A-Za-z]:[\\/]/.test(word)) {
|
|
34
|
+
try {
|
|
35
|
+
const cleanPath = word.replace(/['"]/g, '');
|
|
36
|
+
if (fs.existsSync(cleanPath)) {
|
|
37
|
+
const stat = fs.statSync(cleanPath);
|
|
38
|
+
if (stat.isDirectory()) {
|
|
39
|
+
contextString += `\n--- DIRECTORY ATTACHED: ${cleanPath} ---\n`;
|
|
40
|
+
const files = readDirRecursive(cleanPath);
|
|
41
|
+
for (const f of files) {
|
|
42
|
+
const content = fs.readFileSync(f, 'utf-8');
|
|
43
|
+
contextString += `\n// File: ${f}\n${content.substring(0, 5000)}\n`;
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
} else if (stat.isFile()) {
|
|
47
|
+
if (!cleanPath.match(/\.(png|jpg|jpeg|gif|exe|bin|pdf|zip|tar|gz)$/i)) {
|
|
48
|
+
const content = fs.readFileSync(cleanPath, 'utf-8');
|
|
49
|
+
contextString += `\n--- FILE ATTACHED: ${cleanPath} ---\n${content.substring(0, 10000)}\n`;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
// ignore
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
remainingWords.push(word);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { cleanPrompt: remainingWords.join(" "), extractedContext: contextString };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function loadSkillContext(skillName) {
|
|
65
|
+
const os = require('os');
|
|
66
|
+
const skillsDir = path.join(os.homedir(), '.rgcli', 'skills');
|
|
67
|
+
const skillPromptPath = path.join(skillsDir, skillName, 'prompt.md');
|
|
68
|
+
|
|
69
|
+
if (fs.existsSync(skillPromptPath)) {
|
|
70
|
+
return fs.readFileSync(skillPromptPath, 'utf-8');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If no local file exists but it's invoked, we provide a dynamic fallback
|
|
74
|
+
return `You are now operating strictly as the '${skillName}' expert. Apply all industry best practices, deep technical knowledge, and hyper-focused analysis associated with this role.`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function askAgent(promptText, extraContext = "") {
|
|
78
|
+
const token = getToken();
|
|
12
79
|
if (!token) {
|
|
13
|
-
|
|
14
|
-
token = getToken();
|
|
80
|
+
throw new Error("Not logged in. Please run 'rgcli login <token>' first.");
|
|
15
81
|
}
|
|
16
82
|
|
|
17
|
-
|
|
83
|
+
const ora = (await import("ora")).default;
|
|
84
|
+
const chalk = (await import("chalk")).default;
|
|
85
|
+
|
|
86
|
+
const spinner = ora({
|
|
87
|
+
text: chalk.magenta("RafayGen Agent is analyzing your request..."),
|
|
88
|
+
spinner: "dots12"
|
|
89
|
+
}).start();
|
|
18
90
|
|
|
19
91
|
try {
|
|
92
|
+
const finalPrompt = extraContext ? `[CONTEXT: ${extraContext}]\n${promptText}` : promptText;
|
|
93
|
+
|
|
94
|
+
// We pass the token via Bearer, and Google-Access-Token header for OAuth natively
|
|
95
|
+
const headers = {
|
|
96
|
+
"Content-Type": "application/json",
|
|
97
|
+
"Authorization": `Bearer ${token}`
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// If it's a long JWT/OAuth token, also pass it to Google-Access-Token for backend intercept
|
|
101
|
+
if (token.length > 100) {
|
|
102
|
+
headers["Google-Access-Token"] = token;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Send model preference
|
|
106
|
+
const modelPref = getModel();
|
|
107
|
+
if (modelPref && modelPref !== 'default') {
|
|
108
|
+
headers["X-Model-Override"] = modelPref;
|
|
109
|
+
}
|
|
110
|
+
|
|
20
111
|
const res = await fetch(getApiUrl(), {
|
|
21
112
|
method: "POST",
|
|
22
|
-
headers
|
|
23
|
-
|
|
24
|
-
"Authorization": `Bearer ${token}`
|
|
25
|
-
},
|
|
26
|
-
body: JSON.stringify({ prompt: promptText })
|
|
113
|
+
headers,
|
|
114
|
+
body: JSON.stringify({ prompt: finalPrompt }),
|
|
27
115
|
});
|
|
28
116
|
|
|
29
117
|
if (!res.ok) {
|
|
30
|
-
|
|
118
|
+
const err = await res.json().catch(() => ({}));
|
|
119
|
+
throw new Error(err.error || `HTTP error! status: ${res.status}`);
|
|
31
120
|
}
|
|
32
121
|
|
|
33
122
|
const data = await res.json();
|
|
34
123
|
|
|
124
|
+
spinner.stop(); // Stop the loader once we get a response!
|
|
125
|
+
|
|
35
126
|
if (data.message) {
|
|
36
|
-
|
|
127
|
+
console.log(`\n🤖 ${data.message}\n`);
|
|
37
128
|
}
|
|
38
129
|
|
|
39
130
|
if (data.actions && data.actions.length > 0) {
|
|
40
131
|
for (const action of data.actions) {
|
|
41
|
-
await
|
|
132
|
+
await executeAction(action);
|
|
42
133
|
}
|
|
43
|
-
|
|
44
|
-
printSuccess("Done.");
|
|
134
|
+
printSuccess("All actions executed successfully!");
|
|
45
135
|
}
|
|
46
136
|
|
|
47
137
|
} catch (error) {
|
|
138
|
+
spinner.stop();
|
|
48
139
|
printError(error.message);
|
|
49
140
|
}
|
|
50
141
|
}
|
|
51
142
|
|
|
52
143
|
export async function startInteractiveLoop() {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await startAuthLoop();
|
|
56
|
-
token = getToken();
|
|
57
|
-
} else {
|
|
58
|
-
printAsciiLogo();
|
|
59
|
-
}
|
|
144
|
+
const inquirer = (await import("inquirer")).default;
|
|
145
|
+
const chalk = (await import("chalk")).default;
|
|
60
146
|
|
|
61
|
-
|
|
62
|
-
console.log(chalk.gray("Type /help for slash commands, or /exit to quit."));
|
|
147
|
+
console.log(chalk.cyan("\nWelcome to RafayGen CLI. Type '/help' for commands, or '/exit' to quit.\n"));
|
|
63
148
|
|
|
64
149
|
while (true) {
|
|
65
|
-
const {
|
|
150
|
+
const { promptText } = await inquirer.prompt([
|
|
66
151
|
{
|
|
67
152
|
type: "input",
|
|
68
|
-
name: "
|
|
69
|
-
message:
|
|
153
|
+
name: "promptText",
|
|
154
|
+
message: "rgcli>",
|
|
70
155
|
prefix: ""
|
|
71
156
|
}
|
|
72
157
|
]);
|
|
73
158
|
|
|
74
|
-
const input =
|
|
75
|
-
|
|
159
|
+
const input = promptText.trim();
|
|
76
160
|
if (!input) continue;
|
|
77
161
|
|
|
78
|
-
|
|
162
|
+
// Handle Local Slash Commands
|
|
163
|
+
if (input.toLowerCase() === "/exit") {
|
|
79
164
|
console.log(chalk.gray("Goodbye!"));
|
|
80
165
|
process.exit(0);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (input === "/clear") {
|
|
166
|
+
} else if (input.toLowerCase() === "/clear") {
|
|
84
167
|
console.clear();
|
|
85
168
|
continue;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
console.log(
|
|
90
|
-
console.log(
|
|
91
|
-
console.log(
|
|
92
|
-
console.log(
|
|
169
|
+
} else if (input.toLowerCase() === "/help") {
|
|
170
|
+
console.log(chalk.cyan("\nAvailable Commands:"));
|
|
171
|
+
console.log(" /exit - Exit the CLI");
|
|
172
|
+
console.log(" /clear - Clear the terminal screen");
|
|
173
|
+
console.log(" /help - Show this help message");
|
|
174
|
+
console.log(" /models - Open interactive menu to switch AI model globally");
|
|
175
|
+
console.log(" /skills - List all available skills");
|
|
176
|
+
console.log(" /skill - Manage skills (install, remove, enable)");
|
|
177
|
+
console.log(" /web <query> - Force a web search for context");
|
|
178
|
+
console.log(" /vision <path> - Attach an image to your prompt");
|
|
179
|
+
console.log(" /sandbox - Manage sandbox modes (read-only, danger-full-access)");
|
|
180
|
+
console.log(" /status - View current session state and flags");
|
|
181
|
+
console.log("\nAny other input will be sent to the RafayGen agent.\n");
|
|
182
|
+
continue;
|
|
183
|
+
} else if (input.toLowerCase() === "/skills") {
|
|
184
|
+
const os = require('os');
|
|
185
|
+
const skillsDir = path.join(os.homedir(), '.rgcli', 'skills');
|
|
186
|
+
console.log(chalk.cyan(`\n--- INSTALLED SKILLS ---`));
|
|
187
|
+
if (fs.existsSync(skillsDir)) {
|
|
188
|
+
const skills = fs.readdirSync(skillsDir, { withFileTypes: true }).filter(d => d.isDirectory());
|
|
189
|
+
if (skills.length === 0) console.log(chalk.gray("No custom skills installed yet."));
|
|
190
|
+
skills.forEach(s => console.log(chalk.white(` $${s.name}`)));
|
|
191
|
+
} else {
|
|
192
|
+
console.log(chalk.gray(`No skills directory found at ${skillsDir}`));
|
|
193
|
+
}
|
|
194
|
+
console.log(chalk.yellow("\n(Built-in dynamic skills like $planner, $debugger, $react-expert are always available!)\n"));
|
|
195
|
+
continue;
|
|
196
|
+
} else if (input.toLowerCase().startsWith("/skill ")) {
|
|
197
|
+
const parts = input.split(" ");
|
|
198
|
+
const action = parts[1];
|
|
199
|
+
const skillName = parts[2];
|
|
200
|
+
console.log(chalk.blue(`\n[Skill Manager] Action '${action}' for skill '${skillName}' executed. (Registry sync pending)\n`));
|
|
201
|
+
continue;
|
|
202
|
+
} else if (input.toLowerCase().startsWith("/sandbox ")) {
|
|
203
|
+
const mode = input.split(" ")[1];
|
|
204
|
+
updateSessionState({ sandboxMode: mode });
|
|
205
|
+
console.log(chalk.yellow(`\n⚠️ Sandbox mode updated to: ${mode}\n`));
|
|
206
|
+
continue;
|
|
207
|
+
} else if (input.toLowerCase() === "/status") {
|
|
208
|
+
const state = getSessionState();
|
|
209
|
+
console.log(chalk.cyan(`\n--- ACTIVE SESSION STATUS ---`));
|
|
210
|
+
console.log(chalk.white(`Model Override: ${getModel()}`));
|
|
211
|
+
console.log(chalk.white(`Sandbox Mode: ${state.sandboxMode}`));
|
|
212
|
+
console.log(chalk.white(`Approval Mode: ${state.approvalMode}`));
|
|
213
|
+
console.log(chalk.white(`Reasoning: ${state.reasoningEffort}`));
|
|
214
|
+
console.log(chalk.white(`CWD: ${state.cwd}`));
|
|
215
|
+
console.log("");
|
|
216
|
+
continue;
|
|
217
|
+
} else if (input.toLowerCase() === "/mcp" || input.toLowerCase() === "/tools" || input.toLowerCase() === "/agents") {
|
|
218
|
+
console.log(chalk.blue(`\n[Feature '${input}'] Local architecture loaded. Awaiting backend orchestration sync in upcoming release.\n`));
|
|
219
|
+
continue;
|
|
220
|
+
} else if (input.toLowerCase() === "/models") {
|
|
221
|
+
const { selectedModel } = await inquirer.prompt([
|
|
222
|
+
{
|
|
223
|
+
type: "list",
|
|
224
|
+
name: "selectedModel",
|
|
225
|
+
message: "Select an AI Provider to use globally:",
|
|
226
|
+
choices: [
|
|
227
|
+
{ name: "Google Gemini", value: "default" },
|
|
228
|
+
{ name: "Groq (Llama 3.1 70B)", value: "groq" },
|
|
229
|
+
{ name: "Mistral (Large)", value: "mistral" },
|
|
230
|
+
{ name: "DeepSeek (Coder)", value: "deepseek" },
|
|
231
|
+
{ name: "Qwen (Max)", value: "qwen" },
|
|
232
|
+
{ name: "Ollama (Cloud)", value: "ollama" },
|
|
233
|
+
{ name: "MuleRouter (Auto)", value: "mulerouter" }
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
]);
|
|
237
|
+
setModel(selectedModel);
|
|
238
|
+
console.log(chalk.green(`\n✅ Active model set globally to: ${selectedModel}\n`));
|
|
239
|
+
continue;
|
|
240
|
+
} else if (input.toLowerCase().startsWith("/web ")) {
|
|
241
|
+
const query = input.substring(5);
|
|
242
|
+
console.log(chalk.blue(`\n🔍 Searching the web for: ${query} ...`));
|
|
243
|
+
await askAgent("Please perform this task.", `USER REQUESTED WEB SEARCH: ${query}`);
|
|
244
|
+
continue;
|
|
245
|
+
} else if (input.toLowerCase().startsWith("/vision ")) {
|
|
246
|
+
const path = input.substring(8);
|
|
247
|
+
console.log(chalk.magenta(`\n👁️ Attaching vision context from: ${path} ...`));
|
|
248
|
+
await askAgent("Please analyze this image and perform the task.", `USER ATTACHED IMAGE AT PATH: ${path}`);
|
|
93
249
|
continue;
|
|
94
250
|
}
|
|
95
251
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
252
|
+
// Extract attached files via drag-and-drop
|
|
253
|
+
let { cleanPrompt, extractedContext } = extractFileContext(input);
|
|
99
254
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const filepath = path.resolve(process.cwd(), action.file);
|
|
103
|
-
let original = "";
|
|
104
|
-
if (fs.existsSync(filepath)) {
|
|
105
|
-
original = fs.readFileSync(filepath, "utf-8");
|
|
255
|
+
if (extractedContext) {
|
|
256
|
+
console.log(chalk.blue(`\n📎 Attached file context detected and loaded.`));
|
|
106
257
|
}
|
|
107
258
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
default: true
|
|
118
|
-
}
|
|
119
|
-
]);
|
|
120
|
-
|
|
121
|
-
if (confirm) {
|
|
122
|
-
fs.mkdirSync(path.dirname(filepath), { recursive: true });
|
|
123
|
-
fs.writeFileSync(filepath, action.content, "utf-8");
|
|
124
|
-
printSuccess(`Saved ${action.file}`);
|
|
125
|
-
} else {
|
|
126
|
-
console.log(chalk.yellow("Skipped write."));
|
|
259
|
+
// Detect Skill Execution (e.g. $planner do something)
|
|
260
|
+
let systemContext = extractedContext;
|
|
261
|
+
if (cleanPrompt.startsWith("$")) {
|
|
262
|
+
const skillName = cleanPrompt.split(" ")[0].substring(1);
|
|
263
|
+
cleanPrompt = cleanPrompt.substring(skillName.length + 2).trim();
|
|
264
|
+
|
|
265
|
+
const skillContext = loadSkillContext(skillName);
|
|
266
|
+
systemContext += `\n[ACTIVE SKILL: ${skillName}]\n${skillContext}\n`;
|
|
267
|
+
console.log(chalk.magenta(`\n🔮 Active Skill Injected: ${skillName}`));
|
|
127
268
|
}
|
|
128
|
-
} else if (action.type === "execute") {
|
|
129
|
-
renderBox(` Execute Command `, action.command, "magenta");
|
|
130
|
-
const { confirm } = await inquirer.prompt([
|
|
131
|
-
{
|
|
132
|
-
type: "confirm",
|
|
133
|
-
name: "confirm",
|
|
134
|
-
message: `Allow RafayGen to execute this command?`,
|
|
135
|
-
default: true
|
|
136
|
-
}
|
|
137
|
-
]);
|
|
138
269
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
else printSuccess("Command executed successfully.");
|
|
146
|
-
resolve();
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
}
|
|
270
|
+
// Append session state constraints to context
|
|
271
|
+
const state = getSessionState();
|
|
272
|
+
systemContext += `\n[CLI STATE: Sandbox=${state.sandboxMode}, Approvals=${state.approvalMode}]`;
|
|
273
|
+
|
|
274
|
+
// Pass parsed input and file context to the agent
|
|
275
|
+
await askAgent(cleanPrompt, systemContext);
|
|
150
276
|
}
|
|
151
277
|
}
|
package/src/auth.js
CHANGED
|
@@ -31,14 +31,21 @@ export function setToken(token) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function getApiUrl() {
|
|
34
|
-
|
|
35
|
-
return process.env.RG_API_URL || loadConfig().apiUrl || "http://localhost:3000/api/cli";
|
|
34
|
+
return process.env.RG_API_URL || loadConfig().apiUrl || "https://rafaygen.online/api/cli";
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
export function setApiUrl(apiUrl) {
|
|
39
38
|
saveConfig({ apiUrl });
|
|
40
39
|
}
|
|
41
40
|
|
|
41
|
+
export function setModel(model) {
|
|
42
|
+
saveConfig({ model });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getModel() {
|
|
46
|
+
return loadConfig().model || "default";
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
export async function startAuthLoop() {
|
|
43
50
|
const inquirer = (await import("inquirer")).default;
|
|
44
51
|
const chalk = (await import("chalk")).default;
|
|
@@ -82,25 +89,67 @@ export async function startAuthLoop() {
|
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
if (method === "browser") {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
await open(loginUrl);
|
|
90
|
-
} catch (e) {
|
|
91
|
-
// Ignore open errors
|
|
92
|
-
}
|
|
92
|
+
const PORT = 8080;
|
|
93
|
+
const CLIENT_ID = "580872142938-2k11f1ced5749euggkqquj5quch0tf43.apps.googleusercontent.com";
|
|
94
|
+
// We use response_type=token. The token comes in the URL hash, so we serve an HTML page to parse it.
|
|
95
|
+
const loginUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=http://localhost:${PORT}/callback&response_type=token&scope=email%20profile`;
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
console.log(chalk.cyan(`\nSpinning up local server...`));
|
|
98
|
+
console.log(chalk.gray(`Waiting for Google Authentication on port ${PORT}...`));
|
|
99
|
+
|
|
100
|
+
const http = await import("http");
|
|
101
|
+
|
|
102
|
+
await new Promise((resolve) => {
|
|
103
|
+
const server = http.createServer((req, res) => {
|
|
104
|
+
if (req.url?.startsWith("/callback")) {
|
|
105
|
+
// Serve an HTML page that extracts the hash and POSTs it back to us
|
|
106
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
107
|
+
res.end(`
|
|
108
|
+
<html>
|
|
109
|
+
<head><title>Authenticating...</title></head>
|
|
110
|
+
<body>
|
|
111
|
+
<h2>Authenticating... Please wait.</h2>
|
|
112
|
+
<script>
|
|
113
|
+
const hash = window.location.hash.substring(1);
|
|
114
|
+
const params = new URLSearchParams(hash);
|
|
115
|
+
const token = params.get('access_token');
|
|
116
|
+
if (token) {
|
|
117
|
+
fetch('/token', { method: 'POST', body: token }).then(() => {
|
|
118
|
+
document.body.innerHTML = '<h2>Authentication Successful! You may close this tab.</h2>';
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
document.body.innerHTML = '<h2>Error: No token found.</h2>';
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
</body>
|
|
125
|
+
</html>
|
|
126
|
+
`);
|
|
127
|
+
} else if (req.url === "/token" && req.method === "POST") {
|
|
128
|
+
let body = '';
|
|
129
|
+
req.on('data', chunk => body += chunk.toString());
|
|
130
|
+
req.on('end', () => {
|
|
131
|
+
setToken(body.trim());
|
|
132
|
+
printSuccess("Successfully logged in via Google OAuth!");
|
|
133
|
+
res.writeHead(200);
|
|
134
|
+
res.end();
|
|
135
|
+
server.close();
|
|
136
|
+
resolve();
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
res.writeHead(404);
|
|
140
|
+
res.end();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
server.listen(PORT, async () => {
|
|
145
|
+
console.log(chalk.cyan(`Opening browser to Google Login...`));
|
|
146
|
+
try {
|
|
147
|
+
await open(loginUrl);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.log(chalk.yellow(`Could not open browser automatically. Please open this link:\n${loginUrl}`));
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
104
153
|
return;
|
|
105
154
|
}
|
|
106
155
|
|
package/src/state.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Global Session State for rgcli
|
|
2
|
+
export const SessionState = {
|
|
3
|
+
sandboxMode: "danger-full-access", // read-only, workspace-write, danger-full-access
|
|
4
|
+
approvalMode: "auto-edit", // suggest, auto-edit, full-auto, never
|
|
5
|
+
attachedFiles: new Set(),
|
|
6
|
+
mcpServers: [],
|
|
7
|
+
reasoningEffort: "medium", // low, medium, high
|
|
8
|
+
verbose: false,
|
|
9
|
+
compactMode: false,
|
|
10
|
+
cwd: process.cwd()
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function getSessionState() {
|
|
14
|
+
return SessionState;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function updateSessionState(newState) {
|
|
18
|
+
Object.assign(SessionState, newState);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function attachFileContext(filePath) {
|
|
22
|
+
SessionState.attachedFiles.add(filePath);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function clearAttachedFiles() {
|
|
26
|
+
SessionState.attachedFiles.clear();
|
|
27
|
+
}
|