@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,235 +0,0 @@
1
- /**
2
- * Unit tests for CLI path resolution utilities.
3
- *
4
- * @module cli/paths.spec
5
- */
6
-
7
- import { describe, expect, it } from 'bun:test';
8
- import { homedir } from 'node:os';
9
- import { isAbsolute, join } from 'node:path';
10
-
11
- import { getDefaultConfigPath, getDefaultGlobalStorePath, isAbsolutePath, resolveUserPath } from './paths.ts';
12
- import { validateStorePath } from '../context.ts';
13
-
14
- describe('isAbsolutePath', () => {
15
- it('should return true for Unix absolute path starting with /', () => {
16
- expect(isAbsolutePath('/home/user/foo')).toBe(true);
17
- });
18
-
19
- it('should return true for Unix root path /', () => {
20
- expect(isAbsolutePath('/')).toBe(true);
21
- });
22
-
23
- it('should return true for Windows drive path C:\\', () => {
24
- expect(isAbsolutePath('C:\\Users\\foo')).toBe(true);
25
- });
26
-
27
- it('should return true for Windows drive path D:/', () => {
28
- expect(isAbsolutePath('D:/Users/foo')).toBe(true);
29
- });
30
-
31
- it('should return true for UNC path \\\\server\\share', () => {
32
- expect(isAbsolutePath('\\\\server\\share')).toBe(true);
33
- });
34
-
35
- it('should return true for UNC path //server/share', () => {
36
- expect(isAbsolutePath('//server/share')).toBe(true);
37
- });
38
-
39
- it('should return false for relative path ./foo', () => {
40
- expect(isAbsolutePath('./foo')).toBe(false);
41
- });
42
-
43
- it('should return false for bare filename foo.ts', () => {
44
- expect(isAbsolutePath('foo.ts')).toBe(false);
45
- });
46
-
47
- it('should return false for relative path ../bar', () => {
48
- expect(isAbsolutePath('../bar')).toBe(false);
49
- });
50
-
51
- it('should return false for empty string', () => {
52
- expect(isAbsolutePath('')).toBe(false);
53
- });
54
- });
55
-
56
- describe('resolveUserPath', () => {
57
- const cwd = '/some/working/dir';
58
-
59
- it('should expand ~ to homedir', () => {
60
- const result = resolveUserPath('~', cwd);
61
- expect(result).toBe(homedir());
62
- });
63
-
64
- it('should resolve ~/subdir correctly', () => {
65
- const result = resolveUserPath('~/subdir', cwd);
66
- const expected = `${homedir()}/subdir`;
67
- expect(result).toBe(expected);
68
- });
69
-
70
- it('should return absolute path unchanged for /abs/path', () => {
71
- const result = resolveUserPath('/abs/path', cwd);
72
- expect(result).toBe('/abs/path');
73
- });
74
-
75
- it('should resolve relative paths against cwd', () => {
76
- const result = resolveUserPath('relative/path', cwd);
77
- expect(result).toBe('/some/working/dir/relative/path');
78
- });
79
-
80
- it('should resolve ./foo relative to cwd', () => {
81
- const result = resolveUserPath('./foo', cwd);
82
- expect(result).toBe('/some/working/dir/foo');
83
- });
84
-
85
- it('should resolve ../bar relative to cwd', () => {
86
- const result = resolveUserPath('../bar', cwd);
87
- expect(result).toBe('/some/working/bar');
88
- });
89
-
90
- it('should return an absolute path for any input', () => {
91
- expect(isAbsolute(resolveUserPath('foo', cwd))).toBe(true);
92
- expect(isAbsolute(resolveUserPath('~/foo', cwd))).toBe(true);
93
- expect(isAbsolute(resolveUserPath('/foo', cwd))).toBe(true);
94
- });
95
-
96
- it('should handle ~ with no trailing path', () => {
97
- const result = resolveUserPath('~', cwd);
98
- expect(result).toBe(homedir());
99
- });
100
- });
101
-
102
- describe('validateStorePath', () => {
103
- it('should return ok for absolute path', () => {
104
- const result = validateStorePath('/absolute/path/to/store', 'my-store');
105
- expect(result.ok()).toBe(true);
106
- });
107
-
108
- it('should return INVALID_STORE_PATH error for relative path', () => {
109
- const result = validateStorePath('relative/path', 'my-store');
110
- expect(result.ok()).toBe(false);
111
- if (!result.ok()) {
112
- expect(result.error.code).toBe('INVALID_STORE_PATH');
113
- }
114
- });
115
-
116
- it('should return INVALID_STORE_PATH error for empty path', () => {
117
- const result = validateStorePath('', 'my-store');
118
- expect(result.ok()).toBe(false);
119
- if (!result.ok()) {
120
- expect(result.error.code).toBe('INVALID_STORE_PATH');
121
- }
122
- });
123
-
124
- it('should include the store name in the error message', () => {
125
- const result = validateStorePath('relative/path', 'cortex-store');
126
- expect(result.ok()).toBe(false);
127
- if (!result.ok()) {
128
- expect(result.error.message).toContain('cortex-store');
129
- }
130
- });
131
-
132
- it('should return ok for ~ expanded paths that are absolute after resolution', () => {
133
- // validateStorePath uses isAbsolute directly - ~ is NOT expanded here
134
- // so ~/path is NOT absolute according to isAbsolute
135
- const result = validateStorePath('~/memory', 'my-store');
136
- expect(result.ok()).toBe(false);
137
- if (!result.ok()) {
138
- expect(result.error.code).toBe('INVALID_STORE_PATH');
139
- }
140
- });
141
- });
142
-
143
- describe('getDefaultGlobalStorePath', () => {
144
- it('should return an absolute path', () => {
145
- const path = getDefaultGlobalStorePath();
146
- expect(isAbsolute(path)).toBe(true);
147
- });
148
-
149
- it('should end with cortex/memory', () => {
150
- const path = getDefaultGlobalStorePath();
151
- expect(path.endsWith(join('cortex', 'memory'))).toBe(true);
152
- });
153
-
154
- it('should be under the homedir', () => {
155
- const path = getDefaultGlobalStorePath();
156
- expect(path.startsWith(homedir())).toBe(true);
157
- });
158
-
159
- it('should return a consistent value on multiple calls', () => {
160
- const first = getDefaultGlobalStorePath();
161
- const second = getDefaultGlobalStorePath();
162
- expect(first).toBe(second);
163
- });
164
- });
165
-
166
- describe('getDefaultConfigPath', () => {
167
- it('should return an absolute path', () => {
168
- const path = getDefaultConfigPath();
169
- expect(isAbsolute(path)).toBe(true);
170
- });
171
-
172
- it('should end with cortex/config.yaml', () => {
173
- const path = getDefaultConfigPath();
174
- expect(path.endsWith(join('cortex', 'config.yaml'))).toBe(true);
175
- });
176
-
177
- it('should be under the homedir', () => {
178
- const path = getDefaultConfigPath();
179
- expect(path.startsWith(homedir())).toBe(true);
180
- });
181
-
182
- it('should return a consistent value on multiple calls', () => {
183
- const first = getDefaultConfigPath();
184
- const second = getDefaultConfigPath();
185
- expect(first).toBe(second);
186
- });
187
-
188
- it('should prefer CORTEX_CONFIG when set', () => {
189
- const previous = process.env.CORTEX_CONFIG;
190
- const previousDir = process.env.CORTEX_CONFIG_DIR;
191
- process.env.CORTEX_CONFIG = '/tmp/custom-config.yaml';
192
- process.env.CORTEX_CONFIG_DIR = '/tmp/ignored-dir';
193
-
194
- const path = getDefaultConfigPath();
195
-
196
- if (previous === undefined) {
197
- Reflect.deleteProperty(process.env, 'CORTEX_CONFIG');
198
- }
199
- else {
200
- process.env.CORTEX_CONFIG = previous;
201
- }
202
- if (previousDir === undefined) {
203
- Reflect.deleteProperty(process.env, 'CORTEX_CONFIG_DIR');
204
- }
205
- else {
206
- process.env.CORTEX_CONFIG_DIR = previousDir;
207
- }
208
-
209
- expect(path).toBe('/tmp/custom-config.yaml');
210
- });
211
-
212
- it('should use CORTEX_CONFIG_DIR when CORTEX_CONFIG is unset', () => {
213
- const previous = process.env.CORTEX_CONFIG;
214
- const previousDir = process.env.CORTEX_CONFIG_DIR;
215
- Reflect.deleteProperty(process.env, 'CORTEX_CONFIG');
216
- process.env.CORTEX_CONFIG_DIR = '/tmp/custom-config-dir';
217
-
218
- const path = getDefaultConfigPath();
219
-
220
- if (previous === undefined) {
221
- Reflect.deleteProperty(process.env, 'CORTEX_CONFIG');
222
- }
223
- else {
224
- process.env.CORTEX_CONFIG = previous;
225
- }
226
- if (previousDir === undefined) {
227
- Reflect.deleteProperty(process.env, 'CORTEX_CONFIG_DIR');
228
- }
229
- else {
230
- process.env.CORTEX_CONFIG_DIR = previousDir;
231
- }
232
-
233
- expect(path).toBe(join('/tmp/custom-config-dir', 'config.yaml'));
234
- });
235
- });
@@ -1,75 +0,0 @@
1
- /**
2
- * Path resolution utilities for CLI commands.
3
- *
4
- * Provides cross-platform path handling including:
5
- * - Home directory (~) expansion
6
- * - Absolute path detection (Unix, Windows drive, UNC)
7
- * - Relative path resolution
8
- */
9
-
10
- import { homedir } from 'node:os';
11
- import { resolve } from 'node:path';
12
-
13
- /**
14
- * Checks if a path is absolute.
15
- * Handles Unix paths, Windows drive paths (C:\), and UNC paths (\\server).
16
- */
17
- export function isAbsolutePath(inputPath: string): boolean {
18
- // Unix absolute path
19
- if (inputPath.startsWith('/')) return true;
20
- // Windows drive path (e.g., C:\, D:/)
21
- if (/^[a-zA-Z]:[/\\]/.test(inputPath)) return true;
22
- // UNC path (e.g., \\server\share, //server/share)
23
- if (inputPath.startsWith('\\\\') || inputPath.startsWith('//')) return true;
24
- return false;
25
- }
26
-
27
- /**
28
- * Resolves a user-provided path to an absolute path.
29
- *
30
- * Handles:
31
- * - Home directory expansion (~)
32
- * - Absolute paths (returned as-is, normalized)
33
- * - Relative paths (resolved against cwd)
34
- *
35
- * @param inputPath - The path to resolve
36
- * @param cwd - Current working directory for relative path resolution
37
- * @returns Normalized absolute path
38
- */
39
- export function resolveUserPath(inputPath: string, cwd: string): string {
40
- // Expand home directory
41
- if (inputPath.startsWith('~')) {
42
- const home = homedir();
43
- return resolve(home, inputPath.slice(1).replace(/^[/\\]/, ''));
44
- }
45
- // Already absolute
46
- if (isAbsolutePath(inputPath)) {
47
- return resolve(inputPath);
48
- }
49
- // Relative to cwd
50
- return resolve(cwd, inputPath);
51
- }
52
-
53
- /**
54
- * Default path to the global store.
55
- */
56
- export const getDefaultGlobalStorePath = (): string =>
57
- resolve(homedir(), '.config', 'cortex', 'memory');
58
-
59
- /**
60
- * Default path to the store configuration file.
61
- * Respects CORTEX_CONFIG (config file path) and CORTEX_CONFIG_DIR (config directory)
62
- * environment variables when set.
63
- */
64
- export const getDefaultConfigPath = (): string => {
65
- const envConfigPath = process.env.CORTEX_CONFIG;
66
- if (envConfigPath) {
67
- return resolve(envConfigPath);
68
- }
69
-
70
- const envConfigDir = process.env.CORTEX_CONFIG_DIR;
71
- if (envConfigDir) {
72
- return resolve(envConfigDir, 'config.yaml');
73
- }
74
- return resolve(homedir(), '.config', 'cortex', 'config.yaml');
75
- };
@@ -1,23 +0,0 @@
1
- import { describe, it, expect } from 'bun:test';
2
- import { isTTY } from './prompts.ts';
3
-
4
- describe('isTTY', () => {
5
- it('should return true when stream.isTTY is true', () => {
6
- const stream = { isTTY: true } as unknown as NodeJS.ReadStream;
7
- expect(isTTY(stream)).toBe(true);
8
- });
9
-
10
- it('should return false when stream.isTTY is false', () => {
11
- const stream = { isTTY: false } as unknown as NodeJS.ReadStream;
12
- expect(isTTY(stream)).toBe(false);
13
- });
14
-
15
- it('should return false when stream.isTTY is undefined', () => {
16
- const stream = {} as unknown as NodeJS.ReadStream;
17
- expect(isTTY(stream)).toBe(false);
18
- });
19
-
20
- it('should return false when stream is undefined', () => {
21
- expect(isTTY(undefined)).toBe(false);
22
- });
23
- });
@@ -1,88 +0,0 @@
1
- /**
2
- * Shared prompt utilities for interactive CLI commands.
3
- *
4
- * Provides TTY detection and injectable prompt dependencies (`PromptDeps`) so
5
- * command handlers remain fully testable without spawning real terminals.
6
- *
7
- * Interactive mode activates automatically when stdin is a TTY (same heuristic
8
- * as git and npm). In non-TTY environments (CI, pipes, scripts) the prompts are
9
- * simply skipped.
10
- *
11
- * @module cli/prompts
12
- */
13
-
14
- import { input, confirm } from '@inquirer/prompts';
15
-
16
- /**
17
- * Async function that prompts the user for a text value.
18
- *
19
- * @param opts - Prompt options
20
- * @param opts.message - The prompt message displayed to the user
21
- * @param opts.default - The default value shown in the prompt
22
- * @returns A promise resolving to the user's input or the default
23
- */
24
- export type InputFn = (opts: { message: string; default?: string }) => Promise<string>;
25
-
26
- /**
27
- * Async function that prompts the user for a boolean confirmation.
28
- *
29
- * @param opts - Prompt options
30
- * @param opts.message - The prompt message displayed to the user
31
- * @param opts.default - The default answer (true = yes, false = no)
32
- * @returns A promise resolving to the user's answer
33
- */
34
- export type ConfirmFn = (opts: { message: string; default?: boolean }) => Promise<boolean>;
35
-
36
- /**
37
- * Injectable dependencies for interactive prompts.
38
- *
39
- * Pass real functions (from `@inquirer/prompts`) in production and stub
40
- * implementations in tests to avoid blocking on terminal input.
41
- *
42
- * @example
43
- * ```typescript
44
- * // Production
45
- * const deps = defaultPromptDeps;
46
- *
47
- * // Test stub
48
- * const deps: PromptDeps = {
49
- * input: async ({ default: d }) => d ?? 'test-value',
50
- * confirm: async () => true,
51
- * };
52
- * ```
53
- */
54
- export interface PromptDeps {
55
- input: InputFn;
56
- confirm: ConfirmFn;
57
- }
58
-
59
- /**
60
- * Default prompt dependencies backed by `@inquirer/prompts`.
61
- * Use this in production; inject stubs in tests.
62
- */
63
- export const defaultPromptDeps: PromptDeps = { input, confirm };
64
-
65
- /**
66
- * Checks whether the given readable stream is an interactive terminal (TTY).
67
- *
68
- * Returns `true` only when `stream.isTTY === true`. Returns `false` for:
69
- * - `undefined` stream
70
- * - streams without an `isTTY` property (e.g. `PassThrough`)
71
- * - streams where `isTTY` is `false`
72
- *
73
- * This mirrors the same heuristic used by git and npm for auto-detecting
74
- * interactive mode.
75
- *
76
- * @param stream - The readable stream to check (usually `ctx.stdin`)
77
- * @returns `true` if the stream is a TTY, `false` otherwise
78
- *
79
- * @example
80
- * ```typescript
81
- * if (isTTY(ctx.stdin)) {
82
- * // Show interactive prompts
83
- * }
84
- * ```
85
- */
86
- export function isTTY(stream: NodeJS.ReadableStream | undefined): boolean {
87
- return (stream as NodeJS.ReadStream | undefined)?.isTTY === true;
88
- }
@@ -1,135 +0,0 @@
1
- /**
2
- * Tests for resolveDefaultStore utility.
3
- *
4
- * @module cli/utils/resolve-default-store.spec
5
- */
6
-
7
- import { describe, it, expect } from 'bun:test';
8
- import { resolveDefaultStore } from './resolve-default-store.ts';
9
- import type { CortexContext } from '@yeseh/cortex-core';
10
-
11
- // Minimal mock context factory
12
- function makeCtx(
13
- overrides: {
14
- cwd?: string;
15
- stores?: Record<string, { properties?: Record<string, unknown> }>;
16
- defaultStore?: string;
17
- } = {},
18
- ): CortexContext {
19
- return {
20
- cwd: overrides.cwd ?? '/home/user/myproject',
21
- stores: (overrides.stores ?? {}) as CortexContext['stores'],
22
- settings: { defaultStore: overrides.defaultStore },
23
- cortex: {} as CortexContext['cortex'],
24
- config: {} as CortexContext['config'],
25
- now: () => new Date(),
26
- stdin: process.stdin,
27
- stdout: process.stdout,
28
- };
29
- }
30
-
31
- describe('resolveDefaultStore', () => {
32
- it('should return explicit store when provided', () => {
33
- const ctx = makeCtx({ stores: { mystore: { properties: { path: '/some/path' } } } });
34
- expect(resolveDefaultStore(ctx, 'mystore')).toBe('mystore');
35
- });
36
-
37
- it('should return explicit store even when a local store exists', () => {
38
- const ctx = makeCtx({
39
- cwd: '/home/user/myproject',
40
- stores: {
41
- local: { properties: { path: '/home/user/myproject/.cortex' } },
42
- },
43
- });
44
- expect(resolveDefaultStore(ctx, 'explicit')).toBe('explicit');
45
- });
46
-
47
- it('should auto-detect local store whose path matches <cwd>/.cortex', () => {
48
- const ctx = makeCtx({
49
- cwd: '/home/user/myproject',
50
- stores: {
51
- global: { properties: { path: '/home/user/.config/cortex/memory' } },
52
- myproject: { properties: { path: '/home/user/myproject/.cortex' } },
53
- },
54
- });
55
- expect(resolveDefaultStore(ctx, undefined)).toBe('myproject');
56
- });
57
-
58
- it('should auto-detect local store whose path matches <cwd>/.cortex/memory', () => {
59
- const ctx = makeCtx({
60
- cwd: '/home/user/myproject',
61
- stores: {
62
- global: { properties: { path: '/home/user/.config/cortex/memory' } },
63
- myproject: { properties: { path: '/home/user/myproject/.cortex/memory' } },
64
- },
65
- });
66
- expect(resolveDefaultStore(ctx, undefined)).toBe('myproject');
67
- });
68
-
69
- it('should not match a store whose path only starts with cwd (not an exact .cortex path)', () => {
70
- // e.g. cwd is /home/user/proj and a store has /home/user/proj-other/.cortex
71
- const ctx = makeCtx({
72
- cwd: '/home/user/proj',
73
- stores: {
74
- other: { properties: { path: '/home/user/proj-other/.cortex' } },
75
- global: { properties: { path: '/home/user/.config/cortex/memory' } },
76
- },
77
- defaultStore: 'global',
78
- });
79
- expect(resolveDefaultStore(ctx, undefined)).toBe('global');
80
- });
81
-
82
- it('should fall back to settings.defaultStore when no local store matches', () => {
83
- const ctx = makeCtx({
84
- cwd: '/home/user/myproject',
85
- stores: {
86
- custom: { properties: { path: '/data/custom' } },
87
- },
88
- defaultStore: 'custom',
89
- });
90
- expect(resolveDefaultStore(ctx, undefined)).toBe('custom');
91
- });
92
-
93
- it('should fall back to "global" when no local store and no defaultStore', () => {
94
- const ctx = makeCtx({
95
- cwd: '/home/user/myproject',
96
- stores: {
97
- other: { properties: { path: '/data/other' } },
98
- },
99
- });
100
- expect(resolveDefaultStore(ctx, undefined)).toBe('global');
101
- });
102
-
103
- it('should fall back to "global" when stores is empty', () => {
104
- const ctx = makeCtx({ cwd: '/home/user/myproject', stores: {} });
105
- expect(resolveDefaultStore(ctx, undefined)).toBe('global');
106
- });
107
-
108
- it('should handle stores with missing properties gracefully', () => {
109
- const ctx = makeCtx({
110
- cwd: '/home/user/myproject',
111
- stores: {
112
- broken: {} as { properties?: Record<string, unknown> },
113
- },
114
- });
115
- expect(resolveDefaultStore(ctx, undefined)).toBe('global');
116
- });
117
-
118
- it('should use process.cwd() when ctx.cwd is not set', () => {
119
- // Build a ctx with no cwd — store path matches process.cwd()
120
- const cwd = process.cwd();
121
- const ctx = {
122
- stores: {
123
- local: { properties: { path: `${cwd}/.cortex` } },
124
- } as unknown as CortexContext['stores'],
125
- settings: {},
126
- cortex: {} as CortexContext['cortex'],
127
- config: {} as CortexContext['config'],
128
- now: () => new Date(),
129
- stdin: process.stdin,
130
- stdout: process.stdout,
131
- // cwd intentionally omitted
132
- } as CortexContext;
133
- expect(resolveDefaultStore(ctx, undefined)).toBe('local');
134
- });
135
- });
@@ -1,74 +0,0 @@
1
- /**
2
- * Utility for resolving which store to target when no `--store` flag is given.
3
- *
4
- * Resolution order (first match wins):
5
- * 1. Explicit store name provided by the caller (from `--store` flag)
6
- * 2. Local store – a registered store whose path starts with `<cwd>/.cortex`
7
- * 3. `settings.defaultStore` from the Cortex config file
8
- * 4. Hard-coded fallback: `"global"`
9
- *
10
- * @module cli/utils/resolve-default-store
11
- *
12
- * @example
13
- * ```typescript
14
- * // In a command handler:
15
- * const storeName = resolveDefaultStore(ctx, parentOpts?.store);
16
- * const storeResult = ctx.cortex.getStore(storeName);
17
- * ```
18
- */
19
-
20
- import { join } from 'node:path';
21
- import type { CortexContext } from '@yeseh/cortex-core';
22
-
23
- /**
24
- * Resolves the effective store name for a command invocation.
25
- *
26
- * When a user runs a command without `--store` in a project directory where
27
- * they have run `cortex store init`, the local store registered for that
28
- * directory is automatically selected so they don't have to type `--store`
29
- * on every command.
30
- *
31
- * @param ctx - The current Cortex context (provides `stores`, `settings`, `cwd`)
32
- * @param explicit - Store name from the `--store` CLI flag (may be undefined)
33
- * @returns The resolved store name to use
34
- *
35
- * @example
36
- * ```typescript
37
- * // No --store flag, inside /home/user/my-project with a .cortex store:
38
- * resolveDefaultStore(ctx, undefined);
39
- * // → "my-project" (the store registered at /home/user/my-project/.cortex)
40
- *
41
- * // Explicit flag always wins:
42
- * resolveDefaultStore(ctx, "global");
43
- * // → "global"
44
- * ```
45
- */
46
- export function resolveDefaultStore(ctx: CortexContext, explicit: string | undefined): string {
47
- // 1. Explicit --store flag wins
48
- if (explicit) return explicit;
49
-
50
- const cwd = ctx.cwd ?? process.cwd();
51
- const stores = ctx.stores ?? {};
52
-
53
- // 2. Local store – registered store whose path is the `.cortex` dir in cwd
54
- // Both `.cortex` and `.cortex/memory` are accepted to handle both
55
- // naming conventions in use across the project.
56
- const localPaths = [
57
- join(cwd, '.cortex'), join(cwd, '.cortex', 'memory'),
58
- ];
59
- for (const [
60
- name, store,
61
- ] of Object.entries(stores)) {
62
- const storePath = store.properties?.path as string | undefined;
63
- if (storePath && localPaths.includes(storePath)) {
64
- return name;
65
- }
66
- }
67
-
68
- // 3. settings.defaultStore from config
69
- const defaultStore = ctx.settings?.defaultStore;
70
- if (defaultStore) return defaultStore;
71
-
72
- // 4. Hard-coded fallback
73
- return 'global';
74
- }