@elyracode/coding-agent 0.5.6 → 0.5.8
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 +14 -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/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +33 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +19 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +7 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +269 -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, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
12
|
+
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getBlueprintsDir, getDebugLogPath, getDocsPath, getProjectBlueprintsDir, getShareViewerUrl, 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";
|
|
@@ -54,6 +54,7 @@ import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
|
54
54
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
55
55
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
56
56
|
import { StartupAnimation } from "./components/startup-animation.js";
|
|
57
|
+
import { ThemeSelectorComponent } from "./components/theme-selector.js";
|
|
57
58
|
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
58
59
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
59
60
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
@@ -310,6 +311,21 @@ export class InteractiveMode {
|
|
|
310
311
|
}));
|
|
311
312
|
};
|
|
312
313
|
}
|
|
314
|
+
const themeCommand = slashCommands.find((command) => command.name === "theme");
|
|
315
|
+
if (themeCommand) {
|
|
316
|
+
themeCommand.getArgumentCompletions = (prefix) => {
|
|
317
|
+
const themes = getAvailableThemes();
|
|
318
|
+
const currentTheme = this.settingsManager.getTheme() || "dark";
|
|
319
|
+
const filtered = themes.filter((t) => t.toLowerCase().includes(prefix.toLowerCase()));
|
|
320
|
+
if (filtered.length === 0)
|
|
321
|
+
return null;
|
|
322
|
+
return filtered.map((t) => ({
|
|
323
|
+
value: t,
|
|
324
|
+
label: t,
|
|
325
|
+
description: t === currentTheme ? "(current)" : undefined,
|
|
326
|
+
}));
|
|
327
|
+
};
|
|
328
|
+
}
|
|
313
329
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
314
330
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
315
331
|
name: cmd.name,
|
|
@@ -1930,6 +1946,42 @@ export class InteractiveMode {
|
|
|
1930
1946
|
this.editor.setText("");
|
|
1931
1947
|
return;
|
|
1932
1948
|
}
|
|
1949
|
+
if (text === "/cost") {
|
|
1950
|
+
this.editor.setText("");
|
|
1951
|
+
this.handleCostCommand();
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (text === "/diff") {
|
|
1955
|
+
this.editor.setText("");
|
|
1956
|
+
await this.handleDiffCommand();
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
if (text === "/pin" || text.startsWith("/pin ")) {
|
|
1960
|
+
this.editor.setText("");
|
|
1961
|
+
this.handlePinCommand(text.startsWith("/pin ") ? text.slice(5).trim() : undefined);
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
if (text === "/unpin" || text.startsWith("/unpin ")) {
|
|
1965
|
+
this.editor.setText("");
|
|
1966
|
+
this.handleUnpinCommand(text.startsWith("/unpin ") ? text.slice(7).trim() : undefined);
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
if (text === "/pins") {
|
|
1970
|
+
this.editor.setText("");
|
|
1971
|
+
this.handlePinsCommand();
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
if (text === "/blueprint" || text.startsWith("/blueprint ")) {
|
|
1975
|
+
this.editor.setText("");
|
|
1976
|
+
this.handleBlueprintCommand(text.startsWith("/blueprint ") ? text.slice(11).trim() : undefined);
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
if (text === "/theme" || text.startsWith("/theme ")) {
|
|
1980
|
+
const themeName = text.startsWith("/theme ") ? text.slice(7).trim() : undefined;
|
|
1981
|
+
this.editor.setText("");
|
|
1982
|
+
this.handleThemeCommand(themeName);
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1933
1985
|
if (text === "/scoped-models") {
|
|
1934
1986
|
this.editor.setText("");
|
|
1935
1987
|
await this.showModelsSelector();
|
|
@@ -3237,6 +3289,222 @@ export class InteractiveMode {
|
|
|
3237
3289
|
return { component: selector, focus: selector.getSettingsList() };
|
|
3238
3290
|
});
|
|
3239
3291
|
}
|
|
3292
|
+
handleCostCommand() {
|
|
3293
|
+
const stats = this.session.getSessionStats();
|
|
3294
|
+
const fmt = (n) => n.toLocaleString();
|
|
3295
|
+
const cost = stats.cost > 0 ? `$${stats.cost.toFixed(4)}` : "$0.00";
|
|
3296
|
+
let info = `${theme.bold("Session Cost")}\n\n`;
|
|
3297
|
+
info += `${theme.fg("dim", "Input:")} ${fmt(stats.tokens.input)} tokens\n`;
|
|
3298
|
+
info += `${theme.fg("dim", "Output:")} ${fmt(stats.tokens.output)} tokens\n`;
|
|
3299
|
+
info += `${theme.fg("dim", "Cache read:")} ${fmt(stats.tokens.cacheRead)} tokens\n`;
|
|
3300
|
+
info += `${theme.fg("dim", "Cache write:")} ${fmt(stats.tokens.cacheWrite)} tokens\n`;
|
|
3301
|
+
info += `${theme.fg("dim", "Total:")} ${fmt(stats.tokens.total)} tokens\n\n`;
|
|
3302
|
+
info += `${theme.fg("dim", "Estimated cost:")} ${theme.fg("accent", cost)}\n`;
|
|
3303
|
+
if (stats.contextUsage) {
|
|
3304
|
+
const ctxTokens = stats.contextUsage.tokens ?? 0;
|
|
3305
|
+
const pct = stats.contextUsage.contextWindow > 0 ? Math.round((ctxTokens / stats.contextUsage.contextWindow) * 100) : 0;
|
|
3306
|
+
info += `${theme.fg("dim", "Context:")} ${fmt(ctxTokens)} / ${fmt(stats.contextUsage.contextWindow)} (${pct}%)`;
|
|
3307
|
+
}
|
|
3308
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3309
|
+
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
3310
|
+
this.ui.requestRender();
|
|
3311
|
+
}
|
|
3312
|
+
async handleDiffCommand() {
|
|
3313
|
+
const cwd = this.sessionManager.getCwd();
|
|
3314
|
+
try {
|
|
3315
|
+
const { execSync } = await import("node:child_process");
|
|
3316
|
+
const diff = execSync("git diff", { cwd, encoding: "utf-8", maxBuffer: 1024 * 1024 });
|
|
3317
|
+
if (!diff.trim()) {
|
|
3318
|
+
this.showStatus("No uncommitted changes");
|
|
3319
|
+
return;
|
|
3320
|
+
}
|
|
3321
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3322
|
+
this.chatContainer.addChild(new DynamicBorder());
|
|
3323
|
+
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Uncommitted Changes")), 1, 0));
|
|
3324
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3325
|
+
this.chatContainer.addChild(new Markdown(`\`\`\`diff\n${diff}\n\`\`\``, 1, 1, this.getMarkdownThemeWithSettings()));
|
|
3326
|
+
this.chatContainer.addChild(new DynamicBorder());
|
|
3327
|
+
this.ui.requestRender();
|
|
3328
|
+
}
|
|
3329
|
+
catch {
|
|
3330
|
+
this.showError("Failed to run git diff. Is this a git repository?");
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
handlePinCommand(filePath) {
|
|
3334
|
+
if (!filePath) {
|
|
3335
|
+
this.showError("Usage: /pin <file-path>");
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
const cwd = this.sessionManager.getCwd();
|
|
3339
|
+
const resolved = path.resolve(cwd, filePath);
|
|
3340
|
+
if (!fs.existsSync(resolved)) {
|
|
3341
|
+
this.showError(`File not found: ${filePath}`);
|
|
3342
|
+
return;
|
|
3343
|
+
}
|
|
3344
|
+
const rel = path.relative(cwd, resolved);
|
|
3345
|
+
this.settingsManager.addPinnedFile(rel);
|
|
3346
|
+
this.showStatus(`Pinned: ${rel}`);
|
|
3347
|
+
}
|
|
3348
|
+
handleUnpinCommand(filePath) {
|
|
3349
|
+
if (!filePath) {
|
|
3350
|
+
this.showError("Usage: /unpin <file-path>");
|
|
3351
|
+
return;
|
|
3352
|
+
}
|
|
3353
|
+
const cwd = this.sessionManager.getCwd();
|
|
3354
|
+
const rel = path.relative(cwd, path.resolve(cwd, filePath));
|
|
3355
|
+
const pinned = this.settingsManager.getPinnedFiles();
|
|
3356
|
+
if (!pinned.includes(rel)) {
|
|
3357
|
+
this.showError(`Not pinned: ${rel}`);
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
3360
|
+
this.settingsManager.removePinnedFile(rel);
|
|
3361
|
+
this.showStatus(`Unpinned: ${rel}`);
|
|
3362
|
+
}
|
|
3363
|
+
handlePinsCommand() {
|
|
3364
|
+
const pinned = this.settingsManager.getPinnedFiles();
|
|
3365
|
+
if (pinned.length === 0) {
|
|
3366
|
+
this.showStatus("No pinned files");
|
|
3367
|
+
return;
|
|
3368
|
+
}
|
|
3369
|
+
let info = `${theme.bold("Pinned Files")}\n\n`;
|
|
3370
|
+
for (const file of pinned) {
|
|
3371
|
+
info += ` ${theme.fg("accent", file)}\n`;
|
|
3372
|
+
}
|
|
3373
|
+
info += `\n${theme.fg("dim", `${pinned.length} file(s) pinned to context`)}`;
|
|
3374
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3375
|
+
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
3376
|
+
this.ui.requestRender();
|
|
3377
|
+
}
|
|
3378
|
+
handleBlueprintCommand(name) {
|
|
3379
|
+
const cwd = this.sessionManager.getCwd();
|
|
3380
|
+
const dirs = [getProjectBlueprintsDir(cwd), getBlueprintsDir()];
|
|
3381
|
+
const blueprints = new Map();
|
|
3382
|
+
for (const dir of dirs) {
|
|
3383
|
+
if (!fs.existsSync(dir))
|
|
3384
|
+
continue;
|
|
3385
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
3386
|
+
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
3387
|
+
continue;
|
|
3388
|
+
const bpName = entry.name.slice(0, -3);
|
|
3389
|
+
if (!blueprints.has(bpName)) {
|
|
3390
|
+
blueprints.set(bpName, path.resolve(dir, entry.name));
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
if (!name) {
|
|
3395
|
+
if (blueprints.size === 0) {
|
|
3396
|
+
this.showStatus("No blueprints found. Add .md files to .elyra/blueprints/ or ~/.elyra/agent/blueprints/");
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
let info = `${theme.bold("Available Blueprints")}\n\n`;
|
|
3400
|
+
for (const [bpName] of blueprints) {
|
|
3401
|
+
info += ` ${theme.fg("accent", bpName)}\n`;
|
|
3402
|
+
}
|
|
3403
|
+
info += `\n${theme.fg("dim", "Usage: /blueprint <name>")}`;
|
|
3404
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3405
|
+
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
3406
|
+
this.ui.requestRender();
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
const bpPath = blueprints.get(name);
|
|
3410
|
+
if (!bpPath) {
|
|
3411
|
+
const available = Array.from(blueprints.keys()).join(", ");
|
|
3412
|
+
this.showError(`Blueprint not found: "${name}". Available: ${available || "none"}`);
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3415
|
+
try {
|
|
3416
|
+
const content = fs.readFileSync(bpPath, "utf-8");
|
|
3417
|
+
// Parse optional YAML frontmatter
|
|
3418
|
+
let body = content;
|
|
3419
|
+
const pins = [];
|
|
3420
|
+
if (content.startsWith("---\n")) {
|
|
3421
|
+
const endIdx = content.indexOf("\n---\n", 4);
|
|
3422
|
+
if (endIdx !== -1) {
|
|
3423
|
+
const frontmatter = content.slice(4, endIdx);
|
|
3424
|
+
body = content.slice(endIdx + 5);
|
|
3425
|
+
for (const line of frontmatter.split("\n")) {
|
|
3426
|
+
const pinMatch = line.match(/^pin:\s*(.+)$/);
|
|
3427
|
+
if (pinMatch) {
|
|
3428
|
+
pins.push(...pinMatch[1]
|
|
3429
|
+
.split(",")
|
|
3430
|
+
.map((s) => s.trim())
|
|
3431
|
+
.filter(Boolean));
|
|
3432
|
+
}
|
|
3433
|
+
const pinsMatch = line.match(/^pins:\s*\[(.+)\]$/);
|
|
3434
|
+
if (pinsMatch) {
|
|
3435
|
+
pins.push(...pinsMatch[1]
|
|
3436
|
+
.split(",")
|
|
3437
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, ""))
|
|
3438
|
+
.filter(Boolean));
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
// Apply pins from frontmatter
|
|
3444
|
+
for (const pin of pins) {
|
|
3445
|
+
const resolved = path.resolve(cwd, pin);
|
|
3446
|
+
if (fs.existsSync(resolved)) {
|
|
3447
|
+
const rel = path.relative(cwd, resolved);
|
|
3448
|
+
this.settingsManager.addPinnedFile(rel);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
// Send blueprint content as user message to the agent
|
|
3452
|
+
this.session.prompt(body.trim());
|
|
3453
|
+
const pinMsg = pins.length > 0 ? ` (pinned ${pins.length} file(s))` : "";
|
|
3454
|
+
this.showStatus(`Blueprint: ${name}${pinMsg}`);
|
|
3455
|
+
}
|
|
3456
|
+
catch {
|
|
3457
|
+
this.showError(`Failed to read blueprint: ${bpPath}`);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
handleThemeCommand(themeName) {
|
|
3461
|
+
if (!themeName) {
|
|
3462
|
+
this.showThemeSelector();
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
const available = getAvailableThemes();
|
|
3466
|
+
if (!available.includes(themeName)) {
|
|
3467
|
+
this.showError(`Unknown theme: "${themeName}". Available: ${available.join(", ")}`);
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
const result = setTheme(themeName, true);
|
|
3471
|
+
this.settingsManager.setTheme(themeName);
|
|
3472
|
+
this.ui.invalidate();
|
|
3473
|
+
if (result.success) {
|
|
3474
|
+
this.showStatus(`Theme: ${themeName}`);
|
|
3475
|
+
}
|
|
3476
|
+
else {
|
|
3477
|
+
this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
showThemeSelector() {
|
|
3481
|
+
const currentTheme = this.settingsManager.getTheme() || "dark";
|
|
3482
|
+
const previousTheme = currentTheme;
|
|
3483
|
+
this.showSelector((done) => {
|
|
3484
|
+
const selector = new ThemeSelectorComponent(currentTheme, (themeName) => {
|
|
3485
|
+
const result = setTheme(themeName, true);
|
|
3486
|
+
this.settingsManager.setTheme(themeName);
|
|
3487
|
+
this.ui.invalidate();
|
|
3488
|
+
done();
|
|
3489
|
+
if (!result.success) {
|
|
3490
|
+
this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
|
|
3491
|
+
}
|
|
3492
|
+
}, () => {
|
|
3493
|
+
// Cancel: restore previous theme
|
|
3494
|
+
setTheme(previousTheme, true);
|
|
3495
|
+
this.ui.invalidate();
|
|
3496
|
+
done();
|
|
3497
|
+
}, (themeName) => {
|
|
3498
|
+
// Preview on hover
|
|
3499
|
+
const result = setTheme(themeName, true);
|
|
3500
|
+
if (result.success) {
|
|
3501
|
+
this.ui.invalidate();
|
|
3502
|
+
this.ui.requestRender();
|
|
3503
|
+
}
|
|
3504
|
+
});
|
|
3505
|
+
return { component: selector, focus: selector.getSelectList() };
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3240
3508
|
async handleModelCommand(searchTerm) {
|
|
3241
3509
|
if (!searchTerm) {
|
|
3242
3510
|
this.showModelSelector();
|