Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/memory-management-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@dexto/agent-management': patch
'dexto': minor
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to patch version

---

Add interactive memory management commands to CLI:

- New `# <content>` command to add memory entries to agent instruction files (AGENTS.md, CLAUDE.md, or GEMINI.md)
- New `/memory` command to view current memory file path
- New `/memory list` command to list all memory entries
- New `/memory remove <number>` command to remove specific memory entries
- Memory entries are stored in a `## Memory` section within the instruction file

1 change: 1 addition & 0 deletions packages/agent-management/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
enrichAgentConfig,
deriveAgentId,
discoverCommandPrompts,
discoverAgentInstructionFile,
type EnrichAgentConfigOptions,
} from './config-enrichment.js';
export { ConfigError } from './errors.js';
Expand Down
1 change: 1 addition & 0 deletions packages/agent-management/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export {
loadAgentConfig,
enrichAgentConfig,
deriveAgentId,
discoverAgentInstructionFile,
addPromptToAgentConfig,
removePromptFromAgentConfig,
deletePromptByMetadata,
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/cli/commands/interactive-commands/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { isDextoAuthEnabled } from '@dexto/agent-management';

// Import modular command definitions
import { generalCommands, createHelpCommand } from './general-commands.js';
import { memoryCommand, handleMemoryAdd } from './memory-command.js';
import { searchCommand, resumeCommand, renameCommand } from './session/index.js';
import { exportCommand } from './export/index.js';
import { modelCommands } from './model/index.js';
Expand Down Expand Up @@ -60,6 +61,9 @@ const baseCommands: CommandDefinition[] = [
// General commands (without help)
...generalCommands,

// Memory command - show loaded memory file
memoryCommand, // /memory - show agent memory file loaded to context

// Session management
searchCommand, // /search - opens search overlay
resumeCommand, // /resume - opens session selector overlay
Expand Down Expand Up @@ -112,6 +116,12 @@ export async function executeCommand(
// Create command context with sessionId
const ctx = { sessionId: sessionId ?? null };

// Handle memory-add command (triggered by # prefix)
if (command === 'memory-add') {
const content = args[0] ?? '';
return await handleMemoryAdd(content);
}

// Find the command (including aliases)
const cmd = CLI_COMMANDS.find(
(c) => c.name === command || (c.aliases && c.aliases.includes(command))
Expand Down
170 changes: 170 additions & 0 deletions packages/cli/src/cli/commands/interactive-commands/memory-command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import chalk from 'chalk';
import type { DextoAgent } from '@dexto/core';
import type { CommandDefinition, CommandHandlerResult, CommandContext } from './command-parser.js';
import { formatForInkCli } from './utils/format-output.js';
import { addMemoryEntry, listMemoryEntries, removeMemoryEntry } from './memory-utils.js';

/**
* Handler for /memory show (shows both project and global)
*/
async function handleShowCommand(): Promise<string> {
const { project, global } = listMemoryEntries();
const lines: string[] = [];

lines.push(chalk.bold('\n📝 Memory Entries:\n'));

// Global Section
lines.push(chalk.bold.magenta('User Memory (Global):'));
if (global.filePath) {
lines.push(chalk.dim(` Path: ${global.filePath}`));
}
if (global.entries.length === 0) {
lines.push(chalk.dim(' (No entries yet)'));
} else {
global.entries.forEach((entry, index) => {
lines.push(chalk.cyan(` ${index + 1}. `) + entry);
});
}
lines.push('');

// Project Section
lines.push(chalk.bold.cyan('Project Memory:'));
if (project.filePath) {
lines.push(chalk.dim(` Path: ${project.filePath}`));
}
if (project.entries.length === 0) {
lines.push(chalk.dim(' (No entries yet)'));
} else {
project.entries.forEach((entry, index) => {
lines.push(chalk.cyan(` ${index + 1}. `) + entry);
});
}

lines.push(chalk.dim('\nQuick remove:'));
lines.push(chalk.dim(' /memory remove <number> # Remove from project'));
lines.push(chalk.dim(' /memory remove <number> --global # Remove from global'));
lines.push(chalk.dim(' /memory remove global <number> # Remove from global'));
lines.push(chalk.dim('\nOr use: /memory remove (interactive wizard)'));

return formatForInkCli(lines.join('\n'));
}

/**
* Handler for /memory remove with power user shortcuts
* Syntax:
* /memory remove → Interactive wizard
* /memory remove <number> → Remove from project
* /memory remove <number> --global → Remove from global
* /memory remove global <number> → Remove from global
*/
async function handleRemoveCommand(args: string[]): Promise<CommandHandlerResult> {
Comment on lines +53 to +60
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need power user shortcuts like /memory remove, let interactive command handle all of this

can remove any unnecessary code from these files

// No args → trigger wizard
if (args.length === 0) {
return {
__triggerOverlay: 'memory-remove-wizard',
} as any;
}

// Parse arguments
let scope: 'project' | 'global' = 'project';
let indexStr: string | undefined;

// Check for "global" as first arg: /memory remove global 3
if (args[0] === 'global') {
scope = 'global';
indexStr = args[1];
} else {
indexStr = args[0];
// Check for --global flag: /memory remove 3 --global
if (args.includes('--global')) {
scope = 'global';
}
}

// Validate index
if (!indexStr) {
return formatForInkCli(chalk.red('\n❌ Missing entry number'));
}

const index = parseInt(indexStr, 10) - 1; // Convert to 0-based index

if (isNaN(index)) {
return formatForInkCli(chalk.red('\n❌ Invalid entry number'));
}

// Remove the entry
const result = removeMemoryEntry(index, scope);

if (result.success) {
const scopeLabel = scope === 'global' ? 'User (global)' : 'Project';
return formatForInkCli(
chalk.green(`\n✓ ${scopeLabel} memory entry removed`) +
chalk.dim(`\nFile: ${result.filePath}`)
);
} else {
return formatForInkCli(chalk.red(`\n❌ Failed to remove entry: ${result.error}`));
}
}

/**
* Handler for # <content> - DEPRECATED: Now handled via /memory add
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can remove if unused

* This remains for internal use if needed but prefix # is removed from parser.
*/
export async function handleMemoryAdd(
content: string,
scope: 'project' | 'global' = 'project'
): Promise<string> {
if (!content || content.trim() === '') {
return formatForInkCli(chalk.yellow('\n⚠ No content provided'));
}

const result = addMemoryEntry(content, scope);

if (result.success) {
return formatForInkCli(
chalk.green(`\n✓ ${scope === 'global' ? 'Global' : 'Project'} memory entry added`) +
chalk.dim(`\nFile: ${result.filePath}\n`) +
chalk.dim('View all entries with: /memory show')
);
} else {
return formatForInkCli(chalk.red(`\n❌ Failed to add memory: ${result.error}`));
}
}

export const memoryCommand: CommandDefinition = {
name: 'memory',
description: 'Manage agent memory (interactive menu)',
usage: '/memory [show|add|remove [<number>] [--global]]',
category: 'General',
aliases: ['mem'],
handler: async (
args: string[],
_agent: DextoAgent,
_ctx: CommandContext
): Promise<CommandHandlerResult> => {
const subcommand = args[0]?.toLowerCase();

// Handle subcommands
if (subcommand === 'show') {
return handleShowCommand();
}

if (subcommand === 'remove' || subcommand === 'rm') {
return handleRemoveCommand(args.slice(1));
}

if (subcommand === 'add') {
// If argument is provided, we can jump straight to scope selection (handled by wizard)
// But for now, just trigger the overlay which handles everything
return {
__triggerOverlay: 'memory-add-wizard',
args: args.slice(1),
} as any;
}

// Default: trigger interactive MemoryManager overlay
return {
__triggerOverlay: 'memory-manager',
} as any;
},
};
Loading