@yeseh/cortex-cli 0.6.7 → 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.
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-tgrm2cc9.js +0 -1543
  9. package/dist/chunk-tgrm2cc9.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,169 +0,0 @@
1
- /**
2
- * Unit tests for the handleAdd command handler.
3
- *
4
- * @module cli/memory/commands/add.spec
5
- */
6
-
7
- import { describe, it, expect, afterEach, beforeEach } from 'bun:test';
8
- import { CommanderError, InvalidArgumentError } from '@commander-js/extra-typings';
9
- import { mkdtemp, rm } from 'node:fs/promises';
10
- import { tmpdir } from 'node:os';
11
- import { join } from 'node:path';
12
- import { PassThrough } from 'node:stream';
13
- import {
14
- ok,
15
- type AdapterFactory,
16
- } from '@yeseh/cortex-core';
17
- import { handleAdd } from './add.ts';
18
- import {
19
- createCaptureStream,
20
- createMemoryCommandContext,
21
- createMockMemoryCommandAdapter,
22
- } from './test-helpers.spec.ts';
23
-
24
- // ---------------------------------------------------------------------------
25
- // Local helpers
26
- // ---------------------------------------------------------------------------
27
-
28
- // ---------------------------------------------------------------------------
29
- // Tests
30
- // ---------------------------------------------------------------------------
31
-
32
- describe('handleAdd', () => {
33
- let tempDir: string;
34
-
35
- beforeEach(async () => {
36
- tempDir = await mkdtemp(join(tmpdir(), 'cortex-add-'));
37
- });
38
-
39
- afterEach(async () => {
40
- await rm(tempDir, { recursive: true, force: true });
41
- });
42
-
43
- it('should create a memory and write success message to stdout', async () => {
44
- const capture = createCaptureStream();
45
- const ctx = createMemoryCommandContext({
46
- adapter: createMockMemoryCommandAdapter(),
47
- storePath: tempDir,
48
- stdout: capture.stream,
49
- });
50
-
51
- await handleAdd(ctx, undefined, 'project/notes', { content: 'Hello world' });
52
-
53
- const out = capture.getOutput();
54
- expect(out).toContain('Added memory');
55
- expect(out).toContain('project/notes');
56
- });
57
-
58
- it('should pass tags from options', async () => {
59
- const capture = createCaptureStream();
60
- const ctx = createMemoryCommandContext({
61
- adapter: createMockMemoryCommandAdapter(),
62
- storePath: tempDir,
63
- stdout: capture.stream,
64
- });
65
-
66
- await handleAdd(ctx, undefined, 'project/tagged', {
67
- content: 'Tagged memory',
68
- tags: ['foo', 'bar'],
69
- });
70
-
71
- expect(capture.getOutput()).toContain('Added memory');
72
- expect(capture.getOutput()).toContain('project/tagged');
73
- });
74
-
75
- it('should pass expiresAt from options', async () => {
76
- const capture = createCaptureStream();
77
- const ctx = createMemoryCommandContext({
78
- adapter: createMockMemoryCommandAdapter(),
79
- storePath: tempDir,
80
- stdout: capture.stream,
81
- });
82
-
83
- await handleAdd(ctx, undefined, 'project/expiring', {
84
- content: 'Expires soon',
85
- expiresAt: '2030-12-31T00:00:00Z',
86
- });
87
-
88
- expect(capture.getOutput()).toContain('Added memory');
89
- });
90
-
91
- it('should pass citations from options', async () => {
92
- const capture = createCaptureStream();
93
- const ctx = createMemoryCommandContext({
94
- adapter: createMockMemoryCommandAdapter(),
95
- storePath: tempDir,
96
- stdout: capture.stream,
97
- });
98
-
99
- await handleAdd(ctx, undefined, 'project/cited', {
100
- content: 'Cited memory',
101
- citations: ['https://example.com/source'],
102
- });
103
-
104
- expect(capture.getOutput()).toContain('Added memory');
105
- });
106
-
107
- it('should throw CommanderError when store not found', async () => {
108
- const failingFactory = (() => undefined) as unknown as AdapterFactory;
109
-
110
- const stdin = new PassThrough();
111
- const ctx = createMemoryCommandContext({
112
- adapter: createMockMemoryCommandAdapter(),
113
- storePath: tempDir,
114
- stdin,
115
- adapterFactory: failingFactory,
116
- });
117
-
118
- await expect(
119
- handleAdd(ctx, 'nonexistent', 'project/notes', { content: 'test' })
120
- ).rejects.toThrow(CommanderError);
121
- });
122
-
123
- it('should throw InvalidArgumentError for MISSING_CONTENT when no content provided', async () => {
124
- const stdin = new PassThrough();
125
- stdin.end(); // EOF with no data → empty content → MISSING_CONTENT
126
- const ctx = createMemoryCommandContext({
127
- adapter: createMockMemoryCommandAdapter(),
128
- storePath: tempDir,
129
- stdin,
130
- });
131
-
132
- await expect(handleAdd(ctx, undefined, 'project/notes', {})).rejects.toThrow(
133
- InvalidArgumentError
134
- );
135
- });
136
-
137
- it('should throw CommanderError when memory create fails due to missing category', async () => {
138
- const ctx = createMemoryCommandContext({
139
- adapter: createMockMemoryCommandAdapter({
140
- categories: {
141
- exists: async () => ok(false), // category absent → CATEGORY_NOT_FOUND
142
- },
143
- }),
144
- storePath: tempDir,
145
- });
146
-
147
- await expect(
148
- handleAdd(ctx, undefined, 'project/notes', { content: 'test' })
149
- ).rejects.toThrow(CommanderError);
150
- });
151
-
152
- it('should use stdin when no content option is provided', async () => {
153
- const stdin = new PassThrough();
154
- stdin.end('Content from stdin');
155
- const capture = createCaptureStream();
156
- const ctx = createMemoryCommandContext({
157
- adapter: createMockMemoryCommandAdapter(),
158
- storePath: tempDir,
159
- stdin,
160
- stdout: capture.stream,
161
- });
162
-
163
- await handleAdd(ctx, undefined, 'project/stdin', {});
164
-
165
- const out = capture.getOutput();
166
- expect(out).toContain('Added memory');
167
- expect(out).toContain('stdin');
168
- });
169
- });
@@ -1,158 +0,0 @@
1
- /**
2
- * Memory add command implementation using Commander.js.
3
- *
4
- * Creates a new memory at the specified path with content from inline text,
5
- * a file, or stdin.
6
- *
7
- * @example
8
- * ```bash
9
- * # Add memory with inline content
10
- * cortex memory add project/tech-stack --content "Using TypeScript and Node.js"
11
- *
12
- * # Add memory from a file
13
- * cortex memory add project/notes --file ./notes.md
14
- *
15
- * # Add memory from stdin
16
- * echo "My notes" | cortex memory add project/notes
17
- *
18
- * # Add memory with tags and expiration
19
- * cortex memory add project/temp --content "Temporary note" \
20
- * --tags "temp,cleanup" --expires-at "2025-12-31T00:00:00Z"
21
- * ```
22
- */
23
-
24
- import { Command } from '@commander-js/extra-typings';
25
- import { throwCliError } from '../../errors.ts';
26
- import { type CortexContext } from '@yeseh/cortex-core';
27
- import { resolveInput as resolveCliContent } from '../../utils/input.ts';
28
- import { parseExpiresAt, parseTags } from '../parsing.ts';
29
- import { createCliCommandContext } from '../../context.ts';
30
- import { serializeOutput, type OutputFormat } from '../../output.ts';
31
- import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
32
-
33
- /** Options parsed by Commander for the add command */
34
- export interface AddCommandOptions {
35
- content?: string;
36
- file?: string;
37
- tags?: string[];
38
- expiresAt?: string;
39
- citations?: string[];
40
- format?: string;
41
- }
42
- /**
43
- * Handler for the memory add command.
44
- * Exported for direct testing without Commander parsing.
45
- *
46
- * @param path - Memory path (e.g., "project/tech-stack")
47
- * @param options - Command options from Commander
48
- * @param storeName - Optional store name from parent command
49
- * @param deps - Injectable dependencies for testing
50
- */
51
- export async function handleAdd(
52
- ctx: CortexContext,
53
- storeName: string | undefined,
54
- path: string,
55
- options: AddCommandOptions
56
- ): Promise<void> {
57
- const content = await resolveCliContent({
58
- content: options.content,
59
- filePath: options.file,
60
- stream: ctx.stdin,
61
- // `memory add` accepts stdin by default (when piped).
62
- stdinRequested: options.content === undefined && options.file === undefined,
63
- });
64
-
65
- if (!content.ok()) {
66
- throwCliError(content.error);
67
- }
68
-
69
- if (!content.value.content) {
70
- throwCliError({
71
- code: 'MISSING_CONTENT',
72
- message: 'Memory content is required via --content, --file, or stdin.',
73
- });
74
- }
75
-
76
- const { content: memoryContent, source } = content.value;
77
- const tags = parseTags(options.tags);
78
- const expiresAt = parseExpiresAt(options.expiresAt);
79
- const citations = options.citations ?? [];
80
-
81
- const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
82
- if (!storeResult.ok()) {
83
- throwCliError(storeResult.error);
84
- }
85
-
86
- const store = storeResult.value;
87
- const timestamp = ctx.now() ?? new Date();
88
- const memoryClient = store.getMemory(path);
89
- const memoryResult = await memoryClient.create({
90
- content: memoryContent!,
91
- metadata: {
92
- tags,
93
- source,
94
- createdAt: timestamp,
95
- updatedAt: timestamp,
96
- expiresAt,
97
- citations,
98
- },
99
- });
100
-
101
- if (!memoryResult.ok()) {
102
- throwCliError(memoryResult.error);
103
- }
104
-
105
- const memory = memoryResult.value;
106
- const out = ctx.stdout ?? process.stdout;
107
-
108
- const rawFormat = options.format;
109
- if (!rawFormat) {
110
- out.write(`Added memory ${memory.path} (${source}).\n`);
111
- } else {
112
- const format = rawFormat as OutputFormat;
113
- const serialized = serializeOutput(
114
- {
115
- kind: 'memory',
116
- value: {
117
- path: memory.path.toString(),
118
- metadata: memory.metadata,
119
- content: memory.content,
120
- },
121
- },
122
- format
123
- );
124
- if (!serialized.ok()) {
125
- throwCliError({ code: 'SERIALIZE_FAILED', message: serialized.error.message });
126
- }
127
- out.write(serialized.value + '\n');
128
- }
129
- }
130
-
131
- /**
132
- * The `memory add` subcommand.
133
- *
134
- * Creates a new memory at the specified path. Content can be provided via:
135
- * - `--content` flag for inline text
136
- * - `--file` flag to read from a file
137
- * - stdin when piped
138
- *
139
- * The `--store` option is inherited from the parent `memory` command.
140
- */
141
- export const addCommand = new Command('add')
142
- .description('Create a new memory')
143
- .argument('<path>', 'Memory path (e.g., project/tech-stack)')
144
- .option('-c, --content <text>', 'Memory content as inline text')
145
- .option('-f, --file <filepath>', 'Read content from a file')
146
- .option('-t, --tags <value...>', 'Tags (can be repeated or comma-separated)')
147
- .option('-e, --expires-at <date>', 'Expiration date (ISO 8601)')
148
- .option('--citation <value...>', 'Citation references (file paths or URLs)')
149
- .option('-o, --format <format>', 'Output format (yaml, json, toon)')
150
- .action(async (path, options, command) => {
151
- const parentOpts = command.parent?.opts() as { store?: string } | undefined;
152
- const context = await createCliCommandContext();
153
- if (!context.ok()) {
154
- throwCliError(context.error);
155
- }
156
-
157
- await handleAdd(context.value, parentOpts?.store, path, options);
158
- });
@@ -1,80 +0,0 @@
1
- /**
2
- * Unit tests for memory command definitions.
3
- *
4
- * Verifies that memory subcommands expose the expected names,
5
- * arguments, and options.
6
- *
7
- * @module cli/memory/commands/definitions.spec
8
- */
9
-
10
- import { describe, expect, it } from 'bun:test';
11
-
12
- import { listCommand } from './list.ts';
13
- import { moveCommand } from './move.ts';
14
- import { removeCommand } from './remove.ts';
15
- import { showCommand } from './show.ts';
16
- import { updateCommand } from './update.ts';
17
-
18
- const getLongOptions = (command: { options: ReadonlyArray<{ long?: string }> }): string[] =>
19
- command.options.map((option) => option.long ?? '').filter(Boolean);
20
-
21
- describe('memory command definitions', () => {
22
- describe('listCommand', () => {
23
- it('should expose expected command name and usage', () => {
24
- expect(listCommand.name()).toBe('list');
25
- expect(listCommand.usage()).toContain('[category]');
26
- });
27
-
28
- it('should register expected options', () => {
29
- const options = getLongOptions(listCommand);
30
- expect(options).toContain('--store');
31
- expect(options).toContain('--include-expired');
32
- expect(options).toContain('--format');
33
- });
34
- });
35
-
36
- describe('moveCommand', () => {
37
- it('should expose expected command name and usage', () => {
38
- expect(moveCommand.name()).toBe('move');
39
- expect(moveCommand.usage()).toContain('<from>');
40
- expect(moveCommand.usage()).toContain('<to>');
41
- });
42
- });
43
-
44
- describe('removeCommand', () => {
45
- it('should expose expected command name and usage', () => {
46
- expect(removeCommand.name()).toBe('remove');
47
- expect(removeCommand.usage()).toContain('<path>');
48
- });
49
- });
50
-
51
- describe('showCommand', () => {
52
- it('should expose expected command name and usage', () => {
53
- expect(showCommand.name()).toBe('show');
54
- expect(showCommand.usage()).toContain('<path>');
55
- });
56
-
57
- it('should register expected options', () => {
58
- const options = getLongOptions(showCommand);
59
- expect(options).toContain('--include-expired');
60
- expect(options).toContain('--format');
61
- });
62
- });
63
-
64
- describe('updateCommand', () => {
65
- it('should expose expected command name and usage', () => {
66
- expect(updateCommand.name()).toBe('update');
67
- expect(updateCommand.usage()).toContain('<path>');
68
- });
69
-
70
- it('should register expected options', () => {
71
- const options = getLongOptions(updateCommand);
72
- expect(options).toContain('--content');
73
- expect(options).toContain('--file');
74
- expect(options).toContain('--tags');
75
- expect(options).toContain('--expires-at');
76
- expect(options).toContain('--no-expires-at');
77
- expect(options).toContain('--citation');
78
- });
79
- });
80
- });
@@ -1,123 +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 {
7
- CategoryPath,
8
- MemoryPath,
9
- ok,
10
- type AdapterFactory,
11
- type Category,
12
- } from '@yeseh/cortex-core';
13
-
14
- import { handleList } from './list.ts';
15
- import {
16
- createCaptureStream,
17
- createMemoryCommandContext,
18
- createMemoryFixture,
19
- createMockMemoryCommandAdapter,
20
- } from './test-helpers.spec.ts';
21
-
22
- describe('handleList', () => {
23
- let tempDir: string;
24
-
25
- beforeEach(async () => {
26
- tempDir = await mkdtemp(join(tmpdir(), 'cortex-cli-memory-list-'));
27
- });
28
-
29
- afterEach(async () => {
30
- await rm(tempDir, { recursive: true, force: true });
31
- });
32
-
33
- it('should throw InvalidArgumentError for invalid category paths', async () => {
34
- const ctx = createMemoryCommandContext({
35
- adapter: createMockMemoryCommandAdapter(),
36
- storePath: tempDir,
37
- });
38
-
39
- await expect(handleList(ctx, undefined, '/ /', { format: 'yaml' })).rejects.toThrow(
40
- InvalidArgumentError,
41
- );
42
- });
43
-
44
- it('should throw CommanderError when the adapter factory returns nothing', async () => {
45
- const nullFactory = (() => undefined) as unknown as AdapterFactory;
46
- const ctx = createMemoryCommandContext({
47
- adapter: createMockMemoryCommandAdapter(),
48
- storePath: tempDir,
49
- adapterFactory: nullFactory,
50
- stores: {
51
- global: {
52
- kind: 'filesystem',
53
- properties: { path: tempDir },
54
- categories: {},
55
- },
56
- },
57
- });
58
-
59
- await expect(handleList(ctx, 'missing-store', undefined, {})).rejects.toThrow(
60
- CommanderError,
61
- );
62
- });
63
-
64
- it('should write serialized list output', async () => {
65
- const memoryPath = MemoryPath.fromString('project/one');
66
- if (!memoryPath.ok()) {
67
- throw new Error('Test setup failed to create memory path.');
68
- }
69
- const memory = createMemoryFixture('project/one');
70
-
71
- const rootCategory: Category = {
72
- memories: [],
73
- subcategories: [
74
- {
75
- path: CategoryPath.fromString('project').unwrap(),
76
- memoryCount: 1,
77
- description: 'Project memories',
78
- },
79
- ],
80
- };
81
-
82
- const projectCategory: Category = {
83
- memories: [
84
- {
85
- path: memoryPath.value,
86
- tokenEstimate: 42,
87
- },
88
- ],
89
- subcategories: [],
90
- };
91
-
92
- const adapter = createMockMemoryCommandAdapter({
93
- indexes: {
94
- load: async (path: CategoryPath) => {
95
- if (path.isRoot) {
96
- return ok(rootCategory);
97
- }
98
- if (path.toString() === 'project') {
99
- return ok(projectCategory);
100
- }
101
- return ok(null);
102
- },
103
- },
104
- memories: {
105
- load: async () => ok(memory),
106
- },
107
- });
108
-
109
- const capture = createCaptureStream();
110
- const ctx = createMemoryCommandContext({
111
- adapter,
112
- storePath: tempDir,
113
- stdout: capture.stream,
114
- });
115
-
116
- await handleList(ctx, undefined, undefined, { format: 'json' });
117
-
118
- const output = JSON.parse(capture.getOutput());
119
- expect(output.memories).toHaveLength(1);
120
- expect(output.memories[0].path).toBe('project/one');
121
- expect(output.subcategories[0].path).toBe('project');
122
- });
123
- });