@elyracode/coding-agent 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +2 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +148 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -9,7 +9,7 @@ import * as path from "node:path";
|
|
|
9
9
|
import { getProviders, } from "@elyracode/ai";
|
|
10
10
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@elyracode/tui";
|
|
11
11
|
import { spawn, spawnSync } from "child_process";
|
|
12
|
-
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getBlueprintsDir, getDebugLogPath, getDocsPath, getProjectBlueprintsDir, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
12
|
+
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getBlueprintsDir, getDebugLogPath, getDocsPath, getProjectBlueprintsDir, getProjectSnippetsDir, getShareViewerUrl, getSnippetsDir, VERSION, } from "../../config.js";
|
|
13
13
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
14
14
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
15
15
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
@@ -328,6 +328,32 @@ export class InteractiveMode {
|
|
|
328
328
|
}));
|
|
329
329
|
};
|
|
330
330
|
}
|
|
331
|
+
const snippetCommand = slashCommands.find((command) => command.name === "snippet");
|
|
332
|
+
if (snippetCommand) {
|
|
333
|
+
snippetCommand.getArgumentCompletions = (prefix) => {
|
|
334
|
+
const cwd = this.sessionManager.getCwd();
|
|
335
|
+
const dirs = [getProjectSnippetsDir(cwd), getSnippetsDir()];
|
|
336
|
+
const names = new Set();
|
|
337
|
+
for (const dir of dirs) {
|
|
338
|
+
if (!fs.existsSync(dir))
|
|
339
|
+
continue;
|
|
340
|
+
try {
|
|
341
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
342
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
343
|
+
names.add(entry.name.slice(0, -3));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// skip unreadable dirs
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const filtered = Array.from(names).filter((n) => n.toLowerCase().includes(prefix.toLowerCase()));
|
|
352
|
+
if (filtered.length === 0)
|
|
353
|
+
return null;
|
|
354
|
+
return filtered.sort().map((n) => ({ value: n, label: n }));
|
|
355
|
+
};
|
|
356
|
+
}
|
|
331
357
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
332
358
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
333
359
|
name: cmd.name,
|
|
@@ -1964,6 +1990,11 @@ export class InteractiveMode {
|
|
|
1964
1990
|
this.handleHelpCommand();
|
|
1965
1991
|
return;
|
|
1966
1992
|
}
|
|
1993
|
+
if (text === "/init") {
|
|
1994
|
+
this.editor.setText("");
|
|
1995
|
+
await this.handleInitCommand();
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1967
1998
|
if (text === "/settings") {
|
|
1968
1999
|
this.showSettingsSelector();
|
|
1969
2000
|
this.editor.setText("");
|
|
@@ -2004,6 +2035,11 @@ export class InteractiveMode {
|
|
|
2004
2035
|
this.handleBlueprintCommand(text.startsWith("/blueprint ") ? text.slice(11).trim() : undefined);
|
|
2005
2036
|
return;
|
|
2006
2037
|
}
|
|
2038
|
+
if (text === "/snippet" || text.startsWith("/snippet ")) {
|
|
2039
|
+
this.editor.setText("");
|
|
2040
|
+
this.handleSnippetCommand(text.startsWith("/snippet ") ? text.slice(9).trim() : undefined);
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2007
2043
|
if (text === "/footer") {
|
|
2008
2044
|
this.editor.setText("");
|
|
2009
2045
|
this.handleFooterCommand();
|
|
@@ -3322,6 +3358,67 @@ export class InteractiveMode {
|
|
|
3322
3358
|
return { component: selector, focus: selector.getSettingsList() };
|
|
3323
3359
|
});
|
|
3324
3360
|
}
|
|
3361
|
+
async handleInitCommand() {
|
|
3362
|
+
const cwd = this.sessionManager.getCwd();
|
|
3363
|
+
const elyraDir = path.join(cwd, ".elyra");
|
|
3364
|
+
if (fs.existsSync(elyraDir)) {
|
|
3365
|
+
this.showStatus(".elyra/ already exists in this project");
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
// Detect stack
|
|
3369
|
+
const stackResult = detectStack(cwd);
|
|
3370
|
+
// Create .elyra/ directory
|
|
3371
|
+
fs.mkdirSync(elyraDir, { recursive: true });
|
|
3372
|
+
// Create AGENTS.md with project context
|
|
3373
|
+
const agentsLines = [
|
|
3374
|
+
"# Project Rules",
|
|
3375
|
+
"",
|
|
3376
|
+
"## Overview",
|
|
3377
|
+
"",
|
|
3378
|
+
"<!-- Describe your project here. The agent reads this on every session. -->",
|
|
3379
|
+
"",
|
|
3380
|
+
];
|
|
3381
|
+
if (stackResult.summary) {
|
|
3382
|
+
agentsLines.push(`## Stack`, "", stackResult.summary, "");
|
|
3383
|
+
}
|
|
3384
|
+
agentsLines.push("## Conventions", "", "<!-- Add coding conventions, patterns, and preferences here. -->", "");
|
|
3385
|
+
fs.writeFileSync(path.join(elyraDir, "AGENTS.md"), agentsLines.join("\n"), "utf-8");
|
|
3386
|
+
// Create blueprints directory
|
|
3387
|
+
fs.mkdirSync(path.join(elyraDir, "blueprints"), { recursive: true });
|
|
3388
|
+
// Create snippets directory
|
|
3389
|
+
fs.mkdirSync(path.join(elyraDir, "snippets"), { recursive: true });
|
|
3390
|
+
// Build status message
|
|
3391
|
+
const parts = [];
|
|
3392
|
+
parts.push(theme.bold("Project initialized"));
|
|
3393
|
+
parts.push("");
|
|
3394
|
+
parts.push(` ${theme.fg("success", "Created")} .elyra/AGENTS.md`);
|
|
3395
|
+
parts.push(` ${theme.fg("success", "Created")} .elyra/blueprints/`);
|
|
3396
|
+
parts.push(` ${theme.fg("success", "Created")} .elyra/snippets/`);
|
|
3397
|
+
// Suggest extensions based on detected stack
|
|
3398
|
+
if (stackResult.stack) {
|
|
3399
|
+
const pkg = getSuggestedPackage(stackResult.stack);
|
|
3400
|
+
parts.push("");
|
|
3401
|
+
parts.push(` ${theme.fg("accent", "Detected:")} ${stackResult.stack} stack`);
|
|
3402
|
+
parts.push(` ${theme.fg("dim", "Install:")} elyra install npm:${pkg}`);
|
|
3403
|
+
}
|
|
3404
|
+
if (stackResult.frameworks.some((f) => f.name === "Filament")) {
|
|
3405
|
+
parts.push(` ${theme.fg("dim", "Install:")} elyra install npm:@elyracode/stack-filament`);
|
|
3406
|
+
}
|
|
3407
|
+
if (stackResult.frameworks.some((f) => f.name === "Laravel AI SDK")) {
|
|
3408
|
+
parts.push(` ${theme.fg("dim", "Install:")} elyra install npm:@elyracode/stack-laravel-ai`);
|
|
3409
|
+
}
|
|
3410
|
+
// Check for Docker
|
|
3411
|
+
if (fs.existsSync(path.join(cwd, "docker-compose.yml")) || fs.existsSync(path.join(cwd, "compose.yml"))) {
|
|
3412
|
+
parts.push(` ${theme.fg("dim", "Install:")} elyra install npm:@elyracode/docker`);
|
|
3413
|
+
}
|
|
3414
|
+
// Always suggest useful general extensions
|
|
3415
|
+
parts.push("");
|
|
3416
|
+
parts.push(theme.fg("dim", "Edit .elyra/AGENTS.md to add project rules and conventions."));
|
|
3417
|
+
parts.push(theme.fg("dim", "Browse all extensions with /ext."));
|
|
3418
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3419
|
+
this.chatContainer.addChild(new Text(parts.join("\n"), 1, 0));
|
|
3420
|
+
this.ui.requestRender();
|
|
3421
|
+
}
|
|
3325
3422
|
handleHelpCommand() {
|
|
3326
3423
|
const help = [
|
|
3327
3424
|
theme.bold("Elyra"),
|
|
@@ -3550,6 +3647,56 @@ export class InteractiveMode {
|
|
|
3550
3647
|
this.showError(`Failed to read blueprint: ${bpPath}`);
|
|
3551
3648
|
}
|
|
3552
3649
|
}
|
|
3650
|
+
handleSnippetCommand(name) {
|
|
3651
|
+
const cwd = this.sessionManager.getCwd();
|
|
3652
|
+
const dirs = [getProjectSnippetsDir(cwd), getSnippetsDir()];
|
|
3653
|
+
const snippets = new Map();
|
|
3654
|
+
for (const dir of dirs) {
|
|
3655
|
+
if (!fs.existsSync(dir))
|
|
3656
|
+
continue;
|
|
3657
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
3658
|
+
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
3659
|
+
continue;
|
|
3660
|
+
const snipName = entry.name.slice(0, -3);
|
|
3661
|
+
if (!snippets.has(snipName)) {
|
|
3662
|
+
snippets.set(snipName, path.resolve(dir, entry.name));
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
if (!name) {
|
|
3667
|
+
if (snippets.size === 0) {
|
|
3668
|
+
this.showStatus("No snippets found. Add .md files to .elyra/snippets/ or ~/.elyra/agent/snippets/");
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
// Show selector
|
|
3672
|
+
const items = Array.from(snippets.keys()).sort();
|
|
3673
|
+
this.showSelector((done) => {
|
|
3674
|
+
const selector = new ExtensionSelectorComponent("Select snippet", items, (selected) => {
|
|
3675
|
+
done();
|
|
3676
|
+
this.handleSnippetCommand(selected);
|
|
3677
|
+
}, () => done());
|
|
3678
|
+
return { component: selector, focus: selector };
|
|
3679
|
+
});
|
|
3680
|
+
return;
|
|
3681
|
+
}
|
|
3682
|
+
const filePath = snippets.get(name);
|
|
3683
|
+
if (!filePath) {
|
|
3684
|
+
const available = Array.from(snippets.keys()).join(", ");
|
|
3685
|
+
this.showError(`Snippet not found: "${name}". Available: ${available || "none"}`);
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
try {
|
|
3689
|
+
const content = fs.readFileSync(filePath, "utf-8").trim();
|
|
3690
|
+
if (!content) {
|
|
3691
|
+
this.showError(`Snippet "${name}" is empty`);
|
|
3692
|
+
return;
|
|
3693
|
+
}
|
|
3694
|
+
this.session.prompt(content);
|
|
3695
|
+
}
|
|
3696
|
+
catch {
|
|
3697
|
+
this.showError(`Failed to read snippet: ${filePath}`);
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3553
3700
|
handleFooterCommand() {
|
|
3554
3701
|
const current = this.settingsManager.getMinimalFooter();
|
|
3555
3702
|
this.settingsManager.setMinimalFooter(!current);
|