@yeseh/cortex-cli 0.6.8 → 0.6.9
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,71 +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 { ok } from '@yeseh/cortex-core';
|
|
7
|
-
|
|
8
|
-
import { handleShow } from './show.ts';
|
|
9
|
-
import {
|
|
10
|
-
createCaptureStream,
|
|
11
|
-
createMemoryCommandContext,
|
|
12
|
-
createMemoryFixture,
|
|
13
|
-
createMockMemoryCommandAdapter,
|
|
14
|
-
} from './test-helpers.spec.ts';
|
|
15
|
-
|
|
16
|
-
describe('handleShow', () => {
|
|
17
|
-
let tempDir: string;
|
|
18
|
-
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
tempDir = await mkdtemp(join(tmpdir(), 'cortex-cli-memory-show-'));
|
|
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(handleShow(ctx, undefined, 'invalid', { format: 'yaml' })).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(handleShow(ctx, 'missing-store', 'project/one', { format: 'yaml' })).rejects.toThrow(
|
|
46
|
-
CommanderError,
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should output serialized memory details', async () => {
|
|
51
|
-
const memory = createMemoryFixture('project/show', {}, 'Show content');
|
|
52
|
-
const adapter = createMockMemoryCommandAdapter({
|
|
53
|
-
memories: {
|
|
54
|
-
load: async () => ok(memory),
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
const capture = createCaptureStream();
|
|
58
|
-
const ctx = createMemoryCommandContext({
|
|
59
|
-
adapter,
|
|
60
|
-
storePath: tempDir,
|
|
61
|
-
stdout: capture.stream,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
await handleShow(ctx, undefined, 'project/show', { format: 'json' });
|
|
65
|
-
|
|
66
|
-
const output = JSON.parse(capture.getOutput());
|
|
67
|
-
expect(output.value.path).toBe('project/show');
|
|
68
|
-
expect(output.value.content).toBe('Show content');
|
|
69
|
-
expect(output.value.metadata.tags).toEqual(['test']);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory show command for displaying a memory's content and metadata.
|
|
3
|
-
*
|
|
4
|
-
* This command reads a memory from the store and displays it in the
|
|
5
|
-
* specified format. Expired memories are excluded by default unless
|
|
6
|
-
* the `--include-expired` flag is provided.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```bash
|
|
10
|
-
* # Show a memory in YAML format (default)
|
|
11
|
-
* cortex memory show project/notes
|
|
12
|
-
*
|
|
13
|
-
* # Show a memory in JSON format
|
|
14
|
-
* cortex memory show project/notes --format json
|
|
15
|
-
*
|
|
16
|
-
* # Include expired memories
|
|
17
|
-
* cortex memory show project/notes --include-expired
|
|
18
|
-
*
|
|
19
|
-
* # Use a specific store
|
|
20
|
-
* cortex memory --store my-store show project/notes
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { Command } from '@commander-js/extra-typings';
|
|
25
|
-
import { throwCliError } from '../../errors.ts';
|
|
26
|
-
|
|
27
|
-
import { defaultTokenizer, MemoryPath, type CortexContext } from '@yeseh/cortex-core';
|
|
28
|
-
import { type StoreClient } from '@yeseh/cortex-core/store';
|
|
29
|
-
import { serializeOutput, type OutputMemory, type OutputFormat } from '../../output.ts';
|
|
30
|
-
import { createCliCommandContext } from '../../context.ts';
|
|
31
|
-
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Options for the show command.
|
|
35
|
-
*/
|
|
36
|
-
export interface ShowCommandOptions {
|
|
37
|
-
/** Include expired memories in the output */
|
|
38
|
-
includeExpired?: boolean;
|
|
39
|
-
/** Output format (yaml, json, toon) */
|
|
40
|
-
format?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Dependencies for the show command handler.
|
|
45
|
-
* Allows injection for testing.
|
|
46
|
-
*/
|
|
47
|
-
export interface ShowHandlerDeps {
|
|
48
|
-
/** Output stream for writing results (defaults to process.stdout) */
|
|
49
|
-
stdout?: NodeJS.WritableStream;
|
|
50
|
-
/** Pre-resolved store client for testing */
|
|
51
|
-
store?: StoreClient;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Handles the show command execution.
|
|
56
|
-
*
|
|
57
|
-
* This function:
|
|
58
|
-
* 1. Resolves the store context
|
|
59
|
-
* 2. Validates the memory path
|
|
60
|
-
* 3. Reads the memory file from storage
|
|
61
|
-
* 4. Parses the memory content and frontmatter
|
|
62
|
-
* 5. Checks expiration status (unless --include-expired)
|
|
63
|
-
* 6. Serializes and outputs the result
|
|
64
|
-
*
|
|
65
|
-
* @param path - The memory path to show (e.g., "project/notes")
|
|
66
|
-
* @param options - Command options (includeExpired, format)
|
|
67
|
-
* @param storeName - Optional store name from parent command
|
|
68
|
-
* @param deps - Optional dependencies for testing
|
|
69
|
-
* @throws {InvalidArgumentError} When the path is invalid
|
|
70
|
-
* @throws {CommanderError} When the memory is not found or read fails
|
|
71
|
-
*/
|
|
72
|
-
export async function handleShow(
|
|
73
|
-
ctx: CortexContext,
|
|
74
|
-
storeName: string | undefined,
|
|
75
|
-
path: string,
|
|
76
|
-
options: ShowCommandOptions,
|
|
77
|
-
deps: ShowHandlerDeps = {}
|
|
78
|
-
): Promise<void> {
|
|
79
|
-
const pathResult = MemoryPath.fromString(path);
|
|
80
|
-
if (!pathResult.ok()) {
|
|
81
|
-
throwCliError(pathResult.error);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
85
|
-
if (!storeResult.ok()) {
|
|
86
|
-
throwCliError(storeResult.error);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const store = deps.store ?? storeResult.value;
|
|
90
|
-
|
|
91
|
-
const rootResult = store.root();
|
|
92
|
-
if (!rootResult.ok()) {
|
|
93
|
-
throwCliError(rootResult.error);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const categoryResult = pathResult.value.category.isRoot
|
|
97
|
-
? rootResult
|
|
98
|
-
: rootResult.value.getCategory(pathResult.value.category.toString());
|
|
99
|
-
if (!categoryResult.ok()) {
|
|
100
|
-
throwCliError(categoryResult.error);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const memoryClient = categoryResult.value.getMemory(pathResult.value.slug.toString());
|
|
104
|
-
const readResult = await memoryClient.get({
|
|
105
|
-
includeExpired: options.includeExpired ?? false,
|
|
106
|
-
now: ctx.now(),
|
|
107
|
-
});
|
|
108
|
-
if (!readResult.ok()) {
|
|
109
|
-
throwCliError(readResult.error);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const memory = readResult.value;
|
|
113
|
-
const tokenEstimateResult = defaultTokenizer.estimateTokens(memory.content);
|
|
114
|
-
const tokenEstimate = tokenEstimateResult.ok() ? tokenEstimateResult.value : undefined;
|
|
115
|
-
|
|
116
|
-
const outputMemory: OutputMemory = {
|
|
117
|
-
path: memory.path.toString(),
|
|
118
|
-
metadata: {
|
|
119
|
-
createdAt: memory.metadata.createdAt,
|
|
120
|
-
updatedAt: memory.metadata.updatedAt,
|
|
121
|
-
tags: memory.metadata.tags,
|
|
122
|
-
source: memory.metadata.source,
|
|
123
|
-
tokenEstimate,
|
|
124
|
-
expiresAt: memory.metadata.expiresAt,
|
|
125
|
-
},
|
|
126
|
-
content: memory.content,
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const format = (options.format as OutputFormat) ?? 'yaml';
|
|
130
|
-
const serialized = serializeOutput({ kind: 'memory', value: outputMemory }, format);
|
|
131
|
-
if (!serialized.ok()) {
|
|
132
|
-
throwCliError({ code: 'SERIALIZE_FAILED', message: serialized.error.message });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const out = deps.stdout ?? ctx.stdout;
|
|
136
|
-
out.write(serialized.value + '\n');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* The `show` subcommand for displaying a memory.
|
|
141
|
-
*
|
|
142
|
-
* Reads a memory from the store and displays its content and metadata
|
|
143
|
-
* in the specified format. By default, expired memories are excluded.
|
|
144
|
-
*
|
|
145
|
-
* @example
|
|
146
|
-
* ```bash
|
|
147
|
-
* cortex memory show project/notes
|
|
148
|
-
* cortex memory show project/notes --format json
|
|
149
|
-
* cortex memory show project/notes --include-expired
|
|
150
|
-
* ```
|
|
151
|
-
*/
|
|
152
|
-
export const showCommand = new Command('show')
|
|
153
|
-
.description('Display a memory')
|
|
154
|
-
.argument('<path>', 'Memory path to show')
|
|
155
|
-
.option('-x, --include-expired', 'Include expired memories')
|
|
156
|
-
.option('-o, --format <format>', 'Output format (yaml, json, toon)', 'yaml')
|
|
157
|
-
.action(async (path, options, command) => {
|
|
158
|
-
const parentOpts = command.parent?.opts() as { store?: string } | undefined;
|
|
159
|
-
const context = await createCliCommandContext();
|
|
160
|
-
if (!context.ok()) {
|
|
161
|
-
throwCliError(context.error);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
await handleShow(context.value, parentOpts?.store, path, options);
|
|
165
|
-
});
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { PassThrough } from 'node:stream';
|
|
2
|
-
import {
|
|
3
|
-
Cortex,
|
|
4
|
-
Memory,
|
|
5
|
-
ok,
|
|
6
|
-
type AdapterFactory,
|
|
7
|
-
type ConfigAdapter,
|
|
8
|
-
type ConfigStores,
|
|
9
|
-
type CortexContext,
|
|
10
|
-
type CortexSettings,
|
|
11
|
-
type MemoryMetadata,
|
|
12
|
-
type StorageAdapter,
|
|
13
|
-
} from '@yeseh/cortex-core';
|
|
14
|
-
|
|
15
|
-
export const createMockMemoryCommandAdapter = (
|
|
16
|
-
overrides: Record<string, unknown> = {},
|
|
17
|
-
): StorageAdapter =>
|
|
18
|
-
({
|
|
19
|
-
memories: {
|
|
20
|
-
load: async () => ok(null),
|
|
21
|
-
save: async () => ok(undefined),
|
|
22
|
-
add: async () => ok(undefined),
|
|
23
|
-
remove: async () => ok(undefined),
|
|
24
|
-
move: async () => ok(undefined),
|
|
25
|
-
...(overrides.memories as object | undefined),
|
|
26
|
-
},
|
|
27
|
-
indexes: {
|
|
28
|
-
load: async () => ok(null),
|
|
29
|
-
write: async () => ok(undefined),
|
|
30
|
-
reindex: async () => ok({ warnings: [] }),
|
|
31
|
-
updateAfterMemoryWrite: async () => ok(undefined),
|
|
32
|
-
...(overrides.indexes as object | undefined),
|
|
33
|
-
},
|
|
34
|
-
categories: {
|
|
35
|
-
exists: async () => ok(true),
|
|
36
|
-
ensure: async () => ok(undefined),
|
|
37
|
-
delete: async () => ok(undefined),
|
|
38
|
-
setDescription: async () => ok(undefined),
|
|
39
|
-
...(overrides.categories as object | undefined),
|
|
40
|
-
},
|
|
41
|
-
}) as unknown as StorageAdapter;
|
|
42
|
-
|
|
43
|
-
export const createMemoryCommandContext = (options: {
|
|
44
|
-
adapter: StorageAdapter;
|
|
45
|
-
storePath: string;
|
|
46
|
-
stdout?: PassThrough;
|
|
47
|
-
stdin?: PassThrough;
|
|
48
|
-
stores?: ConfigStores;
|
|
49
|
-
settings?: CortexSettings;
|
|
50
|
-
now?: () => Date;
|
|
51
|
-
adapterFactory?: AdapterFactory;
|
|
52
|
-
}): CortexContext => {
|
|
53
|
-
const stores: ConfigStores = options.stores ?? {
|
|
54
|
-
global: {
|
|
55
|
-
kind: 'filesystem',
|
|
56
|
-
properties: { path: options.storePath },
|
|
57
|
-
categories: {},
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const cortex = Cortex.init({
|
|
62
|
-
settings: options.settings,
|
|
63
|
-
stores,
|
|
64
|
-
adapterFactory: options.adapterFactory ?? (() => options.adapter),
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
cortex,
|
|
69
|
-
config: {
|
|
70
|
-
path: '/tmp/test/config.yaml',
|
|
71
|
-
data: null,
|
|
72
|
-
get stores() {
|
|
73
|
-
return stores;
|
|
74
|
-
},
|
|
75
|
-
get settings() {
|
|
76
|
-
return options.settings ?? null;
|
|
77
|
-
},
|
|
78
|
-
initializeConfig: async () => ok(undefined),
|
|
79
|
-
getSettings: async () => ok(options.settings ?? {}),
|
|
80
|
-
getStores: async () => ok(stores),
|
|
81
|
-
getStore: async (name: string) => ok(stores[name] ?? null),
|
|
82
|
-
saveStore: async () => ok(undefined),
|
|
83
|
-
} as ConfigAdapter,
|
|
84
|
-
settings: (options.settings ?? {}) as CortexSettings,
|
|
85
|
-
stores,
|
|
86
|
-
now: options.now ?? (() => new Date('2025-01-01T00:00:00.000Z')),
|
|
87
|
-
stdin: (options.stdin ?? new PassThrough()) as unknown as NodeJS.ReadStream,
|
|
88
|
-
stdout: (options.stdout ?? new PassThrough()) as unknown as NodeJS.WriteStream,
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export const createCaptureStream = (): { stream: PassThrough; getOutput: () => string } => {
|
|
93
|
-
let output = '';
|
|
94
|
-
const stream = new PassThrough();
|
|
95
|
-
const originalWrite = stream.write.bind(stream);
|
|
96
|
-
stream.write = ((chunk: unknown, encoding?: unknown, cb?: unknown) => {
|
|
97
|
-
output += Buffer.from(chunk as Buffer).toString(
|
|
98
|
-
typeof encoding === 'string' ? (encoding as BufferEncoding) : undefined,
|
|
99
|
-
);
|
|
100
|
-
return originalWrite(chunk as Buffer, encoding as BufferEncoding, cb as () => void);
|
|
101
|
-
}) as typeof stream.write;
|
|
102
|
-
|
|
103
|
-
return { stream, getOutput: () => output };
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
export const createMemoryFixture = (
|
|
107
|
-
path: string,
|
|
108
|
-
overrides: Partial<MemoryMetadata> = {},
|
|
109
|
-
content = 'Memory content',
|
|
110
|
-
): Memory => {
|
|
111
|
-
const timestamp = new Date('2025-01-01T00:00:00.000Z');
|
|
112
|
-
const metadata: MemoryMetadata = {
|
|
113
|
-
createdAt: timestamp,
|
|
114
|
-
updatedAt: timestamp,
|
|
115
|
-
tags: ['test'],
|
|
116
|
-
source: 'user',
|
|
117
|
-
citations: [],
|
|
118
|
-
...overrides,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const result = Memory.init(path, metadata, content);
|
|
122
|
-
if (!result.ok()) {
|
|
123
|
-
throw new Error('Test setup failed to create memory.');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return result.value;
|
|
127
|
-
};
|
|
@@ -1,86 +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 { Memory, MemoryPath, ok } from '@yeseh/cortex-core';
|
|
7
|
-
|
|
8
|
-
import { handleUpdate } from './update.ts';
|
|
9
|
-
import {
|
|
10
|
-
createCaptureStream,
|
|
11
|
-
createMemoryCommandContext,
|
|
12
|
-
createMemoryFixture,
|
|
13
|
-
createMockMemoryCommandAdapter,
|
|
14
|
-
} from './test-helpers.spec.ts';
|
|
15
|
-
|
|
16
|
-
describe('handleUpdate', () => {
|
|
17
|
-
let tempDir: string;
|
|
18
|
-
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
tempDir = await mkdtemp(join(tmpdir(), 'cortex-cli-memory-update-'));
|
|
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(handleUpdate(ctx, undefined, 'invalid', { content: 'Next' })).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(
|
|
46
|
-
handleUpdate(ctx, 'missing-store', 'project/one', { content: 'Next' }),
|
|
47
|
-
).rejects.toThrow(CommanderError);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should update memory and report output', async () => {
|
|
51
|
-
const memory = createMemoryFixture('project/update', {}, 'Original content');
|
|
52
|
-
const writeCalls: Memory[] = [];
|
|
53
|
-
|
|
54
|
-
const adapter = createMockMemoryCommandAdapter({
|
|
55
|
-
memories: {
|
|
56
|
-
load: async () => ok(memory),
|
|
57
|
-
save: async (_: MemoryPath, next: Memory) => {
|
|
58
|
-
writeCalls.push(next);
|
|
59
|
-
return ok(undefined);
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
indexes: {
|
|
63
|
-
updateAfterMemoryWrite: async () => ok(undefined),
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const capture = createCaptureStream();
|
|
68
|
-
const ctx = createMemoryCommandContext({
|
|
69
|
-
adapter,
|
|
70
|
-
storePath: tempDir,
|
|
71
|
-
stdout: capture.stream,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
await handleUpdate(ctx, undefined, 'project/update', {
|
|
75
|
-
content: 'Updated content',
|
|
76
|
-
tags: ['new-tag'],
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
expect(writeCalls).toHaveLength(1);
|
|
80
|
-
if (writeCalls[0]) {
|
|
81
|
-
expect(writeCalls[0].content).toBe('Updated content');
|
|
82
|
-
expect(writeCalls[0].metadata.tags).toEqual(['new-tag']);
|
|
83
|
-
}
|
|
84
|
-
expect(capture.getOutput()).toContain('Updated memory project/update.');
|
|
85
|
-
});
|
|
86
|
-
});
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory update command implementation using Commander.js.
|
|
3
|
-
*
|
|
4
|
-
* Updates an existing memory at the specified path with new content, tags,
|
|
5
|
-
* or expiration date.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```bash
|
|
9
|
-
* # Update memory content inline
|
|
10
|
-
* cortex memory update project/tech-stack --content "Updated stack: TypeScript"
|
|
11
|
-
*
|
|
12
|
-
* # Update memory content from a file
|
|
13
|
-
* cortex memory update project/notes --file ./updated-notes.md
|
|
14
|
-
*
|
|
15
|
-
* # Update tags
|
|
16
|
-
* cortex memory update project/tech-stack --tags "typescript,nodejs,updated"
|
|
17
|
-
*
|
|
18
|
-
* # Update expiration date
|
|
19
|
-
* cortex memory update project/temp --expires-at "2026-12-31T00:00:00Z"
|
|
20
|
-
*
|
|
21
|
-
* # Clear expiration date
|
|
22
|
-
* cortex memory update project/temp --no-expires-at
|
|
23
|
-
*
|
|
24
|
-
* # Update from a specific store
|
|
25
|
-
* cortex memory --store work update project/notes --content "New content"
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { Command } from '@commander-js/extra-typings';
|
|
30
|
-
import { throwCliError } from '../../errors.ts';
|
|
31
|
-
import { MemoryPath, type CortexContext, type UpdateMemoryInput } from '@yeseh/cortex-core';
|
|
32
|
-
import { resolveInput as resolveCliContent } from '../../utils/input.ts';
|
|
33
|
-
import { parseExpiresAt, parseTags } from '../parsing.ts';
|
|
34
|
-
import { createCliCommandContext } from '../../context.ts';
|
|
35
|
-
import { serializeOutput, type OutputFormat } from '../../output.ts';
|
|
36
|
-
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
37
|
-
|
|
38
|
-
/** Options parsed by Commander for the update command */
|
|
39
|
-
export interface UpdateCommandOptions {
|
|
40
|
-
content?: string;
|
|
41
|
-
file?: string;
|
|
42
|
-
tags?: string[];
|
|
43
|
-
/**
|
|
44
|
-
* Expiration date from Commander.js option parsing.
|
|
45
|
-
* - `string` — ISO 8601 date provided via `--expires-at <date>`
|
|
46
|
-
* - `false` — expiration cleared via `--no-expires-at` negation flag
|
|
47
|
-
* - `undefined` (omitted) — keep the existing value unchanged
|
|
48
|
-
*/
|
|
49
|
-
expiresAt?: string | false;
|
|
50
|
-
citation?: string[];
|
|
51
|
-
format?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const parseUpdateExpiresAt = (raw?: string | false): Date | null | undefined => {
|
|
55
|
-
if (raw === false) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!raw) {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return parseExpiresAt(raw);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const resolveUpdateContent = async (
|
|
67
|
-
ctx: CortexContext,
|
|
68
|
-
options: UpdateCommandOptions
|
|
69
|
-
): Promise<string | null> => {
|
|
70
|
-
if (options.content === undefined && options.file === undefined) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const content = await resolveCliContent({
|
|
75
|
-
content: options.content,
|
|
76
|
-
filePath: options.file,
|
|
77
|
-
stream: ctx.stdin,
|
|
78
|
-
// `memory update` does not read stdin.
|
|
79
|
-
stdinRequested: false,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (!content.ok()) {
|
|
83
|
-
throwCliError(content.error);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!content.value.content) {
|
|
87
|
-
throwCliError({
|
|
88
|
-
code: 'MISSING_CONTENT',
|
|
89
|
-
message: 'Memory content is required via --content or --file.',
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return content.value.content;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const buildUpdates = (
|
|
97
|
-
content: string | null,
|
|
98
|
-
tags: string[] | undefined,
|
|
99
|
-
expiresAt: Date | null | undefined,
|
|
100
|
-
citations: string[] | undefined
|
|
101
|
-
): UpdateMemoryInput => {
|
|
102
|
-
const updates: UpdateMemoryInput = {};
|
|
103
|
-
if (content !== null) {
|
|
104
|
-
updates.content = content;
|
|
105
|
-
}
|
|
106
|
-
if (tags !== undefined) {
|
|
107
|
-
updates.tags = tags;
|
|
108
|
-
}
|
|
109
|
-
if (expiresAt !== undefined) {
|
|
110
|
-
updates.expiresAt = expiresAt;
|
|
111
|
-
}
|
|
112
|
-
if (citations !== undefined) {
|
|
113
|
-
updates.citations = citations;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (Object.keys(updates).length === 0) {
|
|
117
|
-
throwCliError({
|
|
118
|
-
code: 'INVALID_ARGUMENTS',
|
|
119
|
-
message:
|
|
120
|
-
'No updates provided. Use --content, --file, --tags, --citation, or expiry flags.',
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return updates;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Handler for the memory update command.
|
|
129
|
-
* Exported for direct testing without Commander parsing.
|
|
130
|
-
*
|
|
131
|
-
* @param ctx - CLI context containing Cortex client and streams
|
|
132
|
-
* @param storeName - Optional store name from parent command
|
|
133
|
-
* @param path - Memory path to update (e.g., "project/tech-stack")
|
|
134
|
-
* @param options - Command options from Commander
|
|
135
|
-
*/
|
|
136
|
-
export async function handleUpdate(
|
|
137
|
-
ctx: CortexContext,
|
|
138
|
-
storeName: string | undefined,
|
|
139
|
-
path: string,
|
|
140
|
-
options: UpdateCommandOptions
|
|
141
|
-
): Promise<void> {
|
|
142
|
-
const pathResult = MemoryPath.fromString(path);
|
|
143
|
-
if (!pathResult.ok()) {
|
|
144
|
-
throwCliError(pathResult.error);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const content = await resolveUpdateContent(ctx, options);
|
|
148
|
-
const tags = options.tags === undefined ? undefined : parseTags(options.tags);
|
|
149
|
-
const expiresAt = parseUpdateExpiresAt(options.expiresAt);
|
|
150
|
-
const updates = buildUpdates(content, tags, expiresAt, options.citation);
|
|
151
|
-
|
|
152
|
-
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
153
|
-
if (!storeResult.ok()) {
|
|
154
|
-
throwCliError(storeResult.error);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const store = storeResult.value;
|
|
158
|
-
const rootResult = store.root();
|
|
159
|
-
if (!rootResult.ok()) {
|
|
160
|
-
throwCliError(rootResult.error);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const categoryResult = pathResult.value.category.isRoot
|
|
164
|
-
? rootResult
|
|
165
|
-
: rootResult.value.getCategory(pathResult.value.category.toString());
|
|
166
|
-
if (!categoryResult.ok()) {
|
|
167
|
-
throwCliError(categoryResult.error);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const memoryClient = categoryResult.value.getMemory(pathResult.value.slug.toString());
|
|
171
|
-
const updateResult = await memoryClient.update(updates);
|
|
172
|
-
if (!updateResult.ok()) {
|
|
173
|
-
throwCliError(updateResult.error);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const memory = updateResult.value;
|
|
177
|
-
const stdout = ctx.stdout ?? process.stdout;
|
|
178
|
-
|
|
179
|
-
const rawFormat = options.format;
|
|
180
|
-
if (!rawFormat) {
|
|
181
|
-
stdout.write(`Updated memory ${memory.path.toString()}.\n`);
|
|
182
|
-
} else {
|
|
183
|
-
const format = rawFormat as OutputFormat;
|
|
184
|
-
const serialized = serializeOutput(
|
|
185
|
-
{
|
|
186
|
-
kind: 'memory',
|
|
187
|
-
value: {
|
|
188
|
-
path: memory.path.toString(),
|
|
189
|
-
metadata: memory.metadata,
|
|
190
|
-
content: memory.content,
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
format
|
|
194
|
-
);
|
|
195
|
-
if (!serialized.ok()) {
|
|
196
|
-
throwCliError({ code: 'SERIALIZE_FAILED', message: serialized.error.message });
|
|
197
|
-
}
|
|
198
|
-
stdout.write(serialized.value + '\n');
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* The `memory update` subcommand.
|
|
204
|
-
*
|
|
205
|
-
* Updates an existing memory at the specified path. Can update:
|
|
206
|
-
* - Content via `--content` flag for inline text or `--file` to read from a file
|
|
207
|
-
* - Tags via `--tags` flag (replaces existing tags)
|
|
208
|
-
* - Expiration via `--expires-at` or `--no-expires-at`
|
|
209
|
-
*
|
|
210
|
-
* The `--store` option is inherited from the parent `memory` command.
|
|
211
|
-
*/
|
|
212
|
-
export const updateCommand = new Command('update')
|
|
213
|
-
.description('Update an existing memory')
|
|
214
|
-
.argument('<path>', 'Memory path to update')
|
|
215
|
-
.option('-c, --content <text>', 'New memory content as inline text')
|
|
216
|
-
.option('-f, --file <filepath>', 'Read new content from a file')
|
|
217
|
-
.option('-t, --tags <value...>', 'Tags (can be repeated or comma-separated, replaces existing)')
|
|
218
|
-
.option('-e, --expires-at <date>', 'New expiration date (ISO 8601)')
|
|
219
|
-
.option('--no-expires-at', 'Remove expiration date')
|
|
220
|
-
.option('--citation <value...>', 'Citation references (replaces existing)')
|
|
221
|
-
.option('-o, --format <format>', 'Output format (yaml, json, toon)')
|
|
222
|
-
.action(async (path, options, command) => {
|
|
223
|
-
const parentOpts = command.parent?.opts() as { store?: string } | undefined;
|
|
224
|
-
const context = await createCliCommandContext();
|
|
225
|
-
if (!context.ok()) {
|
|
226
|
-
throwCliError(context.error);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
await handleUpdate(context.value, parentOpts?.store, path, options);
|
|
230
|
-
});
|