@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.
Files changed (61) hide show
  1. package/.eslintrc.js +13 -0
  2. package/.prettierrc.json +1 -0
  3. package/README.md +23 -0
  4. package/dist/agent-class.d.ts +47 -0
  5. package/dist/agent-class.js +314 -0
  6. package/dist/agent.d.ts +1 -0
  7. package/dist/agent.js +392 -0
  8. package/dist/banner.d.ts +1 -0
  9. package/dist/banner.js +23 -0
  10. package/dist/confirmation-extension.d.ts +10 -0
  11. package/dist/confirmation-extension.js +213 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +141 -0
  14. package/dist/mongosh-interactive-mode.d.ts +33 -0
  15. package/dist/mongosh-interactive-mode.js +244 -0
  16. package/dist/project-agent.d.ts +1 -0
  17. package/dist/project-agent.js +36 -0
  18. package/dist/shell-context.d.ts +17 -0
  19. package/dist/shell-context.js +75 -0
  20. package/dist/skills-loader.d.ts +2 -0
  21. package/dist/skills-loader.js +69 -0
  22. package/dist/src/index.d.ts +1 -0
  23. package/dist/src/index.js +8 -0
  24. package/dist/src/project-agent.d.ts +1 -0
  25. package/dist/src/project-agent.js +36 -0
  26. package/dist/stdout-patcher.d.ts +5 -0
  27. package/dist/stdout-patcher.js +41 -0
  28. package/dist/tools/index.d.ts +4 -0
  29. package/dist/tools/index.js +7 -0
  30. package/dist/tools/mongosh-eval.d.ts +7 -0
  31. package/dist/tools/mongosh-eval.js +84 -0
  32. package/dist/tools/search-docs.d.ts +2 -0
  33. package/dist/tools/search-docs.js +106 -0
  34. package/dist/tools/types.d.ts +12 -0
  35. package/dist/tools/types.js +2 -0
  36. package/dist/tools.d.ts +7 -0
  37. package/dist/tools.js +189 -0
  38. package/dist/types.d.ts +21 -0
  39. package/dist/types.js +2 -0
  40. package/package.json +38 -0
  41. package/skills/mongodb-connection.md +208 -0
  42. package/skills/mongodb-natural-language-querying.md +202 -0
  43. package/skills/mongodb-query-optimizer.md +265 -0
  44. package/skills/mongodb-schema-design.md +455 -0
  45. package/skills/mongodb-search-and-ai.md +357 -0
  46. package/skills/mongosh-shell.md +227 -0
  47. package/src/agent-class.ts +393 -0
  48. package/src/banner.ts +36 -0
  49. package/src/confirmation-extension.ts +297 -0
  50. package/src/index.ts +137 -0
  51. package/src/mongosh-interactive-mode.ts +420 -0
  52. package/src/shell-context.ts +97 -0
  53. package/src/skills-loader.ts +37 -0
  54. package/src/stdout-patcher.ts +48 -0
  55. package/src/tools/index.ts +4 -0
  56. package/src/tools/mongosh-eval.ts +115 -0
  57. package/src/tools/search-docs.ts +115 -0
  58. package/src/tools/types.ts +15 -0
  59. package/src/types.ts +23 -0
  60. package/tsconfig-lint.json +4 -0
  61. package/tsconfig.json +20 -0
@@ -0,0 +1,393 @@
1
+ import * as os from 'os';
2
+ import * as path from 'path';
3
+ import type { Tool } from './tools';
4
+ import type { Skill } from './types';
5
+ import type { StdoutPatcher } from './stdout-patcher';
6
+ import type { ShellContext } from './shell-context';
7
+ import { printBanner } from './banner';
8
+ import createConfirmationExtension from './confirmation-extension';
9
+ import { MongoshInteractiveMode } from './mongosh-interactive-mode';
10
+ import chalk from 'chalk';
11
+
12
+ export type AgentServices = {
13
+ createAgentSessionRuntime: typeof import('@earendil-works/pi-coding-agent').createAgentSessionRuntime;
14
+ createAgentSessionServices: typeof import('@earendil-works/pi-coding-agent').createAgentSessionServices;
15
+ createAgentSessionFromServices: typeof import('@earendil-works/pi-coding-agent').createAgentSessionFromServices;
16
+ SessionManager: typeof import('@earendil-works/pi-coding-agent').SessionManager;
17
+ InteractiveMode: typeof import('@earendil-works/pi-coding-agent').InteractiveMode;
18
+ SettingsManager: typeof import('@earendil-works/pi-coding-agent').SettingsManager;
19
+ getAgentDir: typeof import('@earendil-works/pi-coding-agent').getAgentDir;
20
+ AuthStorage: typeof import('@earendil-works/pi-coding-agent').AuthStorage;
21
+ ModelRegistry: typeof import('@earendil-works/pi-coding-agent').ModelRegistry;
22
+ };
23
+
24
+ export type AgentOptions = {
25
+ services: AgentServices;
26
+ mongoshEvalTool: Tool;
27
+ searchDocsTool: Tool;
28
+ loadedSkills: Skill[];
29
+ skillsDir: string;
30
+ debugLogging: boolean;
31
+ stdoutPatcher: StdoutPatcher;
32
+ shellContext: ShellContext;
33
+ };
34
+
35
+ export class Agent {
36
+ private static isRunning = false;
37
+
38
+ private sessionManager: ReturnType<
39
+ typeof import('@earendil-works/pi-coding-agent').SessionManager.create
40
+ >;
41
+ private services: AgentServices;
42
+ private mongoshEvalTool: Tool;
43
+ private searchDocsTool: Tool;
44
+ private loadedSkills: Skill[];
45
+ private skillsDir: string;
46
+ private debugLogging: boolean;
47
+ private stdoutPatcher: StdoutPatcher;
48
+ private shellContext: ShellContext;
49
+ private sessionId: string | undefined;
50
+ private resumeSessionId: string | undefined;
51
+
52
+ constructor(options: AgentOptions) {
53
+ this.services = options.services;
54
+ this.mongoshEvalTool = options.mongoshEvalTool;
55
+ this.searchDocsTool = options.searchDocsTool;
56
+ this.loadedSkills = options.loadedSkills;
57
+ this.skillsDir = options.skillsDir;
58
+ this.debugLogging = options.debugLogging;
59
+ this.stdoutPatcher = options.stdoutPatcher;
60
+ this.shellContext = options.shellContext;
61
+ this.sessionManager = this.services.SessionManager.create(process.cwd());
62
+ }
63
+
64
+ static getIsRunning(): boolean {
65
+ return Agent.isRunning;
66
+ }
67
+
68
+ getCurrentSessionId(): string | undefined {
69
+ return this.sessionId;
70
+ }
71
+
72
+ setResumeSessionId(sessionId: string): void {
73
+ this.resumeSessionId = sessionId;
74
+ }
75
+
76
+ async resume(sessionId: string): Promise<void> {
77
+ this.resumeSessionId = sessionId;
78
+ await this.run({ resumeSessionId: sessionId });
79
+ }
80
+
81
+ async run(options?: { resumeSessionId?: string }): Promise<void> {
82
+ if (Agent.isRunning) {
83
+ return;
84
+ }
85
+ Agent.isRunning = true;
86
+
87
+ const resumeSessionId = options?.resumeSessionId ?? this.resumeSessionId;
88
+
89
+ // Save and remove mongosh's stdin listeners to prevent interference with TUI
90
+ // Note: We intentionally do NOT pause() stdin as the TUI needs it for input handling
91
+ const savedListeners = process.stdin.rawListeners('data') as ((
92
+ ...args: unknown[]
93
+ ) => void)[];
94
+ process.stdin.removeAllListeners('data');
95
+ // Ensure stdin is in flowing mode for the TUI
96
+ if (process.stdin.isPaused()) {
97
+ process.stdin.resume();
98
+ }
99
+ // Save and set raw mode to enable capturing special keys (Ctrl+O, etc.)
100
+ const originalRawMode =
101
+ process.stdin.isTTY &&
102
+ (process.stdin as typeof process.stdin & { isRaw?: boolean }).isRaw;
103
+ if (process.stdin.isTTY) {
104
+ process.stdin.setRawMode(true);
105
+ }
106
+
107
+ const originalExit = process.exit.bind(process);
108
+
109
+ try {
110
+ const createRuntime = async (runtimeOptions: {
111
+ cwd: string;
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ sessionManager: any;
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
+ sessionStartEvent?: any;
116
+ }) => {
117
+ const {
118
+ SettingsManager,
119
+ createAgentSessionServices,
120
+ createAgentSessionFromServices,
121
+ AuthStorage,
122
+ ModelRegistry,
123
+ } = this.services;
124
+
125
+ const settingsManager = SettingsManager.inMemory({
126
+ quietStartup: true,
127
+ enableInstallTelemetry: false,
128
+ });
129
+
130
+ // Create auth storage and model registry with MongoDB provider pre-configured
131
+ const authStorage = AuthStorage.create();
132
+ const modelRegistry = ModelRegistry.create(authStorage);
133
+
134
+ // Register the MongoDB Docs provider (same as the ai snippet's mongodb provider)
135
+ // Note: The MongoDB Knowledge API doesn't require authentication, but Pi SDK requires apiKey field
136
+ modelRegistry.registerProvider('mongodb', {
137
+ name: 'MongoDB',
138
+ baseUrl: 'https://knowledge.mongodb.com/api/v1',
139
+ api: 'openai-responses',
140
+ apiKey: 'mongodb', // Dummy key - the actual API doesn't require authentication
141
+ authHeader: false, // Don't send Authorization header
142
+ headers: {
143
+ 'X-Request-Origin': 'mongodb-mongosh',
144
+ 'user-agent': 'mongodb-mongosh',
145
+ },
146
+ models: [
147
+ {
148
+ id: 'mongodb-chat-latest',
149
+ name: 'MongoDB Assistant',
150
+ reasoning: false,
151
+ input: ['text'],
152
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
153
+ contextWindow: 128000,
154
+ maxTokens: 4000, // MongoDB Knowledge API max allowed
155
+ },
156
+ ],
157
+ });
158
+
159
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
160
+ const mongoshSkills: any[] = this.loadedSkills.map((skill) => ({
161
+ name: skill.name,
162
+ description: skill.description,
163
+ filePath: skill.source,
164
+ baseDir: this.skillsDir,
165
+ source: 'custom',
166
+ sourceInfo: {
167
+ source: 'custom',
168
+ path: skill.source,
169
+ },
170
+ disableModelInvocation: false,
171
+ }));
172
+
173
+ const sessionServices = await createAgentSessionServices({
174
+ cwd: runtimeOptions.cwd,
175
+ settingsManager,
176
+ authStorage,
177
+ modelRegistry,
178
+ resourceLoaderOptions: {
179
+ extensionFactories: [createConfirmationExtension],
180
+ skillsOverride: (base) => ({
181
+ skills: [...base.skills, ...mongoshSkills],
182
+ diagnostics: base.diagnostics,
183
+ }),
184
+ systemPromptOverride: () => {
185
+ const basePrompt = `You are a MongoDB assistant running inside mongosh.
186
+
187
+ You are connected to a live MongoDB instance and can execute queries and commands using the mongosh_eval tool.
188
+
189
+ Guidelines:
190
+ - Always explain what you're about to do before running queries
191
+ - Use mongosh_eval for queries, inspections, and admin commands
192
+ - For destructive operations (drop, delete, update, insert), ask for confirmation first
193
+ - Suggest optimizations when you see inefficient patterns
194
+ - Use aggregation pipelines for complex data analysis
195
+ - Check indexes before suggesting queries on large collections
196
+
197
+ Available skills:
198
+ ${this.loadedSkills.map((s) => `- ${s.name}: ${s.description}`).join('\n')}
199
+
200
+ When responding:
201
+ 1. For simple questions, answer directly
202
+ 2. For database queries, use mongosh_eval to check and show results
203
+ 3. For performance questions, use explain plans to verify
204
+ 4. Always format JSON results for readability`;
205
+ return basePrompt;
206
+ },
207
+ },
208
+ });
209
+
210
+ // Determine if MongoDB should be the default model
211
+ // Only default to mongodb-chat-latest if it's the only available model
212
+ // Otherwise, require manual selection by the user
213
+ const availableModels = modelRegistry.getAvailable();
214
+ const mongodbModel = modelRegistry.find(
215
+ 'mongodb',
216
+ 'mongodb-chat-latest',
217
+ );
218
+
219
+ // Check if MongoDB is the only available model (no other providers configured)
220
+ const otherModelsExist = availableModels.some(
221
+ (m) => m.provider !== 'mongodb',
222
+ );
223
+ const shouldUseMongoDbAsDefault =
224
+ mongodbModel && !otherModelsExist && availableModels.length > 0;
225
+
226
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
+ const sessionFromServicesOptions: {
228
+ services: typeof sessionServices;
229
+ sessionManager: (typeof runtimeOptions)['sessionManager'];
230
+ sessionStartEvent?: (typeof runtimeOptions)['sessionStartEvent'];
231
+ customTools: Tool[];
232
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
+ model?: any;
234
+ } = {
235
+ services: sessionServices,
236
+ sessionManager: runtimeOptions.sessionManager,
237
+ sessionStartEvent: runtimeOptions.sessionStartEvent,
238
+ customTools: [this.mongoshEvalTool, this.searchDocsTool],
239
+ };
240
+
241
+ // Only set the model if MongoDB should be the default
242
+ if (shouldUseMongoDbAsDefault) {
243
+ sessionFromServicesOptions.model = mongodbModel;
244
+ }
245
+
246
+ return {
247
+ ...(await createAgentSessionFromServices(sessionFromServicesOptions)),
248
+ services: sessionServices,
249
+ diagnostics: sessionServices.diagnostics,
250
+ };
251
+ };
252
+
253
+ const { createAgentSessionRuntime } = this.services;
254
+
255
+ // Use custom MongoDB agent directory
256
+ const agentDir = path.join(os.homedir(), '.mongodb', 'mongosh', 'agent');
257
+
258
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
+ const sessionStartEvent: any = resumeSessionId
260
+ ? { type: 'session-resume', sessionId: resumeSessionId }
261
+ : undefined;
262
+
263
+ const runtime = await createAgentSessionRuntime(createRuntime, {
264
+ cwd: process.cwd(),
265
+ agentDir,
266
+ sessionManager: this.sessionManager,
267
+ ...(sessionStartEvent && { sessionStartEvent }),
268
+ });
269
+
270
+ // Create mongosh eval function for the interactive mode
271
+ const mongoshEval = async (
272
+ expression: string,
273
+ ): Promise<{ output: string; error?: string }> => {
274
+ const {
275
+ shellEvaluator,
276
+ originalEval,
277
+ formatResultValue,
278
+ instanceState,
279
+ capturedPrintOutput,
280
+ } = this.shellContext;
281
+
282
+ // Clear captured output before execution
283
+ capturedPrintOutput.length = 0;
284
+
285
+ try {
286
+ let rawValue = await shellEvaluator.customEval(
287
+ originalEval,
288
+ expression,
289
+ instanceState.context,
290
+ 'mongosh_interactive',
291
+ );
292
+
293
+ // Auto-call functions that take no arguments (e.g., `history` -> `history()`)
294
+ // This provides shell-like behavior for zero-argument functions
295
+ if (typeof rawValue === 'function') {
296
+ try {
297
+ rawValue = await rawValue();
298
+ } catch {
299
+ // If calling fails, keep the original function reference
300
+ }
301
+ }
302
+
303
+ const formatted = await formatResultValue(rawValue);
304
+
305
+ // Build output: captured print output takes priority, then add formatted result if present
306
+ let output: string;
307
+ if (capturedPrintOutput.length > 0) {
308
+ // Has captured print output - use it as primary output
309
+ output = capturedPrintOutput.join('\n');
310
+ // Also append formatted result if it's meaningful (not empty/undefined)
311
+ if (formatted) {
312
+ output += '\n' + formatted;
313
+ }
314
+ } else if (formatted) {
315
+ // No captured output, but has formatted result
316
+ output = formatted;
317
+ } else {
318
+ // Nothing to show
319
+ output = '(no output)';
320
+ }
321
+
322
+ return { output };
323
+ } catch (err) {
324
+ const errorMsg =
325
+ err instanceof Error ? `${err.name}: ${err.message}` : String(err);
326
+
327
+ return { output: '', error: errorMsg };
328
+ }
329
+ };
330
+
331
+ // Create our custom interactive mode with $ mongosh support
332
+ const mode = new MongoshInteractiveMode(runtime, {
333
+ migratedProviders: [],
334
+ initialImages: [],
335
+ initialMessages: [],
336
+ verbose: this.debugLogging,
337
+ shellContext: this.shellContext,
338
+ mongoshEval,
339
+ debugLogging: this.debugLogging,
340
+ InteractiveMode: this.services.InteractiveMode,
341
+ });
342
+
343
+ this.stdoutPatcher.enable();
344
+
345
+ await printBanner();
346
+
347
+ // Capture session ID from sessionManager
348
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
349
+ this.sessionId = (this.sessionManager as any).sessionId;
350
+
351
+ // Initialize the mode (sets up onSubmit handler)
352
+ await mode.init();
353
+
354
+ await new Promise<void>((resolve) => {
355
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
356
+ process.exit = (() => {
357
+ resolve();
358
+ }) as any;
359
+
360
+ mode.run().catch(() => {
361
+ resolve();
362
+ });
363
+ });
364
+
365
+ this.stdoutPatcher.disable();
366
+ } catch (err) {
367
+ if (this.debugLogging) {
368
+ process.stderr.write(`[agent] Error: ${String(err)}\n`);
369
+ }
370
+ } finally {
371
+ Agent.isRunning = false;
372
+ process.exit = originalExit;
373
+ // Restore terminal state
374
+ if (process.stdin.isTTY) {
375
+ process.stdin.setRawMode(originalRawMode ?? false);
376
+ }
377
+ for (const listener of savedListeners) {
378
+ process.stdin.on('data', listener);
379
+ }
380
+ process.stdin.resume();
381
+ const sessionId = this.sessionId || this.resumeSessionId;
382
+ if (sessionId) {
383
+ process.stdout.write(
384
+ `Exited agent mode, resume your session with:\n${chalk.green(`agent.resume ${sessionId}`)}`,
385
+ );
386
+ } else {
387
+ process.stdout.write('\n[Exited agent mode]');
388
+ }
389
+ // Force prompt redraw - emit newline then move up to trigger readline refresh
390
+ process.stdin.emit('data', '\n');
391
+ }
392
+ }
393
+ }
package/src/banner.ts ADDED
@@ -0,0 +1,36 @@
1
+ export async function printBanner(): Promise<void> {
2
+ const chalk = await import('chalk');
3
+ const g = chalk.default.green;
4
+ const w = chalk.default.white.bold;
5
+ const dim = chalk.default.gray;
6
+
7
+ const piAgent = await import('@earendil-works/pi-coding-agent');
8
+ const piVersion = (piAgent as { VERSION?: string }).VERSION ?? 'unknown';
9
+
10
+ process.stdout.write('\n');
11
+ process.stdout.write(` ${g(' . ')}\n`);
12
+ process.stdout.write(` ${g(' /|\\ ')} ${w('mongosh')}\n`);
13
+ process.stdout.write(
14
+ ` ${g(' / | \\ ')} ${g('▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄ ▄▄▄ ▄▄█▄▄')}\n`,
15
+ );
16
+ process.stdout.write(
17
+ ` ${g(' / | \\')} ${g('▀ █ █▀ ▀█ █▀ █ █▀ █ █')}\n`,
18
+ );
19
+ process.stdout.write(
20
+ ` ${g(' | | |')} ${g('▄▀▀▀█ █ █ █▀▀▀▀ █ █ █')}\n`,
21
+ );
22
+ process.stdout.write(
23
+ ` ${g(' \\ | /')} ${g('▀▄▄▀█ ▀█▄▀█ ▀█▄▄▀ █ █ ▀▄▄')}\n`,
24
+ );
25
+ process.stdout.write(` ${g(' \\/|\\/ ')} ${g(' ▄ █')}\n`);
26
+ process.stdout.write(
27
+ ` ${g(' ||| ')} ${g(' ▀▀')} ${dim(`powered by pi ${piVersion}`)}\n`,
28
+ );
29
+ process.stdout.write(`\n`);
30
+ process.stdout.write(
31
+ dim(' Type your prompts below. Enter to send, /quit to quit.\n'),
32
+ );
33
+ process.stdout.write(
34
+ dim(' Run mongosh commands manually with: $ <query>\n'),
35
+ );
36
+ }