@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.
Files changed (74) hide show
  1. package/dist/program.js +1538 -5
  2. package/dist/program.js.map +32 -3
  3. package/dist/run.d.ts +0 -1
  4. package/dist/run.d.ts.map +1 -1
  5. package/dist/run.js +3 -4
  6. package/dist/run.js.map +3 -3
  7. package/package.json +4 -6
  8. package/dist/chunk-dsfj4baj.js +0 -1543
  9. package/dist/chunk-dsfj4baj.js.map +0 -38
  10. package/src/category/commands/create.spec.ts +0 -139
  11. package/src/category/commands/create.ts +0 -119
  12. package/src/category/index.ts +0 -24
  13. package/src/commands/init.spec.ts +0 -203
  14. package/src/commands/init.ts +0 -301
  15. package/src/context.spec.ts +0 -60
  16. package/src/context.ts +0 -170
  17. package/src/errors.spec.ts +0 -264
  18. package/src/errors.ts +0 -105
  19. package/src/memory/commands/add.spec.ts +0 -169
  20. package/src/memory/commands/add.ts +0 -158
  21. package/src/memory/commands/definitions.spec.ts +0 -80
  22. package/src/memory/commands/list.spec.ts +0 -123
  23. package/src/memory/commands/list.ts +0 -269
  24. package/src/memory/commands/move.spec.ts +0 -85
  25. package/src/memory/commands/move.ts +0 -119
  26. package/src/memory/commands/remove.spec.ts +0 -79
  27. package/src/memory/commands/remove.ts +0 -108
  28. package/src/memory/commands/show.spec.ts +0 -71
  29. package/src/memory/commands/show.ts +0 -165
  30. package/src/memory/commands/test-helpers.spec.ts +0 -127
  31. package/src/memory/commands/update.spec.ts +0 -86
  32. package/src/memory/commands/update.ts +0 -230
  33. package/src/memory/index.spec.ts +0 -59
  34. package/src/memory/index.ts +0 -44
  35. package/src/memory/parsing.spec.ts +0 -105
  36. package/src/memory/parsing.ts +0 -22
  37. package/src/observability.spec.ts +0 -126
  38. package/src/observability.ts +0 -82
  39. package/src/output.spec.ts +0 -835
  40. package/src/output.ts +0 -119
  41. package/src/program.spec.ts +0 -46
  42. package/src/program.ts +0 -75
  43. package/src/run.spec.ts +0 -31
  44. package/src/run.ts +0 -9
  45. package/src/store/commands/add.spec.ts +0 -131
  46. package/src/store/commands/add.ts +0 -231
  47. package/src/store/commands/init.spec.ts +0 -220
  48. package/src/store/commands/init.ts +0 -272
  49. package/src/store/commands/list.spec.ts +0 -175
  50. package/src/store/commands/list.ts +0 -102
  51. package/src/store/commands/prune.spec.ts +0 -120
  52. package/src/store/commands/prune.ts +0 -152
  53. package/src/store/commands/reindexs.spec.ts +0 -94
  54. package/src/store/commands/reindexs.ts +0 -97
  55. package/src/store/commands/remove.spec.ts +0 -97
  56. package/src/store/commands/remove.ts +0 -189
  57. package/src/store/index.spec.ts +0 -60
  58. package/src/store/index.ts +0 -49
  59. package/src/store/utils/resolve-store-name.spec.ts +0 -62
  60. package/src/store/utils/resolve-store-name.ts +0 -79
  61. package/src/test-helpers.spec.ts +0 -430
  62. package/src/tests/cli.integration.spec.ts +0 -1306
  63. package/src/toon.spec.ts +0 -183
  64. package/src/toon.ts +0 -462
  65. package/src/utils/git.spec.ts +0 -95
  66. package/src/utils/git.ts +0 -51
  67. package/src/utils/input.spec.ts +0 -326
  68. package/src/utils/input.ts +0 -150
  69. package/src/utils/paths.spec.ts +0 -235
  70. package/src/utils/paths.ts +0 -75
  71. package/src/utils/prompts.spec.ts +0 -23
  72. package/src/utils/prompts.ts +0 -88
  73. package/src/utils/resolve-default-store.spec.ts +0 -135
  74. 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
- });