@gagik.co/snippet-agent 0.1.0
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/.eslintrc.js +13 -0
- package/.prettierrc.json +1 -0
- package/README.md +23 -0
- package/dist/agent-class.d.ts +47 -0
- package/dist/agent-class.js +314 -0
- package/dist/agent.d.ts +1 -0
- package/dist/agent.js +392 -0
- package/dist/banner.d.ts +1 -0
- package/dist/banner.js +23 -0
- package/dist/confirmation-extension.d.ts +10 -0
- package/dist/confirmation-extension.js +213 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +141 -0
- package/dist/mongosh-interactive-mode.d.ts +33 -0
- package/dist/mongosh-interactive-mode.js +244 -0
- package/dist/project-agent.d.ts +1 -0
- package/dist/project-agent.js +36 -0
- package/dist/shell-context.d.ts +17 -0
- package/dist/shell-context.js +75 -0
- package/dist/skills-loader.d.ts +2 -0
- package/dist/skills-loader.js +69 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +8 -0
- package/dist/src/project-agent.d.ts +1 -0
- package/dist/src/project-agent.js +36 -0
- package/dist/stdout-patcher.d.ts +5 -0
- package/dist/stdout-patcher.js +41 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/mongosh-eval.d.ts +7 -0
- package/dist/tools/mongosh-eval.js +84 -0
- package/dist/tools/search-docs.d.ts +2 -0
- package/dist/tools/search-docs.js +106 -0
- package/dist/tools/types.d.ts +12 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools.d.ts +7 -0
- package/dist/tools.js +189 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +2 -0
- package/package.json +38 -0
- package/skills/mongodb-connection.md +208 -0
- package/skills/mongodb-natural-language-querying.md +202 -0
- package/skills/mongodb-query-optimizer.md +265 -0
- package/skills/mongodb-schema-design.md +455 -0
- package/skills/mongodb-search-and-ai.md +357 -0
- package/skills/mongosh-shell.md +227 -0
- package/src/agent-class.ts +393 -0
- package/src/banner.ts +36 -0
- package/src/confirmation-extension.ts +297 -0
- package/src/index.ts +137 -0
- package/src/mongosh-interactive-mode.ts +420 -0
- package/src/shell-context.ts +97 -0
- package/src/skills-loader.ts +37 -0
- package/src/stdout-patcher.ts +48 -0
- package/src/tools/index.ts +4 -0
- package/src/tools/mongosh-eval.ts +115 -0
- package/src/tools/search-docs.ts +115 -0
- package/src/tools/types.ts +15 -0
- package/src/types.ts +23 -0
- package/tsconfig-lint.json +4 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import type { ShellContext } from './shell-context';
|
|
2
|
+
import type { InteractiveModeOptions } from '@earendil-works/pi-coding-agent';
|
|
3
|
+
import type { AgentSessionRuntime } from '@earendil-works/pi-coding-agent';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { truncateToWidth } from '@earendil-works/pi-tui';
|
|
6
|
+
|
|
7
|
+
// Extended interface to access private/protected members
|
|
8
|
+
interface ExtendedInteractiveMode {
|
|
9
|
+
init(): Promise<void>;
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
stop(): void;
|
|
12
|
+
showError(message: string): void;
|
|
13
|
+
showWarning(message: string): void;
|
|
14
|
+
// Editor related
|
|
15
|
+
editor: {
|
|
16
|
+
onSubmit?: (text: string) => Promise<void> | void;
|
|
17
|
+
onChange?: (text: string) => void;
|
|
18
|
+
getText(): string;
|
|
19
|
+
setText(text: string): void;
|
|
20
|
+
addToHistory?(text: string): void;
|
|
21
|
+
borderColor?: (str: string) => string;
|
|
22
|
+
};
|
|
23
|
+
defaultEditor: {
|
|
24
|
+
onSubmit?: (text: string) => Promise<void> | void;
|
|
25
|
+
onChange?: (text: string) => void;
|
|
26
|
+
getText(): string;
|
|
27
|
+
setText(text: string): void;
|
|
28
|
+
addToHistory?(text: string): void;
|
|
29
|
+
borderColor?: (str: string) => string;
|
|
30
|
+
};
|
|
31
|
+
editorContainer: {
|
|
32
|
+
clear(): void;
|
|
33
|
+
addChild(child: unknown): void;
|
|
34
|
+
};
|
|
35
|
+
ui: {
|
|
36
|
+
requestRender(): void;
|
|
37
|
+
setFocus(target: unknown): void;
|
|
38
|
+
};
|
|
39
|
+
// Private methods we'll need to access
|
|
40
|
+
updateEditorBorderColor(): void;
|
|
41
|
+
flushPendingBashComponents(): void;
|
|
42
|
+
onInputCallback?: (text: string) => void;
|
|
43
|
+
chatContainer: {
|
|
44
|
+
addChild(child: unknown): void;
|
|
45
|
+
};
|
|
46
|
+
pendingMessagesContainer: {
|
|
47
|
+
addChild(child: unknown): void;
|
|
48
|
+
};
|
|
49
|
+
session: {
|
|
50
|
+
isStreaming: boolean;
|
|
51
|
+
isCompacting: boolean;
|
|
52
|
+
isBashRunning: boolean;
|
|
53
|
+
prompt(
|
|
54
|
+
text: string,
|
|
55
|
+
options?: { streamingBehavior?: string },
|
|
56
|
+
): Promise<void>;
|
|
57
|
+
recordBashResult?(
|
|
58
|
+
command: string,
|
|
59
|
+
result: { exitCode: number; output: string; cancelled?: boolean },
|
|
60
|
+
options?: { excludeFromContext?: boolean },
|
|
61
|
+
): void;
|
|
62
|
+
};
|
|
63
|
+
sessionManager: {
|
|
64
|
+
getCwd(): string;
|
|
65
|
+
};
|
|
66
|
+
isExtensionCommand?(text: string): boolean;
|
|
67
|
+
queueCompactionMessage?(text: string, behavior: string): void;
|
|
68
|
+
updatePendingMessagesDisplay?(): void;
|
|
69
|
+
// Extension UI methods
|
|
70
|
+
setExtensionWidget?(
|
|
71
|
+
key: string,
|
|
72
|
+
content: string[] | undefined,
|
|
73
|
+
options?: { position?: 'above' | 'below' },
|
|
74
|
+
): void;
|
|
75
|
+
// For mongosh mode tracking
|
|
76
|
+
isMongoshMode?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type MongoshEvalFunction = (expression: string) => Promise<{
|
|
80
|
+
output: string;
|
|
81
|
+
error?: string;
|
|
82
|
+
}>;
|
|
83
|
+
|
|
84
|
+
type MongoshInteractiveModeOptions = InteractiveModeOptions & {
|
|
85
|
+
shellContext: ShellContext;
|
|
86
|
+
mongoshEval: MongoshEvalFunction;
|
|
87
|
+
debugLogging?: boolean;
|
|
88
|
+
// The InteractiveMode class from @earendil-works/pi-coding-agent
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
InteractiveMode: any;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Mongosh execution component for UI rendering
|
|
94
|
+
class MongoshExecutionComponent {
|
|
95
|
+
private command: string;
|
|
96
|
+
private ui: ExtendedInteractiveMode['ui'];
|
|
97
|
+
private excludeFromContext: boolean;
|
|
98
|
+
private output: string[] = [];
|
|
99
|
+
private isComplete = false;
|
|
100
|
+
private exitCode?: number;
|
|
101
|
+
private cancelled = false;
|
|
102
|
+
|
|
103
|
+
constructor(
|
|
104
|
+
command: string,
|
|
105
|
+
ui: ExtendedInteractiveMode['ui'],
|
|
106
|
+
excludeFromContext: boolean,
|
|
107
|
+
) {
|
|
108
|
+
this.command = command;
|
|
109
|
+
this.ui = ui;
|
|
110
|
+
this.excludeFromContext = excludeFromContext;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
appendOutput(chunk: string): void {
|
|
114
|
+
this.output.push(chunk);
|
|
115
|
+
this.ui.requestRender();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setComplete(exitCode?: number, cancelled = false): void {
|
|
119
|
+
this.isComplete = true;
|
|
120
|
+
this.exitCode = exitCode;
|
|
121
|
+
this.cancelled = cancelled;
|
|
122
|
+
this.ui.requestRender();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
render(width: number): string[] {
|
|
126
|
+
const lines: string[] = [];
|
|
127
|
+
const prefix = this.excludeFromContext ? '$$' : '$';
|
|
128
|
+
// Simple syntax highlighting with chalk - just show the command
|
|
129
|
+
// Full highlighting is done via the confirmation extension for tool calls
|
|
130
|
+
const header = `${chalk.gray(prefix)} ${chalk.cyan(this.command)}`;
|
|
131
|
+
lines.push(truncateToWidth(header, width));
|
|
132
|
+
|
|
133
|
+
if (this.output.length > 0) {
|
|
134
|
+
const outputText = this.output.join('');
|
|
135
|
+
const outputLines = outputText.split('\n');
|
|
136
|
+
const prefixStr = chalk.gray('│ ');
|
|
137
|
+
const prefixWidth = 2; // '│ ' is 2 visible characters
|
|
138
|
+
for (const line of outputLines.slice(0, 100)) {
|
|
139
|
+
const availableWidth = Math.max(0, width - prefixWidth);
|
|
140
|
+
lines.push(prefixStr + truncateToWidth(line, availableWidth));
|
|
141
|
+
}
|
|
142
|
+
if (outputLines.length > 100) {
|
|
143
|
+
lines.push(
|
|
144
|
+
chalk.gray(`... ${outputLines.length - 100} more lines`),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (this.isComplete) {
|
|
150
|
+
if (this.cancelled) {
|
|
151
|
+
lines.push(chalk.yellow('◼ cancelled'));
|
|
152
|
+
} else if (this.exitCode !== undefined && this.exitCode !== 0) {
|
|
153
|
+
lines.push(chalk.red(`✗ exit ${this.exitCode}`));
|
|
154
|
+
} else {
|
|
155
|
+
lines.push(chalk.green('✓ done'));
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
lines.push(chalk.cyan('◌ running...'));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return lines;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
invalidate(): void {
|
|
165
|
+
this.ui.requestRender();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Wrapper class that adds mongosh mode to InteractiveMode
|
|
170
|
+
export class MongoshInteractiveMode {
|
|
171
|
+
private baseMode: ExtendedInteractiveMode;
|
|
172
|
+
private shellContext: ShellContext;
|
|
173
|
+
private mongoshEval: MongoshEvalFunction;
|
|
174
|
+
private debugLogging: boolean;
|
|
175
|
+
private mongoshComponent?: MongoshExecutionComponent;
|
|
176
|
+
private pendingMongoshComponents: MongoshExecutionComponent[] = [];
|
|
177
|
+
private originalOnSubmit?: (text: string) => Promise<void> | void;
|
|
178
|
+
private originalBorderColor?: (str: string) => string;
|
|
179
|
+
|
|
180
|
+
constructor(
|
|
181
|
+
runtimeHost: AgentSessionRuntime,
|
|
182
|
+
options: MongoshInteractiveModeOptions,
|
|
183
|
+
) {
|
|
184
|
+
// Store our custom options
|
|
185
|
+
this.shellContext = options.shellContext;
|
|
186
|
+
this.mongoshEval = options.mongoshEval;
|
|
187
|
+
this.debugLogging = options.debugLogging ?? false;
|
|
188
|
+
|
|
189
|
+
// Create the base InteractiveMode using the passed class
|
|
190
|
+
const baseOptions: InteractiveModeOptions = {
|
|
191
|
+
migratedProviders: options.migratedProviders,
|
|
192
|
+
modelFallbackMessage: options.modelFallbackMessage,
|
|
193
|
+
initialMessage: options.initialMessage,
|
|
194
|
+
initialImages: options.initialImages,
|
|
195
|
+
initialMessages: options.initialMessages,
|
|
196
|
+
verbose: options.verbose,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
this.baseMode = new options.InteractiveMode(
|
|
200
|
+
runtimeHost,
|
|
201
|
+
baseOptions,
|
|
202
|
+
) as ExtendedInteractiveMode;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async init(): Promise<void> {
|
|
206
|
+
await this.baseMode.init();
|
|
207
|
+
|
|
208
|
+
// Now that init is done, we can override the onSubmit handler
|
|
209
|
+
this.overrideOnSubmit();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private overrideOnSubmit(): void {
|
|
213
|
+
// Store the original onSubmit from the default editor
|
|
214
|
+
this.originalOnSubmit = this.baseMode.defaultEditor.onSubmit;
|
|
215
|
+
|
|
216
|
+
// Store the original onChange if it exists
|
|
217
|
+
const originalOnChange = this.baseMode.defaultEditor.onChange;
|
|
218
|
+
|
|
219
|
+
if (this.debugLogging) {
|
|
220
|
+
process.stderr.write('[mongosh mode] overrideOnSubmit called\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Create our wrapper onChange that detects $ prefix for visual feedback
|
|
224
|
+
const wrappedOnChange = (text: string): void => {
|
|
225
|
+
// Check if we're in mongosh mode (starts with $)
|
|
226
|
+
const wasMongoshMode = this.baseMode.isMongoshMode;
|
|
227
|
+
const isMongoshMode = text.startsWith('$');
|
|
228
|
+
|
|
229
|
+
// Update the mode flag
|
|
230
|
+
this.baseMode.isMongoshMode = isMongoshMode;
|
|
231
|
+
|
|
232
|
+
// Update indicator if mode changed
|
|
233
|
+
if (wasMongoshMode !== isMongoshMode) {
|
|
234
|
+
this.updateMongoshModeIndicator();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Call original onChange if it exists
|
|
238
|
+
if (originalOnChange) {
|
|
239
|
+
originalOnChange(text);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Create our wrapper onSubmit that checks for $ prefix
|
|
244
|
+
const wrappedOnSubmit = async (text: string): Promise<void> => {
|
|
245
|
+
if (this.debugLogging) {
|
|
246
|
+
process.stderr.write(
|
|
247
|
+
`[mongosh mode] wrappedOnSubmit called with: "${text.substring(0, 50)}..."\n`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Reset mongosh mode flag
|
|
252
|
+
this.baseMode.isMongoshMode = false;
|
|
253
|
+
this.updateMongoshModeIndicator();
|
|
254
|
+
|
|
255
|
+
// Handle mongosh command ($ for normal, $$ for excluded from context)
|
|
256
|
+
if (text.startsWith('$')) {
|
|
257
|
+
if (this.debugLogging) {
|
|
258
|
+
process.stderr.write('[mongosh mode] Detected $ prefix\n');
|
|
259
|
+
}
|
|
260
|
+
const isExcluded = text.startsWith('$$');
|
|
261
|
+
const command = isExcluded
|
|
262
|
+
? text.slice(2).trim()
|
|
263
|
+
: text.slice(1).trim();
|
|
264
|
+
|
|
265
|
+
if (command) {
|
|
266
|
+
// Add to history
|
|
267
|
+
this.baseMode.editor.addToHistory?.(text);
|
|
268
|
+
|
|
269
|
+
// Handle the mongosh command
|
|
270
|
+
await this.handleMongoshCommand(command, isExcluded);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Not a mongosh command - call the original handler
|
|
276
|
+
// We need to call the original onSubmit which handles ! for bash and normal messages
|
|
277
|
+
if (this.originalOnSubmit) {
|
|
278
|
+
await this.originalOnSubmit(text);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Apply the wrapped handlers to both editors
|
|
283
|
+
this.baseMode.defaultEditor.onSubmit = wrappedOnSubmit;
|
|
284
|
+
this.baseMode.editor.onSubmit = wrappedOnSubmit;
|
|
285
|
+
this.baseMode.defaultEditor.onChange = wrappedOnChange;
|
|
286
|
+
this.baseMode.editor.onChange = wrappedOnChange;
|
|
287
|
+
|
|
288
|
+
if (this.debugLogging) {
|
|
289
|
+
process.stderr.write(
|
|
290
|
+
'[mongosh mode] wrappedOnSubmit assigned to editors\n',
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private updateMongoshModeIndicator(): void {
|
|
296
|
+
// Store original border color on first call
|
|
297
|
+
if (!this.originalBorderColor) {
|
|
298
|
+
this.originalBorderColor = this.baseMode.editor.borderColor;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Use green border for mongosh mode (distinct from bash mode which uses yellow/orange)
|
|
302
|
+
// When not in mongosh mode, restore the original color
|
|
303
|
+
this.baseMode.editor.borderColor = this.baseMode.isMongoshMode
|
|
304
|
+
? chalk.green
|
|
305
|
+
: (this.originalBorderColor ?? chalk.gray);
|
|
306
|
+
|
|
307
|
+
// Show/hide the mongosh mode text widget
|
|
308
|
+
if (this.baseMode.setExtensionWidget) {
|
|
309
|
+
if (this.baseMode.isMongoshMode) {
|
|
310
|
+
this.baseMode.setExtensionWidget(
|
|
311
|
+
'mongosh-mode-indicator',
|
|
312
|
+
[chalk.green('mongosh mode')],
|
|
313
|
+
{ position: 'above' },
|
|
314
|
+
);
|
|
315
|
+
} else {
|
|
316
|
+
this.baseMode.setExtensionWidget('mongosh-mode-indicator', undefined);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.baseMode.ui.requestRender();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private async handleMongoshCommand(
|
|
324
|
+
command: string,
|
|
325
|
+
excludeFromContext = false,
|
|
326
|
+
): Promise<void> {
|
|
327
|
+
// Clear the editor
|
|
328
|
+
this.baseMode.editor.setText('');
|
|
329
|
+
|
|
330
|
+
// Create UI component for display
|
|
331
|
+
this.mongoshComponent = new MongoshExecutionComponent(
|
|
332
|
+
command,
|
|
333
|
+
this.baseMode.ui,
|
|
334
|
+
excludeFromContext,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const isDeferred = this.baseMode.session.isStreaming;
|
|
338
|
+
|
|
339
|
+
if (isDeferred) {
|
|
340
|
+
// Show in pending area when agent is streaming
|
|
341
|
+
this.baseMode.pendingMessagesContainer.addChild(
|
|
342
|
+
this.mongoshComponent as Parameters<
|
|
343
|
+
ExtendedInteractiveMode['pendingMessagesContainer']['addChild']
|
|
344
|
+
>[0],
|
|
345
|
+
);
|
|
346
|
+
this.pendingMongoshComponents.push(this.mongoshComponent);
|
|
347
|
+
} else {
|
|
348
|
+
// Show in chat immediately when agent is idle
|
|
349
|
+
this.baseMode.chatContainer.addChild(
|
|
350
|
+
this.mongoshComponent as Parameters<
|
|
351
|
+
ExtendedInteractiveMode['chatContainer']['addChild']
|
|
352
|
+
>[0],
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
this.baseMode.ui.requestRender();
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
// Execute the mongosh command
|
|
360
|
+
const result = await this.mongoshEval(command);
|
|
361
|
+
|
|
362
|
+
if (this.mongoshComponent) {
|
|
363
|
+
if (result.error) {
|
|
364
|
+
this.mongoshComponent.appendOutput(`Error: ${result.error}`);
|
|
365
|
+
this.mongoshComponent.setComplete(1, false);
|
|
366
|
+
} else {
|
|
367
|
+
this.mongoshComponent.appendOutput(result.output);
|
|
368
|
+
this.mongoshComponent.setComplete(0, false);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Record result in session for context (unless excluded)
|
|
373
|
+
if (!excludeFromContext && this.baseMode.session.recordBashResult) {
|
|
374
|
+
this.baseMode.session.recordBashResult(
|
|
375
|
+
command,
|
|
376
|
+
{
|
|
377
|
+
exitCode: result.error ? 1 : 0,
|
|
378
|
+
output: result.output,
|
|
379
|
+
},
|
|
380
|
+
{ excludeFromContext: false },
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (this.mongoshComponent) {
|
|
385
|
+
const errorMsg =
|
|
386
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
387
|
+
this.mongoshComponent.appendOutput(`Error: ${errorMsg}`);
|
|
388
|
+
this.mongoshComponent.setComplete(1, false);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (this.debugLogging) {
|
|
392
|
+
process.stderr.write(
|
|
393
|
+
`[mongosh mode] Error executing command: ${error instanceof Error ? error.message : String(error)}\n`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
this.mongoshComponent = undefined;
|
|
399
|
+
this.baseMode.ui.requestRender();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async run(): Promise<void> {
|
|
403
|
+
// The base mode's run() method handles the main loop
|
|
404
|
+
// Our onSubmit override will intercept $ commands
|
|
405
|
+
await this.baseMode.run();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
stop(): void {
|
|
409
|
+
this.baseMode.stop();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Delegate other methods to base mode
|
|
413
|
+
showError(message: string): void {
|
|
414
|
+
this.baseMode.showError(message);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
showWarning(message: string): void {
|
|
418
|
+
this.baseMode.showWarning(message);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Script } from 'vm';
|
|
2
|
+
import type { ShellInstanceState } from './types';
|
|
3
|
+
|
|
4
|
+
export type ShellContext = {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
shellEvaluator: any;
|
|
7
|
+
originalEval: (input: string, context: object, filename: string) => unknown;
|
|
8
|
+
formatResultValue: (value: unknown) => Promise<string>;
|
|
9
|
+
instanceState: ShellInstanceState;
|
|
10
|
+
capturedPrintOutput: string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function createShellContext(options: {
|
|
14
|
+
shellContext: { db: { _mongo: { _instanceState: ShellInstanceState } } };
|
|
15
|
+
}): ShellContext {
|
|
16
|
+
const { shellContext } = options;
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
19
|
+
const { ShellEvaluator } = require('@mongosh/shell-evaluator');
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
21
|
+
const { toShellResult } = require('@mongosh/shell-api');
|
|
22
|
+
|
|
23
|
+
const instanceState = shellContext.db._mongo._instanceState;
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
const shellEvaluator = new ShellEvaluator(instanceState, (value: any) => value);
|
|
27
|
+
|
|
28
|
+
const originalEval = (input: string, context: object, filename: string) => {
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
30
|
+
return new Script(input, { filename }).runInContext(context as any);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const capturedPrintOutput: string[] = [];
|
|
34
|
+
|
|
35
|
+
// Patch the context's print function to capture output
|
|
36
|
+
if (instanceState.context) {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
const originalPrint = instanceState.context.print as (...args: any[]) => unknown;
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
instanceState.context.print = function(...args: any[]) {
|
|
41
|
+
const output = args.map((arg) =>
|
|
42
|
+
typeof arg === 'string' ? arg : JSON.stringify(arg)
|
|
43
|
+
).join(' ');
|
|
44
|
+
capturedPrintOutput.push(output);
|
|
45
|
+
// Also call original print if it exists
|
|
46
|
+
if (originalPrint) {
|
|
47
|
+
return originalPrint.apply(this, args);
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Also patch printjson
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
+
const originalPrintJson = instanceState.context.printjson as (value: any) => unknown;
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
+
instanceState.context.printjson = function(value: any) {
|
|
57
|
+
const output = typeof value === 'string'
|
|
58
|
+
? value
|
|
59
|
+
: JSON.stringify(value, null, 2);
|
|
60
|
+
capturedPrintOutput.push(output);
|
|
61
|
+
if (originalPrintJson) {
|
|
62
|
+
return originalPrintJson.call(this, value);
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const formatResultValue = async (value: unknown): Promise<string> => {
|
|
69
|
+
if (value === undefined) return '';
|
|
70
|
+
|
|
71
|
+
const shellResult = await toShellResult(value);
|
|
72
|
+
const printable = shellResult.printable;
|
|
73
|
+
|
|
74
|
+
if (printable === undefined || printable === null) {
|
|
75
|
+
return String(printable);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof printable === 'string') return printable;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
if (typeof printable.toJSON === 'function') {
|
|
82
|
+
return JSON.stringify(printable.toJSON(), null, 2);
|
|
83
|
+
}
|
|
84
|
+
return JSON.stringify(printable, null, 2);
|
|
85
|
+
} catch {
|
|
86
|
+
return String(printable);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
shellEvaluator,
|
|
92
|
+
originalEval,
|
|
93
|
+
formatResultValue,
|
|
94
|
+
instanceState,
|
|
95
|
+
capturedPrintOutput,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import type { Skill } from './types';
|
|
4
|
+
|
|
5
|
+
export function loadSkillsFromDir(skillsDir: string): Skill[] {
|
|
6
|
+
const skills: Skill[] = [];
|
|
7
|
+
if (!fs.existsSync(skillsDir)) return skills;
|
|
8
|
+
|
|
9
|
+
const files = fs.readdirSync(skillsDir).filter(f => f.endsWith('.md'));
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
const filePath = path.join(skillsDir, file);
|
|
12
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
13
|
+
|
|
14
|
+
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
15
|
+
let name = file.replace('.md', '');
|
|
16
|
+
let description = '';
|
|
17
|
+
let skillContent = content;
|
|
18
|
+
|
|
19
|
+
if (frontmatterMatch) {
|
|
20
|
+
const frontmatter = frontmatterMatch[1];
|
|
21
|
+
skillContent = content.slice(frontmatterMatch[0].length);
|
|
22
|
+
|
|
23
|
+
const nameMatch = frontmatter.match(/name:\s*(.+)/);
|
|
24
|
+
const descMatch = frontmatter.match(/description:\s*(.+)/);
|
|
25
|
+
if (nameMatch) name = nameMatch[1].trim();
|
|
26
|
+
if (descMatch) description = descMatch[1].trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
skills.push({
|
|
30
|
+
name,
|
|
31
|
+
description,
|
|
32
|
+
content: skillContent.trim(),
|
|
33
|
+
source: filePath,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return skills;
|
|
37
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type StdoutPatcher = {
|
|
2
|
+
enable: () => void;
|
|
3
|
+
disable: () => void;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// Specific problematic Kitty protocol sequences to suppress
|
|
7
|
+
const KITTY_QUERY = '\x1b[?u';
|
|
8
|
+
const KITTY_ENABLE_7U = '\x1b[>7u';
|
|
9
|
+
const KITTY_ENABLE_4_2M = '\x1b[>4;2m';
|
|
10
|
+
|
|
11
|
+
export function createStdoutPatcher(): StdoutPatcher {
|
|
12
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
13
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
14
|
+
let suppressKittyQueries = false;
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
const filterWrite = (original: any) =>
|
|
18
|
+
function (chunk: any, encoding?: any, callback?: any) {
|
|
19
|
+
if (suppressKittyQueries) {
|
|
20
|
+
const str = typeof chunk === 'string' ? chunk : chunk.toString();
|
|
21
|
+
// Only suppress specific Kitty protocol sequences (not general color codes)
|
|
22
|
+
const isKittySeq =
|
|
23
|
+
str === KITTY_QUERY ||
|
|
24
|
+
str === KITTY_ENABLE_7U ||
|
|
25
|
+
str === KITTY_ENABLE_4_2M ||
|
|
26
|
+
str.endsWith(KITTY_QUERY) ||
|
|
27
|
+
str.endsWith(KITTY_ENABLE_7U) ||
|
|
28
|
+
str.endsWith(KITTY_ENABLE_4_2M);
|
|
29
|
+
if (isKittySeq) {
|
|
30
|
+
if (typeof callback === 'function') callback();
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return original(chunk, encoding, callback);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
process.stdout.write = filterWrite(originalStdoutWrite);
|
|
38
|
+
process.stderr.write = filterWrite(originalStderrWrite);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
enable: () => {
|
|
42
|
+
suppressKittyQueries = true;
|
|
43
|
+
},
|
|
44
|
+
disable: () => {
|
|
45
|
+
suppressKittyQueries = false;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|