@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
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
|
+
};
|
package/dist/banner.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED