@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
package/dist/agent.js ADDED
@@ -0,0 +1,392 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fs = __importStar(require("fs"));
37
+ const path = __importStar(require("path"));
38
+ const vm_1 = require("vm");
39
+ // Load skills from the skills directory
40
+ function loadSkillsFromDir(skillsDir) {
41
+ const skills = [];
42
+ if (!fs.existsSync(skillsDir))
43
+ return skills;
44
+ const files = fs.readdirSync(skillsDir).filter(f => f.endsWith('.md'));
45
+ for (const file of files) {
46
+ const filePath = path.join(skillsDir, file);
47
+ const content = fs.readFileSync(filePath, 'utf-8');
48
+ // Parse frontmatter
49
+ const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
50
+ let name = file.replace('.md', '');
51
+ let description = '';
52
+ let skillContent = content;
53
+ if (frontmatterMatch) {
54
+ const frontmatter = frontmatterMatch[1];
55
+ skillContent = content.slice(frontmatterMatch[0].length);
56
+ const nameMatch = frontmatter.match(/name:\s*(.+)/);
57
+ const descMatch = frontmatter.match(/description:\s*(.+)/);
58
+ if (nameMatch)
59
+ name = nameMatch[1].trim();
60
+ if (descMatch)
61
+ description = descMatch[1].trim();
62
+ }
63
+ skills.push({
64
+ name,
65
+ description,
66
+ content: skillContent.trim(),
67
+ source: filePath,
68
+ });
69
+ }
70
+ return skills;
71
+ }
72
+ module.exports = async (mongoshContext) => {
73
+ const logRequests = process.env.DEBUG_AGENT_REQUESTS === '1';
74
+ const debugLogging = process.env.DEBUG_AGENT === '1';
75
+ // Store reference to mongosh shell for use in tools
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ const shellContext = mongoshContext;
78
+ if (debugLogging) {
79
+ process.stderr.write(`[agent] DEBUG_AGENT_REQUESTS=${process.env.DEBUG_AGENT_REQUESTS ?? 'undefined'}\n`);
80
+ }
81
+ if (logRequests) {
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
+ const originalFetch = globalThis.fetch;
84
+ if (originalFetch) {
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ globalThis.fetch = async (input, init) => {
87
+ const url = typeof input === 'string' ? input : input.toString();
88
+ const method = init?.method || 'GET';
89
+ process.stderr.write(`[agent:fetch] ${method} ${url}\n`);
90
+ const start = Date.now();
91
+ try {
92
+ const response = await originalFetch(input, init);
93
+ process.stderr.write(`[agent:fetch] Response: ${response.status} (${Date.now() - start}ms)\n`);
94
+ return response;
95
+ }
96
+ catch (err) {
97
+ process.stderr.write(`[agent:fetch] Error: ${err}\n`);
98
+ throw err;
99
+ }
100
+ };
101
+ }
102
+ }
103
+ const { createAgentSessionRuntime, createAgentSessionServices, createAgentSessionFromServices, SessionManager, InteractiveMode, SettingsManager, getAgentDir, initTheme, defineTool, } = await import('@earendil-works/pi-coding-agent');
104
+ const { Type } = await import('@sinclair/typebox');
105
+ // Initialize default dark theme
106
+ initTheme('dark', false);
107
+ // Create settings with quiet startup to hide pi branding
108
+ const settingsManager = SettingsManager.inMemory({
109
+ quietStartup: true,
110
+ });
111
+ // Load MongoDB skills from the skills directory
112
+ const skillsDir = path.join(__dirname, '..', 'skills');
113
+ const loadedSkills = loadSkillsFromDir(skillsDir);
114
+ // Build skills array for resourceLoader
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ const mongoshSkills = loadedSkills.map(skill => ({
117
+ name: skill.name,
118
+ description: skill.description,
119
+ filePath: skill.source,
120
+ baseDir: skillsDir,
121
+ source: 'custom',
122
+ sourceInfo: {
123
+ source: 'custom',
124
+ path: skill.source,
125
+ },
126
+ disableModelInvocation: false,
127
+ }));
128
+ // Set up a ShellEvaluator — the same evaluation pipeline that the mongosh
129
+ // REPL uses. This gives us async-rewriter support (so `db.coll.find()`
130
+ // auto-awaits), direct shell commands (`show dbs`, `use <db>`, etc.), and
131
+ // proper output formatting for cursors / BSON types.
132
+ //
133
+ // Both @mongosh/shell-evaluator and @mongosh/shell-api are already loaded
134
+ // in the mongosh process so we can require() them directly.
135
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
136
+ const { ShellEvaluator } = require('@mongosh/shell-evaluator');
137
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
138
+ const { toShellResult } = require('@mongosh/shell-api');
139
+ const instanceState = shellContext.db._mongo._instanceState;
140
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
+ const shellEvaluator = new ShellEvaluator(instanceState, (value) => value);
142
+ // The same originalEval the REPL passes: compile the (async-rewritten) code
143
+ // with vm.Script and run it in the shell's existing context which already
144
+ // has db, rs, sh, sp, sleep, ObjectId, NumberLong, etc.
145
+ const originalEval = (input, context, filename) => {
146
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
+ return new vm_1.Script(input, { filename }).runInContext(context);
148
+ };
149
+ // Capture print() / console.log() output during eval so it can be returned
150
+ // alongside the result.
151
+ let capturedPrintOutput = [];
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
+ const formatResultValue = async (value) => {
154
+ if (value === undefined)
155
+ return '';
156
+ const shellResult = await toShellResult(value);
157
+ const printable = shellResult.printable;
158
+ if (printable === undefined || printable === null) {
159
+ return String(printable);
160
+ }
161
+ if (typeof printable === 'string')
162
+ return printable;
163
+ try {
164
+ // EJSON-style serialisation for objects — handles BSON types gracefully
165
+ if (typeof printable.toJSON === 'function') {
166
+ return JSON.stringify(printable.toJSON(), null, 2);
167
+ }
168
+ return JSON.stringify(printable, null, 2);
169
+ }
170
+ catch {
171
+ return String(printable);
172
+ }
173
+ };
174
+ // Define mongosh_eval tool for executing shell expressions
175
+ const mongoshEvalTool = defineTool({
176
+ name: 'mongosh_eval',
177
+ label: 'mongosh eval',
178
+ description: 'Execute a mongosh shell expression against the connected MongoDB instance. ' +
179
+ 'Supports the full mongosh API: queries, aggregations, admin commands, ' +
180
+ 'direct shell commands (show dbs, use <db>, it, etc.), and auto-awaiting. ' +
181
+ 'The expression runs in the same context as the interactive mongosh REPL. ' +
182
+ 'For destructive operations (drop, delete, insert, update), ask the user to confirm first.',
183
+ parameters: Type.Object({
184
+ expression: Type.String({
185
+ description: 'The mongosh expression to evaluate. Examples: "db.getMongo()", "db.users.find().limit(5)", "show dbs", "use mydb", "db.serverStatus()"',
186
+ }),
187
+ }),
188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
+ execute: async (_toolCallId, params) => {
190
+ const expr = params.expression;
191
+ capturedPrintOutput = [];
192
+ try {
193
+ const rawValue = await shellEvaluator.customEval(originalEval, expr, instanceState.context, 'mongosh_eval');
194
+ const formatted = await formatResultValue(rawValue);
195
+ const parts = [];
196
+ if (capturedPrintOutput.length > 0) {
197
+ parts.push(capturedPrintOutput.join('\n'));
198
+ }
199
+ if (formatted) {
200
+ parts.push(formatted);
201
+ }
202
+ const output = parts.join('\n') || '(no output)';
203
+ if (debugLogging) {
204
+ process.stderr.write(`[mongosh_eval] Output: ${output.substring(0, 200)}\n`);
205
+ }
206
+ return {
207
+ content: [{ type: 'text', text: output }],
208
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
209
+ details: { expression: expr },
210
+ };
211
+ }
212
+ catch (err) {
213
+ const errorMsg = err instanceof Error
214
+ ? `${err.name}: ${err.message}`
215
+ : String(err);
216
+ if (debugLogging) {
217
+ process.stderr.write(`[mongosh_eval] Error: ${errorMsg}\n`);
218
+ }
219
+ const parts = [];
220
+ if (capturedPrintOutput.length > 0) {
221
+ parts.push(capturedPrintOutput.join('\n'));
222
+ }
223
+ parts.push(`Error: ${errorMsg}`);
224
+ return {
225
+ content: [{ type: 'text', text: parts.join('\n') }],
226
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
+ details: { error: errorMsg, expression: expr },
228
+ };
229
+ }
230
+ },
231
+ });
232
+ // Suppress Kitty keyboard protocol query sequences by intercepting stdout.write
233
+ // The sequence "\x1b[?u" triggers terminal responses that can leak as "3u"
234
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
235
+ let suppressKittyQueries = false;
236
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
237
+ process.stdout.write = function (chunk, encoding, callback) {
238
+ if (suppressKittyQueries) {
239
+ const str = typeof chunk === 'string' ? chunk : chunk.toString();
240
+ // Filter out Kitty protocol query and enable sequences
241
+ if (str.includes('\x1b[?u') || str.includes('\x1b[>7u') || str.includes('\x1b[>4;2m')) {
242
+ if (debugLogging) {
243
+ process.stderr.write(`[agent] Suppressed Kitty sequence: ${str}\n`);
244
+ }
245
+ // Call callback immediately to avoid hanging
246
+ if (typeof callback === 'function') {
247
+ callback();
248
+ }
249
+ return true;
250
+ }
251
+ }
252
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
253
+ return originalStdoutWrite(chunk, encoding, callback);
254
+ };
255
+ class Agent {
256
+ constructor({ sessionManager }) {
257
+ this.sessionManager = sessionManager;
258
+ }
259
+ static create() {
260
+ const sessionManager = SessionManager.create(process.cwd());
261
+ return new Agent({ sessionManager });
262
+ }
263
+ async run() {
264
+ // Detach mongosh's stdin listeners so TUI can own stdin
265
+ const savedListeners = process.stdin.rawListeners('data');
266
+ process.stdin.removeAllListeners('data');
267
+ process.stdin.pause();
268
+ // Intercept process.exit so /quit and Ctrl+D return to mongosh
269
+ const originalExit = process.exit;
270
+ try {
271
+ // Create the runtime factory for InteractiveMode
272
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
273
+ const createRuntime = async (options) => {
274
+ const services = await createAgentSessionServices({
275
+ cwd: options.cwd,
276
+ settingsManager,
277
+ resourceLoaderOptions: {
278
+ // Inject MongoDB skills
279
+ skillsOverride: (base) => ({
280
+ skills: [...base.skills, ...mongoshSkills],
281
+ diagnostics: base.diagnostics,
282
+ }),
283
+ // Override system prompt with MongoDB context
284
+ systemPromptOverride: () => {
285
+ const basePrompt = `You are a MongoDB assistant running inside mongosh.
286
+
287
+ You are connected to a live MongoDB instance and can execute queries and commands using the mongosh_eval tool.
288
+
289
+ Guidelines:
290
+ - Always explain what you're about to do before running queries
291
+ - Use mongosh_eval for queries, inspections, and admin commands
292
+ - For destructive operations (drop, delete, update, insert), ask for confirmation first
293
+ - Suggest optimizations when you see inefficient patterns
294
+ - Use aggregation pipelines for complex data analysis
295
+ - Check indexes before suggesting queries on large collections
296
+
297
+ Available skills:
298
+ ${loadedSkills.map(s => `- ${s.name}: ${s.description}`).join('\n')}
299
+
300
+ When responding:
301
+ 1. For simple questions, answer directly
302
+ 2. For database queries, use mongosh_eval to check and show results
303
+ 3. For performance questions, use explain plans to verify
304
+ 4. Always format JSON results for readability`;
305
+ return basePrompt;
306
+ },
307
+ },
308
+ });
309
+ return {
310
+ ...(await createAgentSessionFromServices({
311
+ services,
312
+ sessionManager: options.sessionManager,
313
+ sessionStartEvent: options.sessionStartEvent,
314
+ customTools: [mongoshEvalTool],
315
+ })),
316
+ services,
317
+ diagnostics: services.diagnostics,
318
+ };
319
+ };
320
+ // Create the runtime
321
+ const runtime = await createAgentSessionRuntime(createRuntime, {
322
+ cwd: process.cwd(),
323
+ agentDir: getAgentDir(),
324
+ sessionManager: this.sessionManager,
325
+ });
326
+ // Create InteractiveMode
327
+ const mode = new InteractiveMode(runtime, {
328
+ migratedProviders: [],
329
+ initialImages: [],
330
+ initialMessages: [],
331
+ verbose: debugLogging,
332
+ });
333
+ // Enable Kitty sequence suppression during TUI initialization
334
+ suppressKittyQueries = true;
335
+ // Print MongoDB leaf welcome banner before TUI takes over
336
+ const chalk = await import('chalk');
337
+ const g = chalk.default.green;
338
+ const w = chalk.default.white.bold;
339
+ const dim = chalk.default.gray;
340
+ process.stdout.write('\n');
341
+ process.stdout.write(` ${g(' . ')}\n`);
342
+ process.stdout.write(` ${g(' /|\\ ')} ${w('mongosh')}\n`);
343
+ process.stdout.write(` ${g(' / | \\ ')} ${g('▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄ ▄▄▄ ▄▄█▄▄')}\n`);
344
+ process.stdout.write(` ${g(' / | \\')} ${g('▀ █ █▀ ▀█ █▀ █ █▀ █ █')}\n`);
345
+ process.stdout.write(` ${g(' | ||| |')} ${g('▄▀▀▀█ █ █ █▀▀▀▀ █ █ █')}\n`);
346
+ process.stdout.write(` ${g(' \\ ||| /')} ${g('▀▄▄▀█ ▀█▄▀█ ▀█▄▄▀ █ █ ▀▄▄')}\n`);
347
+ process.stdout.write(` ${g(' \\|||/ ')} ${g(' ▄ █')}\n`);
348
+ process.stdout.write(` ${g(' ||| ')} ${g(' ▀▀')}\n`);
349
+ process.stdout.write(`\n`);
350
+ process.stdout.write(dim(' Type your prompts below. Enter to send, Alt+Enter for new line, /quit to quit.') + '\n\n');
351
+ // Wrap mode.run() in a promise that resolves when process.exit is called
352
+ // InteractiveMode.run() has a while(true) loop that only breaks via process.exit()
353
+ await new Promise((resolve) => {
354
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
355
+ process.exit = (() => {
356
+ resolve();
357
+ });
358
+ mode.run().catch(() => {
359
+ resolve();
360
+ });
361
+ });
362
+ // Disable suppression after run completes
363
+ suppressKittyQueries = false;
364
+ }
365
+ catch (err) {
366
+ if (debugLogging) {
367
+ process.stderr.write(`[agent] Error: ${err}\n`);
368
+ }
369
+ }
370
+ finally {
371
+ process.exit = originalExit;
372
+ // Restore mongosh's stdin listeners
373
+ for (const listener of savedListeners) {
374
+ process.stdin.on('data', listener);
375
+ }
376
+ process.stdin.resume();
377
+ process.stdout.write('\n[Exited agent mode]\n');
378
+ }
379
+ }
380
+ }
381
+ const agent = Agent.create();
382
+ // Register "agent" as a direct shell command
383
+ const agentFn = async () => {
384
+ await agent.run();
385
+ };
386
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
387
+ agentFn.isDirectShellCommand = true;
388
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
389
+ agentFn.returnsPromise = true;
390
+ instanceState.shellApi['agent'] = agentFn;
391
+ instanceState.context['agent'] = agentFn;
392
+ };
@@ -0,0 +1 @@
1
+ export declare function printBanner(): Promise<void>;
package/dist/banner.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printBanner = printBanner;
4
+ async function printBanner() {
5
+ const chalk = await import('chalk');
6
+ const g = chalk.default.green;
7
+ const w = chalk.default.white.bold;
8
+ const dim = chalk.default.gray;
9
+ const piAgent = await import('@earendil-works/pi-coding-agent');
10
+ const piVersion = piAgent.VERSION ?? 'unknown';
11
+ process.stdout.write('\n');
12
+ process.stdout.write(` ${g(' . ')}\n`);
13
+ process.stdout.write(` ${g(' /|\\ ')} ${w('mongosh')}\n`);
14
+ process.stdout.write(` ${g(' / | \\ ')} ${g('▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄ ▄▄▄ ▄▄█▄▄')}\n`);
15
+ process.stdout.write(` ${g(' / | \\')} ${g('▀ █ █▀ ▀█ █▀ █ █▀ █ █')}\n`);
16
+ process.stdout.write(` ${g(' | | |')} ${g('▄▀▀▀█ █ █ █▀▀▀▀ █ █ █')}\n`);
17
+ process.stdout.write(` ${g(' \\ | /')} ${g('▀▄▄▀█ ▀█▄▀█ ▀█▄▄▀ █ █ ▀▄▄')}\n`);
18
+ process.stdout.write(` ${g(' \\/|\\/ ')} ${g(' ▄ █')}\n`);
19
+ process.stdout.write(` ${g(' ||| ')} ${g(' ▀▀')} ${dim(`powered by pi ${piVersion}`)}\n`);
20
+ process.stdout.write(`\n`);
21
+ process.stdout.write(dim(' Type your prompts below. Enter to send, /quit to quit.\n'));
22
+ process.stdout.write(dim(' Run mongosh commands manually with: $ <query>\n'));
23
+ }
@@ -0,0 +1,10 @@
1
+ type ConfirmationExtensionOptions = {
2
+ skipConfirmation?: boolean;
3
+ allowedTools?: string[];
4
+ blockedTools?: string[];
5
+ skipConfirmTools?: string[];
6
+ };
7
+ export declare function setConfirmationOptions(options: ConfirmationExtensionOptions): void;
8
+ export declare function getConfirmationOptions(): ConfirmationExtensionOptions;
9
+ export default function createConfirmationExtension(pi: any): void;
10
+ export {};
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.setConfirmationOptions = setConfirmationOptions;
7
+ exports.getConfirmationOptions = getConfirmationOptions;
8
+ exports.default = createConfirmationExtension;
9
+ const util_1 = require("util");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const cli_highlight_1 = require("cli-highlight");
12
+ let globalOptions = {};
13
+ function setConfirmationOptions(options) {
14
+ globalOptions = options;
15
+ }
16
+ function getConfirmationOptions() {
17
+ return globalOptions;
18
+ }
19
+ function formatToolName(toolName) {
20
+ const displayName = toolName.replace(/_/g, ' ');
21
+ return chalk_1.default.white.bold(displayName);
22
+ }
23
+ function formatToolParams(toolName, input) {
24
+ const lines = [];
25
+ for (const [key, value] of Object.entries(input)) {
26
+ const formatted = typeof value === 'string'
27
+ ? value
28
+ : (0, util_1.inspect)(value, { depth: 3, breakLength: 80 });
29
+ lines.push(` ${chalk_1.default.gray(key)}: ${formatted}`);
30
+ }
31
+ return lines.length > 0 ? '\n\n' + lines.join('\n') : '';
32
+ }
33
+ function stripAnsiCodes(str) {
34
+ // eslint-disable-next-line no-control-regex
35
+ return str.replace(/\u001b\[[0-9;]*m/g, '');
36
+ }
37
+ function formatWithBackground(content) {
38
+ // ANSI color codes - darker gray background (48;5;236) and reset
39
+ const BG_DARK_GRAY = '\u001b[48;5;236m';
40
+ const RESET = '\u001b[0m';
41
+ const PADDING = ' ';
42
+ // Always use maximum available terminal width (minus small margin)
43
+ const terminalWidth = process.stdout.columns || 80;
44
+ const boxWidth = Math.max(40, terminalWidth - 2);
45
+ const contentWidth = boxWidth - 4; // minus padding on both sides
46
+ // Split content and wrap long lines to fit the full width
47
+ const rawLines = content.split('\n');
48
+ const processedLines = [];
49
+ for (const line of rawLines) {
50
+ const visibleLen = stripAnsiCodes(line).length;
51
+ if (visibleLen > contentWidth) {
52
+ // Hard wrap: keep ANSI codes, split at visible character boundary
53
+ let currentLine = line;
54
+ let currentVisibleLen = visibleLen;
55
+ while (currentVisibleLen > contentWidth) {
56
+ // Map visible character position to actual string position (preserving ANSI codes)
57
+ let visibleCount = 0;
58
+ let actualIndex = 0;
59
+ for (let i = 0; i < currentLine.length && visibleCount < contentWidth; i++) {
60
+ if (currentLine[i] === '\u001b') {
61
+ // Skip ANSI sequence
62
+ while (i < currentLine.length && currentLine[i] !== 'm') {
63
+ i++;
64
+ }
65
+ }
66
+ else {
67
+ visibleCount++;
68
+ }
69
+ actualIndex = i + 1;
70
+ }
71
+ processedLines.push(currentLine.slice(0, actualIndex));
72
+ currentLine = currentLine.slice(actualIndex);
73
+ currentVisibleLen = stripAnsiCodes(currentLine).length;
74
+ }
75
+ if (currentLine.length > 0) {
76
+ processedLines.push(currentLine);
77
+ }
78
+ }
79
+ else {
80
+ processedLines.push(line);
81
+ }
82
+ }
83
+ // Build lines with dark gray background extending to full box width
84
+ const formattedLines = processedLines.map((line) => {
85
+ const visibleLen = stripAnsiCodes(line).length;
86
+ const pad = ' '.repeat(Math.max(0, contentWidth - visibleLen));
87
+ return `${BG_DARK_GRAY}${PADDING}${line}${pad}${PADDING}${RESET}`;
88
+ });
89
+ // Add empty padding lines with same full-width background
90
+ const emptyLine = `${BG_DARK_GRAY}${' '.repeat(boxWidth)}${RESET}`;
91
+ return [emptyLine, ...formattedLines, emptyLine].join('\n');
92
+ }
93
+ function formatConfirmationMessage(toolName, input) {
94
+ if (toolName === 'mongosh_eval') {
95
+ const expression = input.expression;
96
+ if (expression) {
97
+ const highlighted = (0, cli_highlight_1.highlight)(expression, {
98
+ language: 'javascript',
99
+ theme: {
100
+ keyword: chalk_1.default.magenta,
101
+ function: chalk_1.default.cyan,
102
+ string: chalk_1.default.green,
103
+ number: chalk_1.default.yellow,
104
+ comment: chalk_1.default.gray,
105
+ operator: chalk_1.default.white,
106
+ punctuation: chalk_1.default.white,
107
+ literal: chalk_1.default.yellow,
108
+ params: chalk_1.default.white,
109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
+ },
111
+ });
112
+ return {
113
+ title: `Run this mongosh script? ${chalk_1.default.gray('(please review the code above)')}`,
114
+ message: '\n' + formatWithBackground(highlighted),
115
+ };
116
+ }
117
+ }
118
+ const nameLine = formatToolName(toolName);
119
+ const paramsSection = formatToolParams(toolName, input);
120
+ return {
121
+ title: 'Tool Call Confirmation',
122
+ message: nameLine + paramsSection,
123
+ };
124
+ }
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ function createConfirmationExtension(pi) {
127
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
128
+ pi.on('tool_call', async (event, ctx) => {
129
+ const { toolName, input } = event;
130
+ const options = getConfirmationOptions();
131
+ if (options.skipConfirmation) {
132
+ return;
133
+ }
134
+ if (options.allowedTools && !options.allowedTools.includes(toolName)) {
135
+ return {
136
+ block: true,
137
+ reason: `Tool "${toolName}" is not in the allowed tools list.`,
138
+ };
139
+ }
140
+ if (options.blockedTools?.includes(toolName)) {
141
+ return {
142
+ block: true,
143
+ reason: `Tool "${toolName}" is blocked by policy.`,
144
+ };
145
+ }
146
+ // Skip confirmation for whitelisted tools
147
+ if (options.skipConfirmTools?.includes(toolName)) {
148
+ return;
149
+ }
150
+ const { title, message } = formatConfirmationMessage(toolName, input);
151
+ // Show code in chat history (static, scrollable)
152
+ ctx.ui.notify(message, 'info');
153
+ // Custom minimal confirm dialog using Pi SDK's custom() API with done callback
154
+ const confirmed = await ctx.ui.custom((_tui, _theme, _keybindings, done) => {
155
+ let selectedIndex = 0;
156
+ const opts = ['Yes', 'No'];
157
+ return {
158
+ render: () => {
159
+ const lines = [];
160
+ // Title at the top of the dialog
161
+ lines.push(chalk_1.default.white.bold(title));
162
+ // Yes/No options
163
+ for (let i = 0; i < opts.length; i++) {
164
+ const isSelected = i === selectedIndex;
165
+ const line = isSelected
166
+ ? `${chalk_1.default.cyan('→')} ${chalk_1.default.cyan.bold(opts[i])}`
167
+ : ` ${chalk_1.default.gray(opts[i])}`;
168
+ lines.push(line);
169
+ }
170
+ return lines;
171
+ },
172
+ invalidate: () => { },
173
+ handleInput: (keyData) => {
174
+ // Up/Down to navigate
175
+ if (keyData === '\u001b[A') {
176
+ // Up arrow
177
+ selectedIndex = Math.max(0, selectedIndex - 1);
178
+ }
179
+ else if (keyData === '\u001b[B') {
180
+ // Down arrow
181
+ selectedIndex = Math.min(opts.length - 1, selectedIndex + 1);
182
+ }
183
+ else if (keyData === '\r' || keyData === '\n') {
184
+ // Enter to confirm
185
+ done(selectedIndex === 0); // Yes = index 0
186
+ }
187
+ else if (keyData === '\x03' || keyData === '\u001b') {
188
+ // Ctrl+C or Escape to cancel
189
+ done(false);
190
+ }
191
+ else if (keyData.toLowerCase() === 'y') {
192
+ done(true);
193
+ }
194
+ else if (keyData.toLowerCase() === 'n') {
195
+ done(false);
196
+ }
197
+ },
198
+ };
199
+ }, {
200
+ overlay: true,
201
+ overlayOptions: {
202
+ anchor: 'bottom-center',
203
+ offsetY: -4, // Move up above the input field
204
+ },
205
+ });
206
+ if (!confirmed) {
207
+ return {
208
+ block: true,
209
+ reason: `Tool "${toolName}" was cancelled by user.`,
210
+ };
211
+ }
212
+ });
213
+ }
@@ -0,0 +1,3 @@
1
+ import type { CliContext } from './types';
2
+ declare const _default: (mongoshContext: CliContext) => Promise<void>;
3
+ export = _default;