@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.
@@ -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);