rafaygen-cli 1.3.2 → 1.3.3
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 +302 -29
- package/package.json +2 -2
- package/src/agent.js +1297 -191
- package/src/auth.js +446 -105
- package/src/executor.js +715 -65
- package/src/state.js +254 -10
- package/src/ui.js +419 -51
package/src/state.js
CHANGED
|
@@ -1,27 +1,271 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
const RGCLI_DIR = path.join(os.homedir(), '.rgcli');
|
|
7
|
+
const SESSIONS_DIR = path.join(RGCLI_DIR, 'sessions');
|
|
8
|
+
|
|
9
|
+
function ensureDir(dir) {
|
|
10
|
+
if (!fs.existsSync(dir)) {
|
|
11
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const sessionState = {
|
|
16
|
+
sandboxMode: 'workspace-write',
|
|
17
|
+
approvalMode: 'suggest',
|
|
5
18
|
attachedFiles: new Set(),
|
|
6
19
|
mcpServers: [],
|
|
7
|
-
reasoningEffort:
|
|
20
|
+
reasoningEffort: 'medium',
|
|
8
21
|
verbose: false,
|
|
9
22
|
compactMode: false,
|
|
10
|
-
cwd: process.cwd()
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
jsonOutput: false,
|
|
25
|
+
colorEnabled: true,
|
|
26
|
+
skipGitCheck: false,
|
|
27
|
+
autoEdit: false,
|
|
28
|
+
searchEnabled: false,
|
|
29
|
+
imageAttached: null,
|
|
30
|
+
activeSkill: null,
|
|
31
|
+
conversationHistory: [],
|
|
32
|
+
sessionId: crypto.randomUUID(),
|
|
11
33
|
};
|
|
12
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Returns a shallow copy of the current session state.
|
|
37
|
+
* The attachedFiles Set is copied into a new Set so external
|
|
38
|
+
* mutations don't leak back into the canonical state.
|
|
39
|
+
*/
|
|
13
40
|
export function getSessionState() {
|
|
14
|
-
return
|
|
41
|
+
return {
|
|
42
|
+
...sessionState,
|
|
43
|
+
attachedFiles: new Set(sessionState.attachedFiles),
|
|
44
|
+
mcpServers: [...sessionState.mcpServers],
|
|
45
|
+
conversationHistory: [...sessionState.conversationHistory],
|
|
46
|
+
};
|
|
15
47
|
}
|
|
16
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Merge partial updates into the session state.
|
|
51
|
+
* Supports both plain objects and Sets / Arrays for the
|
|
52
|
+
* collection fields.
|
|
53
|
+
*/
|
|
17
54
|
export function updateSessionState(newState) {
|
|
18
|
-
|
|
55
|
+
if (!newState || typeof newState !== 'object') {
|
|
56
|
+
return getSessionState();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const [key, value] of Object.entries(newState)) {
|
|
60
|
+
if (!(key in sessionState)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (key === 'attachedFiles') {
|
|
65
|
+
if (value instanceof Set) {
|
|
66
|
+
sessionState.attachedFiles = new Set(value);
|
|
67
|
+
} else if (Array.isArray(value)) {
|
|
68
|
+
sessionState.attachedFiles = new Set(value);
|
|
69
|
+
} else {
|
|
70
|
+
sessionState.attachedFiles = new Set();
|
|
71
|
+
}
|
|
72
|
+
} else if (key === 'mcpServers') {
|
|
73
|
+
sessionState.mcpServers = Array.isArray(value) ? [...value] : [];
|
|
74
|
+
} else if (key === 'conversationHistory') {
|
|
75
|
+
sessionState.conversationHistory = Array.isArray(value) ? [...value] : [];
|
|
76
|
+
} else {
|
|
77
|
+
sessionState[key] = value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return getSessionState();
|
|
19
82
|
}
|
|
20
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Attach a file to the current session context.
|
|
86
|
+
* Resolves the path against the current working directory stored
|
|
87
|
+
* in state so relative paths work as expected.
|
|
88
|
+
*/
|
|
21
89
|
export function attachFileContext(filePath) {
|
|
22
|
-
|
|
90
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const resolved = path.isAbsolute(filePath)
|
|
95
|
+
? filePath
|
|
96
|
+
: path.resolve(sessionState.cwd, filePath);
|
|
97
|
+
|
|
98
|
+
if (!fs.existsSync(resolved)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
sessionState.attachedFiles.add(resolved);
|
|
103
|
+
return true;
|
|
23
104
|
}
|
|
24
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Remove all attached files from the session.
|
|
108
|
+
*/
|
|
25
109
|
export function clearAttachedFiles() {
|
|
26
|
-
|
|
110
|
+
sessionState.attachedFiles.clear();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Append a message to the conversation history.
|
|
115
|
+
* @param {'user'|'assistant'|'system'} role
|
|
116
|
+
* @param {string} content
|
|
117
|
+
*/
|
|
118
|
+
export function addToHistory(role, content) {
|
|
119
|
+
const validRoles = ['user', 'assistant', 'system'];
|
|
120
|
+
const normalizedRole = validRoles.includes(role) ? role : 'user';
|
|
121
|
+
|
|
122
|
+
sessionState.conversationHistory.push({
|
|
123
|
+
role: normalizedRole,
|
|
124
|
+
content: typeof content === 'string' ? content : String(content),
|
|
125
|
+
timestamp: new Date().toISOString(),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Return the full conversation history array (copy).
|
|
131
|
+
*/
|
|
132
|
+
export function getHistory() {
|
|
133
|
+
return [...sessionState.conversationHistory];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Clear the in-memory conversation history.
|
|
138
|
+
*/
|
|
139
|
+
export function clearHistory() {
|
|
140
|
+
sessionState.conversationHistory = [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Persist the current session to disk.
|
|
145
|
+
* File: ~/.rgcli/sessions/<sessionId>.json
|
|
146
|
+
*/
|
|
147
|
+
export function saveSession() {
|
|
148
|
+
ensureDir(SESSIONS_DIR);
|
|
149
|
+
|
|
150
|
+
const filePath = path.join(SESSIONS_DIR, `${sessionState.sessionId}.json`);
|
|
151
|
+
|
|
152
|
+
const serializable = {
|
|
153
|
+
sessionId: sessionState.sessionId,
|
|
154
|
+
sandboxMode: sessionState.sandboxMode,
|
|
155
|
+
approvalMode: sessionState.approvalMode,
|
|
156
|
+
attachedFiles: [...sessionState.attachedFiles],
|
|
157
|
+
mcpServers: [...sessionState.mcpServers],
|
|
158
|
+
reasoningEffort: sessionState.reasoningEffort,
|
|
159
|
+
verbose: sessionState.verbose,
|
|
160
|
+
compactMode: sessionState.compactMode,
|
|
161
|
+
cwd: sessionState.cwd,
|
|
162
|
+
jsonOutput: sessionState.jsonOutput,
|
|
163
|
+
colorEnabled: sessionState.colorEnabled,
|
|
164
|
+
skipGitCheck: sessionState.skipGitCheck,
|
|
165
|
+
autoEdit: sessionState.autoEdit,
|
|
166
|
+
searchEnabled: sessionState.searchEnabled,
|
|
167
|
+
imageAttached: sessionState.imageAttached,
|
|
168
|
+
activeSkill: sessionState.activeSkill,
|
|
169
|
+
conversationHistory: sessionState.conversationHistory,
|
|
170
|
+
savedAt: new Date().toISOString(),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
fs.writeFileSync(filePath, JSON.stringify(serializable, null, 2), 'utf-8');
|
|
174
|
+
return filePath;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Load a previously-saved session from disk and hydrate state.
|
|
179
|
+
* @param {string} id Session UUID
|
|
180
|
+
* @returns {boolean} true if loaded successfully
|
|
181
|
+
*/
|
|
182
|
+
export function loadSession(id) {
|
|
183
|
+
if (!id || typeof id !== 'string') {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const filePath = path.join(SESSIONS_DIR, `${id}.json`);
|
|
188
|
+
|
|
189
|
+
if (!fs.existsSync(filePath)) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
195
|
+
const data = JSON.parse(raw);
|
|
196
|
+
|
|
197
|
+
if (data.sessionId) sessionState.sessionId = data.sessionId;
|
|
198
|
+
if (data.sandboxMode) sessionState.sandboxMode = data.sandboxMode;
|
|
199
|
+
if (data.approvalMode) sessionState.approvalMode = data.approvalMode;
|
|
200
|
+
if (Array.isArray(data.attachedFiles)) {
|
|
201
|
+
sessionState.attachedFiles = new Set(data.attachedFiles);
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(data.mcpServers)) {
|
|
204
|
+
sessionState.mcpServers = [...data.mcpServers];
|
|
205
|
+
}
|
|
206
|
+
if (data.reasoningEffort) sessionState.reasoningEffort = data.reasoningEffort;
|
|
207
|
+
if (typeof data.verbose === 'boolean') sessionState.verbose = data.verbose;
|
|
208
|
+
if (typeof data.compactMode === 'boolean') sessionState.compactMode = data.compactMode;
|
|
209
|
+
if (data.cwd) sessionState.cwd = data.cwd;
|
|
210
|
+
if (typeof data.jsonOutput === 'boolean') sessionState.jsonOutput = data.jsonOutput;
|
|
211
|
+
if (typeof data.colorEnabled === 'boolean') sessionState.colorEnabled = data.colorEnabled;
|
|
212
|
+
if (typeof data.skipGitCheck === 'boolean') sessionState.skipGitCheck = data.skipGitCheck;
|
|
213
|
+
if (typeof data.autoEdit === 'boolean') sessionState.autoEdit = data.autoEdit;
|
|
214
|
+
if (typeof data.searchEnabled === 'boolean') sessionState.searchEnabled = data.searchEnabled;
|
|
215
|
+
sessionState.imageAttached = data.imageAttached ?? null;
|
|
216
|
+
sessionState.activeSkill = data.activeSkill ?? null;
|
|
217
|
+
if (Array.isArray(data.conversationHistory)) {
|
|
218
|
+
sessionState.conversationHistory = [...data.conversationHistory];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return true;
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* List every saved session with basic metadata.
|
|
229
|
+
* Returns an array of { sessionId, savedAt, messageCount, filePath }.
|
|
230
|
+
*/
|
|
231
|
+
export function listSessions() {
|
|
232
|
+
ensureDir(SESSIONS_DIR);
|
|
233
|
+
|
|
234
|
+
const files = fs.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith('.json'));
|
|
235
|
+
|
|
236
|
+
const sessions = [];
|
|
237
|
+
|
|
238
|
+
for (const file of files) {
|
|
239
|
+
const filePath = path.join(SESSIONS_DIR, file);
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
243
|
+
const data = JSON.parse(raw);
|
|
244
|
+
|
|
245
|
+
sessions.push({
|
|
246
|
+
sessionId: data.sessionId || path.basename(file, '.json'),
|
|
247
|
+
savedAt: data.savedAt || null,
|
|
248
|
+
messageCount: Array.isArray(data.conversationHistory)
|
|
249
|
+
? data.conversationHistory.length
|
|
250
|
+
: 0,
|
|
251
|
+
filePath,
|
|
252
|
+
});
|
|
253
|
+
} catch {
|
|
254
|
+
sessions.push({
|
|
255
|
+
sessionId: path.basename(file, '.json'),
|
|
256
|
+
savedAt: null,
|
|
257
|
+
messageCount: 0,
|
|
258
|
+
filePath,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
sessions.sort((a, b) => {
|
|
264
|
+
if (!a.savedAt && !b.savedAt) return 0;
|
|
265
|
+
if (!a.savedAt) return 1;
|
|
266
|
+
if (!b.savedAt) return -1;
|
|
267
|
+
return new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return sessions;
|
|
27
271
|
}
|