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 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 (e.g. http://localhost:3000/api/cli)")
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.0.3",
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": "^9.4.0"
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 { startAuthLoop } from "./auth.js";
3
- import { renderBox, renderDiffBox, printError, printStep, printSuccess, printAsciiLogo, printRandomWelcome } from "./ui.js";
4
- import inquirer from "inquirer";
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
- export async function askAgent(promptText) {
11
- let token = getToken();
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
- await startAuthLoop();
14
- token = getToken();
80
+ throw new Error("Not logged in. Please run 'rgcli login <token>' first.");
15
81
  }
16
82
 
17
- printStep("Connecting to RafayGen...");
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
- "Content-Type": "application/json",
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
- throw new Error(`API Error: ${res.statusText}`);
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
- renderBox(" RafayGen ", data.message, "cyan");
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 handleAction(action);
132
+ await executeAction(action);
42
133
  }
43
- } else {
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
- let token = getToken();
54
- if (!token) {
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
- printRandomWelcome();
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 { prompt } = await inquirer.prompt([
150
+ const { promptText } = await inquirer.prompt([
66
151
  {
67
152
  type: "input",
68
- name: "prompt",
69
- message: chalk.magenta.bold("rgcli> "),
153
+ name: "promptText",
154
+ message: "rgcli>",
70
155
  prefix: ""
71
156
  }
72
157
  ]);
73
158
 
74
- const input = prompt.trim();
75
-
159
+ const input = promptText.trim();
76
160
  if (!input) continue;
77
161
 
78
- if (input === "/exit" || input === "/quit") {
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
- if (input === "/help") {
89
- console.log(chalk.cyan("\\nAvailable Slash Commands:"));
90
- console.log(chalk.white(" /clear") + chalk.gray(" - Clear the terminal window"));
91
- console.log(chalk.white(" /exit ") + chalk.gray(" - Exit the interactive shell"));
92
- console.log(chalk.white(" /help ") + chalk.gray(" - Show this message\\n"));
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
- await askAgent(input);
97
- }
98
- }
252
+ // Extract attached files via drag-and-drop
253
+ let { cleanPrompt, extractedContext } = extractFileContext(input);
99
254
 
100
- async function handleAction(action) {
101
- if (action.type === "write") {
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
- // Show diff
109
- renderDiffBox(action.file, original, action.content);
110
-
111
- // Ask user to confirm
112
- const { confirm } = await inquirer.prompt([
113
- {
114
- type: "confirm",
115
- name: "confirm",
116
- message: `Allow RafayGen to write to ${action.file}?`,
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
- if (confirm) {
140
- await new Promise((resolve) => {
141
- const proc = exec(action.command, (err, stdout, stderr) => {
142
- if (stdout) console.log(chalk.gray(stdout));
143
- if (stderr) console.log(chalk.red(stderr));
144
- if (err) printError(`Command failed with code ${err.code}`);
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
- // Default to localhost for local testing, can be overridden by environment variable or config
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 loginUrl = "https://rafaygen.com/settings/tokens";
86
- console.log(chalk.cyan(`\nOpening browser to: ${loginUrl}`));
87
- console.log(chalk.gray("If the browser doesn't open, copy and paste the URL manually."));
88
- try {
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
- const { token } = await inquirer.prompt([
95
- {
96
- type: "password",
97
- name: "token",
98
- message: "Once you create a token, paste it here:",
99
- validate: (input) => input.trim() !== "" ? true : "Token cannot be empty"
100
- }
101
- ]);
102
- setToken(token.trim());
103
- printSuccess("Successfully logged in to RafayGen!");
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
+ }