@yeseh/cortex-cli 0.6.8 → 0.6.10
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/dist/program.js +1538 -5
- package/dist/program.js.map +32 -3
- package/dist/run.d.ts +0 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +3 -4
- package/dist/run.js.map +3 -3
- package/package.json +4 -6
- package/dist/chunk-dsfj4baj.js +0 -1543
- package/dist/chunk-dsfj4baj.js.map +0 -38
- package/src/category/commands/create.spec.ts +0 -139
- package/src/category/commands/create.ts +0 -119
- package/src/category/index.ts +0 -24
- package/src/commands/init.spec.ts +0 -203
- package/src/commands/init.ts +0 -301
- package/src/context.spec.ts +0 -60
- package/src/context.ts +0 -170
- package/src/errors.spec.ts +0 -264
- package/src/errors.ts +0 -105
- package/src/memory/commands/add.spec.ts +0 -169
- package/src/memory/commands/add.ts +0 -158
- package/src/memory/commands/definitions.spec.ts +0 -80
- package/src/memory/commands/list.spec.ts +0 -123
- package/src/memory/commands/list.ts +0 -269
- package/src/memory/commands/move.spec.ts +0 -85
- package/src/memory/commands/move.ts +0 -119
- package/src/memory/commands/remove.spec.ts +0 -79
- package/src/memory/commands/remove.ts +0 -108
- package/src/memory/commands/show.spec.ts +0 -71
- package/src/memory/commands/show.ts +0 -165
- package/src/memory/commands/test-helpers.spec.ts +0 -127
- package/src/memory/commands/update.spec.ts +0 -86
- package/src/memory/commands/update.ts +0 -230
- package/src/memory/index.spec.ts +0 -59
- package/src/memory/index.ts +0 -44
- package/src/memory/parsing.spec.ts +0 -105
- package/src/memory/parsing.ts +0 -22
- package/src/observability.spec.ts +0 -126
- package/src/observability.ts +0 -82
- package/src/output.spec.ts +0 -835
- package/src/output.ts +0 -119
- package/src/program.spec.ts +0 -46
- package/src/program.ts +0 -75
- package/src/run.spec.ts +0 -31
- package/src/run.ts +0 -9
- package/src/store/commands/add.spec.ts +0 -131
- package/src/store/commands/add.ts +0 -231
- package/src/store/commands/init.spec.ts +0 -220
- package/src/store/commands/init.ts +0 -272
- package/src/store/commands/list.spec.ts +0 -175
- package/src/store/commands/list.ts +0 -102
- package/src/store/commands/prune.spec.ts +0 -120
- package/src/store/commands/prune.ts +0 -152
- package/src/store/commands/reindexs.spec.ts +0 -94
- package/src/store/commands/reindexs.ts +0 -97
- package/src/store/commands/remove.spec.ts +0 -97
- package/src/store/commands/remove.ts +0 -189
- package/src/store/index.spec.ts +0 -60
- package/src/store/index.ts +0 -49
- package/src/store/utils/resolve-store-name.spec.ts +0 -62
- package/src/store/utils/resolve-store-name.ts +0 -79
- package/src/test-helpers.spec.ts +0 -430
- package/src/tests/cli.integration.spec.ts +0 -1306
- package/src/toon.spec.ts +0 -183
- package/src/toon.ts +0 -462
- package/src/utils/git.spec.ts +0 -95
- package/src/utils/git.ts +0 -51
- package/src/utils/input.spec.ts +0 -326
- package/src/utils/input.ts +0 -150
- package/src/utils/paths.spec.ts +0 -235
- package/src/utils/paths.ts +0 -75
- package/src/utils/prompts.spec.ts +0 -23
- package/src/utils/prompts.ts +0 -88
- package/src/utils/resolve-default-store.spec.ts +0 -135
- package/src/utils/resolve-default-store.ts +0 -74
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory list command for browsing memories with optional filtering.
|
|
3
|
-
*
|
|
4
|
-
* Lists all memories in a category, or all memories across all categories
|
|
5
|
-
* if no category is specified. Expired memories are excluded by default
|
|
6
|
-
* unless the `--include-expired` flag is provided.
|
|
7
|
-
*
|
|
8
|
-
* When no category is specified, the command dynamically discovers all
|
|
9
|
-
* root categories from the store's index rather than using a hardcoded list.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```bash
|
|
13
|
-
* # List all memories
|
|
14
|
-
* cortex memory list
|
|
15
|
-
*
|
|
16
|
-
* # List memories in a specific category
|
|
17
|
-
* cortex memory list project/cortex
|
|
18
|
-
*
|
|
19
|
-
* # Include expired memories
|
|
20
|
-
* cortex memory list --include-expired
|
|
21
|
-
*
|
|
22
|
-
* # Output in JSON format
|
|
23
|
-
* cortex memory list --format json
|
|
24
|
-
*
|
|
25
|
-
* # Use a specific store (either placement works)
|
|
26
|
-
* cortex memory list -s my-store
|
|
27
|
-
* cortex memory -s my-store list
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
import { Command } from '@commander-js/extra-typings';
|
|
32
|
-
import { throwCliError } from '../../errors.ts';
|
|
33
|
-
|
|
34
|
-
import { CategoryPath, type CategoryClient } from '@yeseh/cortex-core/category';
|
|
35
|
-
import { serialize, type CortexContext } from '@yeseh/cortex-core';
|
|
36
|
-
import type { SubcategoryEntry } from '@yeseh/cortex-core/category';
|
|
37
|
-
import { type OutputFormat } from '../../output.ts';
|
|
38
|
-
import { createCliCommandContext } from '../../context.ts';
|
|
39
|
-
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Options for the list command.
|
|
43
|
-
*/
|
|
44
|
-
export interface ListCommandOptions {
|
|
45
|
-
/** Include expired memories in the output */
|
|
46
|
-
includeExpired?: boolean;
|
|
47
|
-
/** Output format (yaml, json, toon) */
|
|
48
|
-
format?: string;
|
|
49
|
-
/** Store name (can be specified on subcommand or parent) */
|
|
50
|
-
store?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Dependencies for the list command handler.
|
|
55
|
-
* Allows injection for testing.
|
|
56
|
-
*/
|
|
57
|
-
export interface ListHandlerDeps {
|
|
58
|
-
/** Output stream for writing results (defaults to process.stdout) */
|
|
59
|
-
stdout?: NodeJS.WritableStream;
|
|
60
|
-
/** Current time for expiration checks */
|
|
61
|
-
now?: Date;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Entry representing a memory in the list output.
|
|
66
|
-
*/
|
|
67
|
-
export interface ListMemoryEntry {
|
|
68
|
-
path: string;
|
|
69
|
-
tokenEstimate: number;
|
|
70
|
-
summary?: string;
|
|
71
|
-
expiresAt?: Date;
|
|
72
|
-
isExpired: boolean;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Entry representing a subcategory in the list output.
|
|
77
|
-
*/
|
|
78
|
-
export interface ListSubcategoryEntry {
|
|
79
|
-
path: string;
|
|
80
|
-
memoryCount: number;
|
|
81
|
-
description?: string;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Result of the list command containing memories and subcategories.
|
|
86
|
-
*/
|
|
87
|
-
export interface ListResult {
|
|
88
|
-
memories: ListMemoryEntry[];
|
|
89
|
-
subcategories: ListSubcategoryEntry[];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Handles the list command execution.
|
|
94
|
-
*
|
|
95
|
-
* This function:
|
|
96
|
-
* 1. Resolves the store context
|
|
97
|
-
* 2. Loads category index (or all categories if none specified)
|
|
98
|
-
* 3. Collects memories and subcategories, filtering expired if needed
|
|
99
|
-
* 4. Formats and outputs the result
|
|
100
|
-
*
|
|
101
|
-
* @param category - Optional category path to list (lists all if omitted)
|
|
102
|
-
* @param options - Command options (includeExpired, format)
|
|
103
|
-
* @param storeName - Optional store name from parent command
|
|
104
|
-
* @param deps - Optional dependencies for testing
|
|
105
|
-
* @throws {InvalidArgumentError} When arguments are invalid
|
|
106
|
-
* @throws {CommanderError} When read or parse fails
|
|
107
|
-
*/
|
|
108
|
-
export async function handleList(
|
|
109
|
-
ctx: CortexContext,
|
|
110
|
-
storeName: string | undefined,
|
|
111
|
-
category: string | undefined,
|
|
112
|
-
options: ListCommandOptions,
|
|
113
|
-
deps: ListHandlerDeps = {}
|
|
114
|
-
): Promise<void> {
|
|
115
|
-
const categoryResult = CategoryPath.fromString(category ?? '');
|
|
116
|
-
if (!categoryResult.ok()) {
|
|
117
|
-
throwCliError(categoryResult.error);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
121
|
-
if (!storeResult.ok()) {
|
|
122
|
-
throwCliError(storeResult.error);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const rootResult = storeResult.value.root();
|
|
126
|
-
if (!rootResult.ok()) {
|
|
127
|
-
throwCliError(rootResult.error);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const root = rootResult.value;
|
|
131
|
-
let categoryClient: CategoryClient;
|
|
132
|
-
|
|
133
|
-
if (categoryResult.value.isRoot) {
|
|
134
|
-
categoryClient = root;
|
|
135
|
-
} else {
|
|
136
|
-
const categoryClientResult = root.getCategory(categoryResult.value.toString());
|
|
137
|
-
if (!categoryClientResult.ok()) {
|
|
138
|
-
throwCliError(categoryClientResult.error);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
categoryClient = categoryClientResult.value;
|
|
142
|
-
}
|
|
143
|
-
const now = deps.now ?? ctx.now();
|
|
144
|
-
const includeExpired = options.includeExpired ?? false;
|
|
145
|
-
const visited = new Set<string>();
|
|
146
|
-
|
|
147
|
-
const collectMemories = async (client: CategoryClient): Promise<ListMemoryEntry[]> => {
|
|
148
|
-
const categoryKey = client.rawPath;
|
|
149
|
-
if (visited.has(categoryKey)) {
|
|
150
|
-
return [];
|
|
151
|
-
}
|
|
152
|
-
visited.add(categoryKey);
|
|
153
|
-
|
|
154
|
-
const memoriesResult = await client.listMemories({ includeExpired });
|
|
155
|
-
if (!memoriesResult.ok()) {
|
|
156
|
-
throwCliError(memoriesResult.error);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const memories: ListMemoryEntry[] = [];
|
|
160
|
-
for (const entry of memoriesResult.value) {
|
|
161
|
-
const memoryClient = client.getMemory(entry.path.slug.toString());
|
|
162
|
-
const memoryResult = await memoryClient.get({ includeExpired: true, now });
|
|
163
|
-
if (!memoryResult.ok()) {
|
|
164
|
-
if (memoryResult.error.code === 'MEMORY_NOT_FOUND') {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
if (!includeExpired && memoryResult.error.code === 'MEMORY_EXPIRED') {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
throwCliError(memoryResult.error);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const memory = memoryResult.value;
|
|
174
|
-
const isExpired = memory.isExpired(now);
|
|
175
|
-
if (!includeExpired && isExpired) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
memories.push({
|
|
180
|
-
path: entry.path.toString(),
|
|
181
|
-
tokenEstimate: entry.tokenEstimate,
|
|
182
|
-
summary: undefined,
|
|
183
|
-
expiresAt: memory.metadata.expiresAt,
|
|
184
|
-
isExpired,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const subcategoriesResult = await client.listSubcategories();
|
|
189
|
-
if (!subcategoriesResult.ok()) {
|
|
190
|
-
throwCliError(subcategoriesResult.error);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
for (const subcategory of subcategoriesResult.value) {
|
|
194
|
-
const subcategoryClientResult = root.getCategory(subcategory.path.toString());
|
|
195
|
-
if (!subcategoryClientResult.ok()) {
|
|
196
|
-
throwCliError(subcategoryClientResult.error);
|
|
197
|
-
}
|
|
198
|
-
const subMemories = await collectMemories(subcategoryClientResult.value);
|
|
199
|
-
memories.push(...subMemories);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return memories;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const subcategoriesResult = await categoryClient.listSubcategories();
|
|
206
|
-
if (!subcategoriesResult.ok()) {
|
|
207
|
-
throwCliError(subcategoriesResult.error);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const memories = await collectMemories(categoryClient);
|
|
211
|
-
const result: ListResult = {
|
|
212
|
-
memories,
|
|
213
|
-
subcategories: subcategoriesResult.value.map((subcategory: SubcategoryEntry) => ({
|
|
214
|
-
path: subcategory.path.toString(),
|
|
215
|
-
memoryCount: subcategory.memoryCount,
|
|
216
|
-
description: subcategory.description,
|
|
217
|
-
})),
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
// 3. Format and output
|
|
221
|
-
const validFormats = ['yaml', 'json', 'toon'] as const;
|
|
222
|
-
const format: OutputFormat = validFormats.includes(options.format as OutputFormat)
|
|
223
|
-
? (options.format as OutputFormat)
|
|
224
|
-
: 'yaml';
|
|
225
|
-
const output = serialize(result, format);
|
|
226
|
-
if (!output.ok()) {
|
|
227
|
-
throwCliError({ code: 'SERIALIZE_FAILED', message: output.error.message });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const out = deps.stdout ?? ctx.stdout;
|
|
231
|
-
out.write(output.value + '\n');
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* The `list` subcommand for browsing memories.
|
|
236
|
-
*
|
|
237
|
-
* Lists memories in a category, or all memories across all root categories
|
|
238
|
-
* if no category is specified. By default, expired memories are excluded.
|
|
239
|
-
*
|
|
240
|
-
* The `--store` option can be specified either on this command or on the
|
|
241
|
-
* parent `memory` command for flexibility.
|
|
242
|
-
*
|
|
243
|
-
* @example
|
|
244
|
-
* ```bash
|
|
245
|
-
* cortex memory list
|
|
246
|
-
* cortex memory list project/cortex
|
|
247
|
-
* cortex memory list --include-expired
|
|
248
|
-
* cortex memory list --format json
|
|
249
|
-
* cortex memory list -s my-store
|
|
250
|
-
* cortex memory -s my-store list
|
|
251
|
-
* ```
|
|
252
|
-
*/
|
|
253
|
-
export const listCommand = new Command('list')
|
|
254
|
-
.description('List memories in a category')
|
|
255
|
-
.argument('[category]', 'Category path to list (lists all if omitted)')
|
|
256
|
-
.option('-s, --store <name>', 'Use a specific named store')
|
|
257
|
-
.option('-x, --include-expired', 'Include expired memories')
|
|
258
|
-
.option('-o, --format <format>', 'Output format (yaml, json, toon)', 'yaml')
|
|
259
|
-
.action(async (category, options, command) => {
|
|
260
|
-
const parentOpts = command.parent?.opts() as { store?: string } | undefined;
|
|
261
|
-
// Allow store to be specified on either the subcommand or parent command
|
|
262
|
-
const storeName = options.store ?? parentOpts?.store;
|
|
263
|
-
const context = await createCliCommandContext();
|
|
264
|
-
if (!context.ok()) {
|
|
265
|
-
throwCliError(context.error);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
await handleList(context.value, storeName, category, options);
|
|
269
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
2
|
-
import { CommanderError, InvalidArgumentError } from '@commander-js/extra-typings';
|
|
3
|
-
import { mkdtemp, rm } from 'node:fs/promises';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { MemoryPath, ok } from '@yeseh/cortex-core';
|
|
7
|
-
|
|
8
|
-
import { handleMove } from './move.ts';
|
|
9
|
-
import {
|
|
10
|
-
createCaptureStream,
|
|
11
|
-
createMemoryCommandContext,
|
|
12
|
-
createMemoryFixture,
|
|
13
|
-
createMockMemoryCommandAdapter,
|
|
14
|
-
} from './test-helpers.spec.ts';
|
|
15
|
-
|
|
16
|
-
describe('handleMove', () => {
|
|
17
|
-
let tempDir: string;
|
|
18
|
-
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
tempDir = await mkdtemp(join(tmpdir(), 'cortex-cli-memory-move-'));
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(async () => {
|
|
24
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should throw InvalidArgumentError for invalid memory paths', async () => {
|
|
28
|
-
const ctx = createMemoryCommandContext({
|
|
29
|
-
adapter: createMockMemoryCommandAdapter(),
|
|
30
|
-
storePath: tempDir,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
await expect(handleMove(ctx, undefined, 'invalid', 'project/two')).rejects.toThrow(
|
|
34
|
-
InvalidArgumentError,
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should throw CommanderError when store is missing', async () => {
|
|
39
|
-
const ctx = createMemoryCommandContext({
|
|
40
|
-
adapter: createMockMemoryCommandAdapter(),
|
|
41
|
-
storePath: tempDir,
|
|
42
|
-
stores: undefined,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
await expect(handleMove(ctx, 'missing-store', 'project/one', 'project/two')).rejects.toThrow(
|
|
46
|
-
CommanderError,
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should move memory and report output', async () => {
|
|
51
|
-
const memory = createMemoryFixture('project/from');
|
|
52
|
-
const moveCalls: { from: MemoryPath; to: MemoryPath }[] = [];
|
|
53
|
-
|
|
54
|
-
const adapter = createMockMemoryCommandAdapter({
|
|
55
|
-
memories: {
|
|
56
|
-
load: async (path: MemoryPath) => {
|
|
57
|
-
if (path.toString() === 'project/from') {
|
|
58
|
-
return ok(memory);
|
|
59
|
-
}
|
|
60
|
-
return ok(null);
|
|
61
|
-
},
|
|
62
|
-
move: async (from: MemoryPath, to: MemoryPath) => {
|
|
63
|
-
moveCalls.push({ from, to });
|
|
64
|
-
return ok(undefined);
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const capture = createCaptureStream();
|
|
70
|
-
const ctx = createMemoryCommandContext({
|
|
71
|
-
adapter,
|
|
72
|
-
storePath: tempDir,
|
|
73
|
-
stdout: capture.stream,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
await handleMove(ctx, undefined, 'project/from', 'project/to');
|
|
77
|
-
|
|
78
|
-
expect(moveCalls).toHaveLength(1);
|
|
79
|
-
if (moveCalls[0]) {
|
|
80
|
-
expect(moveCalls[0].from.toString()).toBe('project/from');
|
|
81
|
-
expect(moveCalls[0].to.toString()).toBe('project/to');
|
|
82
|
-
}
|
|
83
|
-
expect(capture.getOutput()).toContain('Moved memory project/from to project/to');
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory move command implementation using Commander.js.
|
|
3
|
-
*
|
|
4
|
-
* Moves a memory from one path to another within the same store.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```bash
|
|
8
|
-
* # Move a memory to a new location
|
|
9
|
-
* cortex memory move project/old-name project/new-name
|
|
10
|
-
*
|
|
11
|
-
* # Move with explicit store
|
|
12
|
-
* cortex memory --store my-store move project/old project/new
|
|
13
|
-
* ```\n */
|
|
14
|
-
|
|
15
|
-
import { Command } from '@commander-js/extra-typings';
|
|
16
|
-
import { throwCliError } from '../../errors.ts';
|
|
17
|
-
import { MemoryPath, type CortexContext } from '@yeseh/cortex-core';
|
|
18
|
-
import { createCliCommandContext } from '../../context.ts';
|
|
19
|
-
import { serializeOutput, type OutputFormat } from '../../output.ts';
|
|
20
|
-
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
21
|
-
|
|
22
|
-
/** Options for the move command. */
|
|
23
|
-
export interface MoveCommandOptions {
|
|
24
|
-
/** Output format (yaml, json, toon) */
|
|
25
|
-
format?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Handler for the memory move command.
|
|
30
|
-
* Exported for direct testing without Commander parsing.
|
|
31
|
-
*
|
|
32
|
-
* @param ctx - CLI context containing Cortex client and streams
|
|
33
|
-
* @param storeName - Optional store name from parent command
|
|
34
|
-
* @param from - Source memory path
|
|
35
|
-
* @param to - Destination memory path
|
|
36
|
-
*/
|
|
37
|
-
export async function handleMove(
|
|
38
|
-
ctx: CortexContext,
|
|
39
|
-
storeName: string | undefined,
|
|
40
|
-
from: string,
|
|
41
|
-
to: string,
|
|
42
|
-
options: MoveCommandOptions = {}
|
|
43
|
-
): Promise<void> {
|
|
44
|
-
const fromResult = MemoryPath.fromString(from);
|
|
45
|
-
if (!fromResult.ok()) {
|
|
46
|
-
throwCliError(fromResult.error);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const toResult = MemoryPath.fromString(to);
|
|
50
|
-
if (!toResult.ok()) {
|
|
51
|
-
throwCliError(toResult.error);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
55
|
-
if (!storeResult.ok()) {
|
|
56
|
-
throwCliError(storeResult.error);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const store = storeResult.value;
|
|
60
|
-
const rootResult = store.root();
|
|
61
|
-
if (!rootResult.ok()) {
|
|
62
|
-
throwCliError(rootResult.error);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const sourceCategoryResult = fromResult.value.category.isRoot
|
|
66
|
-
? rootResult
|
|
67
|
-
: rootResult.value.getCategory(fromResult.value.category.toString());
|
|
68
|
-
if (!sourceCategoryResult.ok()) {
|
|
69
|
-
throwCliError(sourceCategoryResult.error);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const sourceMemory = sourceCategoryResult.value.getMemory(fromResult.value.slug.toString());
|
|
73
|
-
const moveResult = await sourceMemory.move(toResult.value);
|
|
74
|
-
if (!moveResult.ok()) {
|
|
75
|
-
throwCliError(moveResult.error);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const out = ctx.stdout ?? process.stdout;
|
|
79
|
-
const rawFormat = options.format;
|
|
80
|
-
if (!rawFormat) {
|
|
81
|
-
out.write(`Moved memory ${fromResult.value.toString()} to ${toResult.value.toString()}.\n`);
|
|
82
|
-
} else {
|
|
83
|
-
const format = rawFormat as OutputFormat;
|
|
84
|
-
const serialized = serializeOutput(
|
|
85
|
-
{
|
|
86
|
-
kind: 'moved-memory',
|
|
87
|
-
value: { from: fromResult.value.toString(), to: toResult.value.toString() },
|
|
88
|
-
},
|
|
89
|
-
format
|
|
90
|
-
);
|
|
91
|
-
if (!serialized.ok()) {
|
|
92
|
-
throwCliError({ code: 'SERIALIZE_FAILED', message: serialized.error.message });
|
|
93
|
-
}
|
|
94
|
-
out.write(serialized.value + '\n');
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* The `memory move` subcommand.
|
|
100
|
-
*
|
|
101
|
-
* Moves a memory from one path to another within the store.
|
|
102
|
-
* Both paths must be valid memory slug paths.
|
|
103
|
-
*
|
|
104
|
-
* The `--store` option is inherited from the parent `memory` command.
|
|
105
|
-
*/
|
|
106
|
-
export const moveCommand = new Command('move')
|
|
107
|
-
.description('Move a memory to a new path')
|
|
108
|
-
.argument('<from>', 'Source memory path')
|
|
109
|
-
.argument('<to>', 'Destination memory path')
|
|
110
|
-
.option('-o, --format <format>', 'Output format (yaml, json, toon)')
|
|
111
|
-
.action(async (from, to, options, command) => {
|
|
112
|
-
const parentOpts = command.parent?.opts() as { store?: string } | undefined;
|
|
113
|
-
const context = await createCliCommandContext();
|
|
114
|
-
if (!context.ok()) {
|
|
115
|
-
throwCliError(context.error);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
await handleMove(context.value, parentOpts?.store, from, to, options);
|
|
119
|
-
});
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
2
|
-
import { CommanderError, InvalidArgumentError } from '@commander-js/extra-typings';
|
|
3
|
-
import { mkdtemp, rm } from 'node:fs/promises';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { MemoryPath, ok } from '@yeseh/cortex-core';
|
|
7
|
-
|
|
8
|
-
import { handleRemove } from './remove.ts';
|
|
9
|
-
import {
|
|
10
|
-
createCaptureStream,
|
|
11
|
-
createMemoryCommandContext,
|
|
12
|
-
createMemoryFixture,
|
|
13
|
-
createMockMemoryCommandAdapter,
|
|
14
|
-
} from './test-helpers.spec.ts';
|
|
15
|
-
|
|
16
|
-
describe('handleRemove', () => {
|
|
17
|
-
let tempDir: string;
|
|
18
|
-
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
tempDir = await mkdtemp(join(tmpdir(), 'cortex-cli-memory-remove-'));
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(async () => {
|
|
24
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should throw InvalidArgumentError for invalid memory paths', async () => {
|
|
28
|
-
const ctx = createMemoryCommandContext({
|
|
29
|
-
adapter: createMockMemoryCommandAdapter(),
|
|
30
|
-
storePath: tempDir,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
await expect(handleRemove(ctx, undefined, 'invalid')).rejects.toThrow(
|
|
34
|
-
InvalidArgumentError,
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should throw CommanderError when store is missing', async () => {
|
|
39
|
-
const ctx = createMemoryCommandContext({
|
|
40
|
-
adapter: createMockMemoryCommandAdapter(),
|
|
41
|
-
storePath: tempDir,
|
|
42
|
-
stores: undefined,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
await expect(handleRemove(ctx, 'missing-store', 'project/one')).rejects.toThrow(
|
|
46
|
-
CommanderError,
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should remove memory and report output', async () => {
|
|
51
|
-
const memory = createMemoryFixture('project/remove');
|
|
52
|
-
const removeCalls: MemoryPath[] = [];
|
|
53
|
-
|
|
54
|
-
const adapter = createMockMemoryCommandAdapter({
|
|
55
|
-
memories: {
|
|
56
|
-
load: async () => ok(memory),
|
|
57
|
-
remove: async (path: MemoryPath) => {
|
|
58
|
-
removeCalls.push(path);
|
|
59
|
-
return ok(undefined);
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const capture = createCaptureStream();
|
|
65
|
-
const ctx = createMemoryCommandContext({
|
|
66
|
-
adapter,
|
|
67
|
-
storePath: tempDir,
|
|
68
|
-
stdout: capture.stream,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
await handleRemove(ctx, undefined, 'project/remove');
|
|
72
|
-
|
|
73
|
-
expect(removeCalls).toHaveLength(1);
|
|
74
|
-
if (removeCalls[0]) {
|
|
75
|
-
expect(removeCalls[0].toString()).toBe('project/remove');
|
|
76
|
-
}
|
|
77
|
-
expect(capture.getOutput()).toContain('Removed memory project/remove.');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory remove command implementation using Commander.js.
|
|
3
|
-
*
|
|
4
|
-
* Deletes an existing memory at the specified path.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```bash
|
|
8
|
-
* # Remove a memory
|
|
9
|
-
* cortex memory remove project/tech-stack
|
|
10
|
-
*
|
|
11
|
-
* # Remove memory from a specific store
|
|
12
|
-
* cortex memory --store work remove project/notes
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { Command } from '@commander-js/extra-typings';
|
|
17
|
-
import { throwCliError } from '../../errors.ts';
|
|
18
|
-
import { MemoryPath, type CortexContext } from '@yeseh/cortex-core';
|
|
19
|
-
import { createCliCommandContext } from '../../context.ts';
|
|
20
|
-
import { serializeOutput, type OutputFormat } from '../../output.ts';
|
|
21
|
-
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
22
|
-
|
|
23
|
-
/** Options for the remove command. */
|
|
24
|
-
export interface RemoveCommandOptions {
|
|
25
|
-
/** Output format (yaml, json, toon) */
|
|
26
|
-
format?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Handler for the memory remove command.
|
|
31
|
-
* Exported for direct testing without Commander parsing.
|
|
32
|
-
*
|
|
33
|
-
* @param ctx - CLI context containing Cortex client and streams
|
|
34
|
-
* @param storeName - Optional store name from parent command
|
|
35
|
-
* @param path - Memory path to remove (e.g., "project/tech-stack")
|
|
36
|
-
*/
|
|
37
|
-
export async function handleRemove(
|
|
38
|
-
ctx: CortexContext,
|
|
39
|
-
storeName: string | undefined,
|
|
40
|
-
path: string,
|
|
41
|
-
options: RemoveCommandOptions = {}
|
|
42
|
-
): Promise<void> {
|
|
43
|
-
const pathResult = MemoryPath.fromString(path);
|
|
44
|
-
if (!pathResult.ok()) {
|
|
45
|
-
throwCliError(pathResult.error);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
49
|
-
if (!storeResult.ok()) {
|
|
50
|
-
throwCliError(storeResult.error);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const store = storeResult.value;
|
|
54
|
-
const rootResult = store.root();
|
|
55
|
-
if (!rootResult.ok()) {
|
|
56
|
-
throwCliError(rootResult.error);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const categoryResult = pathResult.value.category.isRoot
|
|
60
|
-
? rootResult
|
|
61
|
-
: rootResult.value.getCategory(pathResult.value.category.toString());
|
|
62
|
-
if (!categoryResult.ok()) {
|
|
63
|
-
throwCliError(categoryResult.error);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const memoryClient = categoryResult.value.getMemory(pathResult.value.slug.toString());
|
|
67
|
-
const removeResult = await memoryClient.delete();
|
|
68
|
-
if (!removeResult.ok()) {
|
|
69
|
-
throwCliError(removeResult.error);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const out = ctx.stdout ?? process.stdout;
|
|
73
|
-
const rawFormat = options.format;
|
|
74
|
-
if (!rawFormat) {
|
|
75
|
-
out.write(`Removed memory ${pathResult.value.toString()}.\n`);
|
|
76
|
-
} else {
|
|
77
|
-
const format = rawFormat as OutputFormat;
|
|
78
|
-
const serialized = serializeOutput(
|
|
79
|
-
{ kind: 'path', value: { path: pathResult.value.toString() } },
|
|
80
|
-
format
|
|
81
|
-
);
|
|
82
|
-
if (!serialized.ok()) {
|
|
83
|
-
throwCliError({ code: 'SERIALIZE_FAILED', message: serialized.error.message });
|
|
84
|
-
}
|
|
85
|
-
out.write(serialized.value + '\n');
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* The `memory remove` subcommand.
|
|
91
|
-
*
|
|
92
|
-
* Deletes an existing memory at the specified path.
|
|
93
|
-
*
|
|
94
|
-
* The `--store` option is inherited from the parent `memory` command.
|
|
95
|
-
*/
|
|
96
|
-
export const removeCommand = new Command('remove')
|
|
97
|
-
.description('Delete a memory')
|
|
98
|
-
.argument('<path>', 'Memory path to remove')
|
|
99
|
-
.option('-o, --format <format>', 'Output format (yaml, json, toon)')
|
|
100
|
-
.action(async (path, options, command) => {
|
|
101
|
-
const parentOpts = command.parent?.opts() as { store?: string } | undefined;
|
|
102
|
-
const context = await createCliCommandContext();
|
|
103
|
-
if (!context.ok()) {
|
|
104
|
-
throwCliError(context.error);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
await handleRemove(context.value, parentOpts?.store, path, options);
|
|
108
|
-
});
|