@yeseh/cortex-cli 0.6.8 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/program.js +1538 -5
- package/dist/program.js.map +32 -3
- package/dist/run.d.ts +0 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +3 -4
- package/dist/run.js.map +3 -3
- package/package.json +4 -6
- package/dist/chunk-dsfj4baj.js +0 -1543
- package/dist/chunk-dsfj4baj.js.map +0 -38
- package/src/category/commands/create.spec.ts +0 -139
- package/src/category/commands/create.ts +0 -119
- package/src/category/index.ts +0 -24
- package/src/commands/init.spec.ts +0 -203
- package/src/commands/init.ts +0 -301
- package/src/context.spec.ts +0 -60
- package/src/context.ts +0 -170
- package/src/errors.spec.ts +0 -264
- package/src/errors.ts +0 -105
- package/src/memory/commands/add.spec.ts +0 -169
- package/src/memory/commands/add.ts +0 -158
- package/src/memory/commands/definitions.spec.ts +0 -80
- package/src/memory/commands/list.spec.ts +0 -123
- package/src/memory/commands/list.ts +0 -269
- package/src/memory/commands/move.spec.ts +0 -85
- package/src/memory/commands/move.ts +0 -119
- package/src/memory/commands/remove.spec.ts +0 -79
- package/src/memory/commands/remove.ts +0 -108
- package/src/memory/commands/show.spec.ts +0 -71
- package/src/memory/commands/show.ts +0 -165
- package/src/memory/commands/test-helpers.spec.ts +0 -127
- package/src/memory/commands/update.spec.ts +0 -86
- package/src/memory/commands/update.ts +0 -230
- package/src/memory/index.spec.ts +0 -59
- package/src/memory/index.ts +0 -44
- package/src/memory/parsing.spec.ts +0 -105
- package/src/memory/parsing.ts +0 -22
- package/src/observability.spec.ts +0 -126
- package/src/observability.ts +0 -82
- package/src/output.spec.ts +0 -835
- package/src/output.ts +0 -119
- package/src/program.spec.ts +0 -46
- package/src/program.ts +0 -75
- package/src/run.spec.ts +0 -31
- package/src/run.ts +0 -9
- package/src/store/commands/add.spec.ts +0 -131
- package/src/store/commands/add.ts +0 -231
- package/src/store/commands/init.spec.ts +0 -220
- package/src/store/commands/init.ts +0 -272
- package/src/store/commands/list.spec.ts +0 -175
- package/src/store/commands/list.ts +0 -102
- package/src/store/commands/prune.spec.ts +0 -120
- package/src/store/commands/prune.ts +0 -152
- package/src/store/commands/reindexs.spec.ts +0 -94
- package/src/store/commands/reindexs.ts +0 -97
- package/src/store/commands/remove.spec.ts +0 -97
- package/src/store/commands/remove.ts +0 -189
- package/src/store/index.spec.ts +0 -60
- package/src/store/index.ts +0 -49
- package/src/store/utils/resolve-store-name.spec.ts +0 -62
- package/src/store/utils/resolve-store-name.ts +0 -79
- package/src/test-helpers.spec.ts +0 -430
- package/src/tests/cli.integration.spec.ts +0 -1306
- package/src/toon.spec.ts +0 -183
- package/src/toon.ts +0 -462
- package/src/utils/git.spec.ts +0 -95
- package/src/utils/git.ts +0 -51
- package/src/utils/input.spec.ts +0 -326
- package/src/utils/input.ts +0 -150
- package/src/utils/paths.spec.ts +0 -235
- package/src/utils/paths.ts +0 -75
- package/src/utils/prompts.spec.ts +0 -23
- package/src/utils/prompts.ts +0 -88
- package/src/utils/resolve-default-store.spec.ts +0 -135
- package/src/utils/resolve-default-store.ts +0 -74
package/src/utils/paths.spec.ts
DELETED
|
@@ -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
|
-
});
|
package/src/utils/paths.ts
DELETED
|
@@ -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
|
-
});
|
package/src/utils/prompts.ts
DELETED
|
@@ -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
|
-
}
|