@yeseh/cortex-cli 0.6.0 → 0.6.3
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/package.json +3 -3
- package/src/category/commands/create.ts +7 -3
- package/src/context.ts +15 -20
- package/src/memory/commands/add.ts +2 -1
- package/src/memory/commands/list.ts +2 -1
- package/src/memory/commands/move.ts +6 -2
- package/src/memory/commands/remove.ts +6 -2
- package/src/memory/commands/show.ts +3 -2
- package/src/memory/commands/update.ts +2 -1
- package/src/run.ts +0 -0
- package/src/store/commands/init.spec.ts +62 -78
- package/src/store/commands/init.ts +31 -15
- package/src/store/commands/prune.ts +4 -4
- package/src/store/commands/reindexs.ts +4 -3
- package/src/tests/cli.integration.spec.ts +136 -0
- package/src/utils/input.ts +9 -4
- package/src/utils/resolve-default-store.spec.ts +135 -0
- package/src/utils/resolve-default-store.ts +74 -0
- package/dist/category/commands/create.d.ts +0 -44
- package/dist/category/commands/create.d.ts.map +0 -1
- package/dist/category/commands/create.spec.d.ts +0 -7
- package/dist/category/commands/create.spec.d.ts.map +0 -1
- package/dist/category/index.d.ts +0 -19
- package/dist/category/index.d.ts.map +0 -1
- package/dist/commands/init.d.ts +0 -58
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.spec.d.ts +0 -2
- package/dist/commands/init.spec.d.ts.map +0 -1
- package/dist/context.d.ts +0 -18
- package/dist/context.d.ts.map +0 -1
- package/dist/context.spec.d.ts +0 -2
- package/dist/context.spec.d.ts.map +0 -1
- package/dist/create-cli-command.d.ts +0 -23
- package/dist/create-cli-command.d.ts.map +0 -1
- package/dist/create-cli-command.spec.d.ts +0 -10
- package/dist/create-cli-command.spec.d.ts.map +0 -1
- package/dist/errors.d.ts +0 -57
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.spec.d.ts +0 -2
- package/dist/errors.spec.d.ts.map +0 -1
- package/dist/input.d.ts +0 -42
- package/dist/input.d.ts.map +0 -1
- package/dist/input.spec.d.ts +0 -2
- package/dist/input.spec.d.ts.map +0 -1
- package/dist/memory/commands/add.d.ts +0 -62
- package/dist/memory/commands/add.d.ts.map +0 -1
- package/dist/memory/commands/add.spec.d.ts +0 -7
- package/dist/memory/commands/add.spec.d.ts.map +0 -1
- package/dist/memory/commands/definitions.spec.d.ts +0 -10
- package/dist/memory/commands/definitions.spec.d.ts.map +0 -1
- package/dist/memory/commands/handlers.spec.d.ts +0 -2
- package/dist/memory/commands/handlers.spec.d.ts.map +0 -1
- package/dist/memory/commands/list.d.ts +0 -119
- package/dist/memory/commands/list.d.ts.map +0 -1
- package/dist/memory/commands/list.spec.d.ts +0 -2
- package/dist/memory/commands/list.spec.d.ts.map +0 -1
- package/dist/memory/commands/move.d.ts +0 -42
- package/dist/memory/commands/move.d.ts.map +0 -1
- package/dist/memory/commands/move.spec.d.ts +0 -2
- package/dist/memory/commands/move.spec.d.ts.map +0 -1
- package/dist/memory/commands/remove.d.ts +0 -41
- package/dist/memory/commands/remove.d.ts.map +0 -1
- package/dist/memory/commands/remove.spec.d.ts +0 -2
- package/dist/memory/commands/remove.spec.d.ts.map +0 -1
- package/dist/memory/commands/show.d.ts +0 -81
- package/dist/memory/commands/show.d.ts.map +0 -1
- package/dist/memory/commands/show.spec.d.ts +0 -2
- package/dist/memory/commands/show.spec.d.ts.map +0 -1
- package/dist/memory/commands/test-helpers.spec.d.ts +0 -19
- package/dist/memory/commands/test-helpers.spec.d.ts.map +0 -1
- package/dist/memory/commands/update.d.ts +0 -73
- package/dist/memory/commands/update.d.ts.map +0 -1
- package/dist/memory/commands/update.spec.d.ts +0 -2
- package/dist/memory/commands/update.spec.d.ts.map +0 -1
- package/dist/memory/index.d.ts +0 -29
- package/dist/memory/index.d.ts.map +0 -1
- package/dist/memory/index.spec.d.ts +0 -10
- package/dist/memory/index.spec.d.ts.map +0 -1
- package/dist/memory/parsing.d.ts +0 -3
- package/dist/memory/parsing.d.ts.map +0 -1
- package/dist/memory/parsing.spec.d.ts +0 -7
- package/dist/memory/parsing.spec.d.ts.map +0 -1
- package/dist/output.d.ts +0 -87
- package/dist/output.d.ts.map +0 -1
- package/dist/output.spec.d.ts +0 -2
- package/dist/output.spec.d.ts.map +0 -1
- package/dist/paths.d.ts +0 -27
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.spec.d.ts +0 -7
- package/dist/paths.spec.d.ts.map +0 -1
- package/dist/program.d.ts +0 -41
- package/dist/program.d.ts.map +0 -1
- package/dist/program.spec.d.ts +0 -11
- package/dist/program.spec.d.ts.map +0 -1
- package/dist/run.d.ts +0 -7
- package/dist/run.d.ts.map +0 -1
- package/dist/run.spec.d.ts +0 -12
- package/dist/run.spec.d.ts.map +0 -1
- package/dist/store/commands/add.d.ts +0 -73
- package/dist/store/commands/add.d.ts.map +0 -1
- package/dist/store/commands/add.spec.d.ts +0 -17
- package/dist/store/commands/add.spec.d.ts.map +0 -1
- package/dist/store/commands/init.d.ts +0 -75
- package/dist/store/commands/init.d.ts.map +0 -1
- package/dist/store/commands/init.spec.d.ts +0 -7
- package/dist/store/commands/init.spec.d.ts.map +0 -1
- package/dist/store/commands/list.d.ts +0 -62
- package/dist/store/commands/list.d.ts.map +0 -1
- package/dist/store/commands/list.spec.d.ts +0 -7
- package/dist/store/commands/list.spec.d.ts.map +0 -1
- package/dist/store/commands/prune.d.ts +0 -92
- package/dist/store/commands/prune.d.ts.map +0 -1
- package/dist/store/commands/prune.spec.d.ts +0 -7
- package/dist/store/commands/prune.spec.d.ts.map +0 -1
- package/dist/store/commands/reindexs.d.ts +0 -54
- package/dist/store/commands/reindexs.d.ts.map +0 -1
- package/dist/store/commands/reindexs.spec.d.ts +0 -7
- package/dist/store/commands/reindexs.spec.d.ts.map +0 -1
- package/dist/store/commands/remove.d.ts +0 -63
- package/dist/store/commands/remove.d.ts.map +0 -1
- package/dist/store/commands/remove.spec.d.ts +0 -17
- package/dist/store/commands/remove.spec.d.ts.map +0 -1
- package/dist/store/index.d.ts +0 -32
- package/dist/store/index.d.ts.map +0 -1
- package/dist/store/index.spec.d.ts +0 -9
- package/dist/store/index.spec.d.ts.map +0 -1
- package/dist/store/utils/resolve-store-name.d.ts +0 -30
- package/dist/store/utils/resolve-store-name.d.ts.map +0 -1
- package/dist/store/utils/resolve-store-name.spec.d.ts +0 -2
- package/dist/store/utils/resolve-store-name.spec.d.ts.map +0 -1
- package/dist/test-helpers.spec.d.ts +0 -224
- package/dist/test-helpers.spec.d.ts.map +0 -1
- package/dist/tests/cli.integration.spec.d.ts +0 -11
- package/dist/tests/cli.integration.spec.d.ts.map +0 -1
- package/dist/toon.d.ts +0 -197
- package/dist/toon.d.ts.map +0 -1
- package/dist/toon.spec.d.ts +0 -9
- package/dist/toon.spec.d.ts.map +0 -1
- package/dist/utils/git.d.ts +0 -20
- package/dist/utils/git.d.ts.map +0 -1
- package/dist/utils/git.spec.d.ts +0 -7
- package/dist/utils/git.spec.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeseh/cortex-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"typescript": "^5"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@yeseh/cortex-core": "
|
|
38
|
-
"@yeseh/cortex-storage-fs": "
|
|
37
|
+
"@yeseh/cortex-core": "workspace:*",
|
|
38
|
+
"@yeseh/cortex-storage-fs": "workspace:*",
|
|
39
39
|
"@commander-js/extra-typings": "^14.0.0",
|
|
40
40
|
"@inquirer/prompts": "^8.0.0",
|
|
41
41
|
"@toon-format/toon": "^1.0.0",
|
|
@@ -22,6 +22,7 @@ import { type CortexContext, type Result } from '@yeseh/cortex-core';
|
|
|
22
22
|
import { createCliCommandContext } from '../../context.ts';
|
|
23
23
|
import { throwCliError } from '../../errors.ts';
|
|
24
24
|
import { serializeOutput, type OutputFormat } from '../../output.ts';
|
|
25
|
+
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
25
26
|
|
|
26
27
|
/** Options parsed by Commander for the create command */
|
|
27
28
|
export interface CreateCommandOptions {
|
|
@@ -39,7 +40,7 @@ export interface CreateCommandOptions {
|
|
|
39
40
|
function writeCreateOutput(
|
|
40
41
|
payload: { path: string; created: boolean },
|
|
41
42
|
options: CreateCommandOptions,
|
|
42
|
-
stdout: NodeJS.WritableStream
|
|
43
|
+
stdout: NodeJS.WritableStream
|
|
43
44
|
): void {
|
|
44
45
|
const rawFormat = options.format;
|
|
45
46
|
|
|
@@ -49,7 +50,10 @@ function writeCreateOutput(
|
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
const serialized = serializeOutput(
|
|
53
|
+
const serialized = serializeOutput(
|
|
54
|
+
{ kind: 'created-category', value: payload },
|
|
55
|
+
rawFormat as OutputFormat
|
|
56
|
+
);
|
|
53
57
|
if (!serialized.ok()) {
|
|
54
58
|
throwCliError({ code: 'SERIALIZE_FAILED', message: serialized.error.message });
|
|
55
59
|
}
|
|
@@ -85,7 +89,7 @@ export async function handleCreate(
|
|
|
85
89
|
path: string,
|
|
86
90
|
options: CreateCommandOptions = {}
|
|
87
91
|
): Promise<void> {
|
|
88
|
-
const store = unwrapOrThrow(ctx.cortex.getStore(storeName
|
|
92
|
+
const store = unwrapOrThrow(ctx.cortex.getStore(resolveDefaultStore(ctx, storeName)));
|
|
89
93
|
const root = unwrapOrThrow(store.root());
|
|
90
94
|
const category = unwrapOrThrow(root.getCategory(path));
|
|
91
95
|
const result = unwrapOrThrow(await category.create());
|
package/src/context.ts
CHANGED
|
@@ -24,7 +24,7 @@ const makeAbsolute = (pathStr: string): string => {
|
|
|
24
24
|
|
|
25
25
|
export const validateStorePath = (
|
|
26
26
|
storePath: string,
|
|
27
|
-
storeName: string
|
|
27
|
+
storeName: string
|
|
28
28
|
): Result<void, ConfigValidationError> => {
|
|
29
29
|
if (!isAbsolute(storePath)) {
|
|
30
30
|
return err({
|
|
@@ -65,14 +65,19 @@ export const createCliAdapterFactory = (configAdapter: FilesystemConfigAdapter)
|
|
|
65
65
|
const stores = configAdapter.stores!;
|
|
66
66
|
const storeEntry = stores[storeName];
|
|
67
67
|
if (!storeEntry) {
|
|
68
|
+
const available = Object.keys(stores).join(', ') || 'none';
|
|
68
69
|
throw new Error(
|
|
69
|
-
`Store '${storeName}' not
|
|
70
|
+
`Store '${storeName}' is not registered. Available stores: ${available}. ` +
|
|
71
|
+
'Use --store to specify a registered store, or run `cortex store init` to create one.'
|
|
70
72
|
);
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
const storePath = storeEntry.properties?.path as string | undefined;
|
|
74
76
|
if (!storePath) {
|
|
75
|
-
throw new Error(
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Store '${storeName}' has no path configured. ` +
|
|
79
|
+
'Check your cortex config or re-run `cortex store init`.'
|
|
80
|
+
);
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
return new FilesystemStorageAdapter(configAdapter, {
|
|
@@ -82,10 +87,11 @@ export const createCliAdapterFactory = (configAdapter: FilesystemConfigAdapter)
|
|
|
82
87
|
};
|
|
83
88
|
|
|
84
89
|
export const createCliConfigContext = async (
|
|
85
|
-
options: CliContextOptions = {}
|
|
90
|
+
options: CliContextOptions = {}
|
|
86
91
|
): Promise<Result<CliConfigContext, any>> => {
|
|
87
92
|
const envConfigPath = process.env.CORTEX_CONFIG;
|
|
88
93
|
const envConfigDir = process.env.CORTEX_CONFIG_DIR;
|
|
94
|
+
const envConfigCwd = process.env.CORTEX_CONFIG_CWD;
|
|
89
95
|
|
|
90
96
|
const explicitConfigPath =
|
|
91
97
|
typeof envConfigPath === 'string' && envConfigPath.length > 0
|
|
@@ -93,10 +99,10 @@ export const createCliConfigContext = async (
|
|
|
93
99
|
: undefined;
|
|
94
100
|
|
|
95
101
|
const dir = options.configDir ?? envConfigDir ?? resolve(homedir(), '.config', 'cortex');
|
|
102
|
+
|
|
96
103
|
const absoluteDir = makeAbsolute(dir);
|
|
97
104
|
const configPath = explicitConfigPath ?? resolve(absoluteDir, 'config.yaml');
|
|
98
105
|
|
|
99
|
-
const envConfigCwd = process.env.CORTEX_CONFIG_CWD;
|
|
100
106
|
const effectiveCwd =
|
|
101
107
|
options.configCwd ??
|
|
102
108
|
(typeof envConfigCwd === 'string' && envConfigCwd.length > 0
|
|
@@ -109,20 +115,10 @@ export const createCliConfigContext = async (
|
|
|
109
115
|
return initResult;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
const settingsResult = await configAdapter.getSettings();
|
|
113
|
-
if (!settingsResult.ok()) {
|
|
114
|
-
return settingsResult;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const storesResult = await configAdapter.getStores();
|
|
118
|
-
if (!storesResult.ok()) {
|
|
119
|
-
return storesResult;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
118
|
return ok({
|
|
123
119
|
configAdapter,
|
|
124
|
-
settings:
|
|
125
|
-
stores:
|
|
120
|
+
settings: configAdapter.settings!,
|
|
121
|
+
stores: configAdapter.stores!,
|
|
126
122
|
effectiveCwd,
|
|
127
123
|
});
|
|
128
124
|
};
|
|
@@ -131,7 +127,7 @@ export const createCliConfigContext = async (
|
|
|
131
127
|
* This function is used to create a context object that can be injected into command handlers for consistent access to the Cortex client and other utilities.
|
|
132
128
|
*/
|
|
133
129
|
export const createCliCommandContext = async (
|
|
134
|
-
configDir?: string
|
|
130
|
+
configDir?: string
|
|
135
131
|
): Promise<Result<CortexContext, any>> => {
|
|
136
132
|
try {
|
|
137
133
|
const configContextResult = await createCliConfigContext({
|
|
@@ -165,8 +161,7 @@ export const createCliCommandContext = async (
|
|
|
165
161
|
};
|
|
166
162
|
|
|
167
163
|
return ok(context);
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
164
|
+
} catch (error) {
|
|
170
165
|
return err({
|
|
171
166
|
code: 'CONTEXT_CREATION_FAILED',
|
|
172
167
|
message: `Unexpected error creating CLI command context: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -28,6 +28,7 @@ import { resolveInput as resolveCliContent } from '../../utils/input.ts';
|
|
|
28
28
|
import { parseExpiresAt, parseTags } from '../parsing.ts';
|
|
29
29
|
import { createCliCommandContext } from '../../context.ts';
|
|
30
30
|
import { serializeOutput, type OutputFormat } from '../../output.ts';
|
|
31
|
+
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
31
32
|
|
|
32
33
|
/** Options parsed by Commander for the add command */
|
|
33
34
|
export interface AddCommandOptions {
|
|
@@ -77,7 +78,7 @@ export async function handleAdd(
|
|
|
77
78
|
const expiresAt = parseExpiresAt(options.expiresAt);
|
|
78
79
|
const citations = options.citations ?? [];
|
|
79
80
|
|
|
80
|
-
const storeResult = ctx.cortex.getStore(storeName
|
|
81
|
+
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
81
82
|
if (!storeResult.ok()) {
|
|
82
83
|
throwCliError(storeResult.error);
|
|
83
84
|
}
|
|
@@ -36,6 +36,7 @@ import { serialize, type CortexContext } from '@yeseh/cortex-core';
|
|
|
36
36
|
import type { SubcategoryEntry } from '@yeseh/cortex-core/category';
|
|
37
37
|
import { type OutputFormat } from '../../output.ts';
|
|
38
38
|
import { createCliCommandContext } from '../../context.ts';
|
|
39
|
+
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Options for the list command.
|
|
@@ -116,7 +117,7 @@ export async function handleList(
|
|
|
116
117
|
throwCliError(categoryResult.error);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
const storeResult = ctx.cortex.getStore(storeName
|
|
120
|
+
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
120
121
|
if (!storeResult.ok()) {
|
|
121
122
|
throwCliError(storeResult.error);
|
|
122
123
|
}
|
|
@@ -17,6 +17,7 @@ import { throwCliError } from '../../errors.ts';
|
|
|
17
17
|
import { MemoryPath, type CortexContext } from '@yeseh/cortex-core';
|
|
18
18
|
import { createCliCommandContext } from '../../context.ts';
|
|
19
19
|
import { serializeOutput, type OutputFormat } from '../../output.ts';
|
|
20
|
+
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
20
21
|
|
|
21
22
|
/** Options for the move command. */
|
|
22
23
|
export interface MoveCommandOptions {
|
|
@@ -50,7 +51,7 @@ export async function handleMove(
|
|
|
50
51
|
throwCliError(toResult.error);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
const storeResult = ctx.cortex.getStore(storeName
|
|
54
|
+
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
54
55
|
if (!storeResult.ok()) {
|
|
55
56
|
throwCliError(storeResult.error);
|
|
56
57
|
}
|
|
@@ -81,7 +82,10 @@ export async function handleMove(
|
|
|
81
82
|
} else {
|
|
82
83
|
const format = rawFormat as OutputFormat;
|
|
83
84
|
const serialized = serializeOutput(
|
|
84
|
-
{
|
|
85
|
+
{
|
|
86
|
+
kind: 'moved-memory',
|
|
87
|
+
value: { from: fromResult.value.toString(), to: toResult.value.toString() },
|
|
88
|
+
},
|
|
85
89
|
format
|
|
86
90
|
);
|
|
87
91
|
if (!serialized.ok()) {
|
|
@@ -18,6 +18,7 @@ import { throwCliError } from '../../errors.ts';
|
|
|
18
18
|
import { MemoryPath, type CortexContext } from '@yeseh/cortex-core';
|
|
19
19
|
import { createCliCommandContext } from '../../context.ts';
|
|
20
20
|
import { serializeOutput, type OutputFormat } from '../../output.ts';
|
|
21
|
+
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
21
22
|
|
|
22
23
|
/** Options for the remove command. */
|
|
23
24
|
export interface RemoveCommandOptions {
|
|
@@ -44,7 +45,7 @@ export async function handleRemove(
|
|
|
44
45
|
throwCliError(pathResult.error);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
const storeResult = ctx.cortex.getStore(storeName
|
|
48
|
+
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
48
49
|
if (!storeResult.ok()) {
|
|
49
50
|
throwCliError(storeResult.error);
|
|
50
51
|
}
|
|
@@ -74,7 +75,10 @@ export async function handleRemove(
|
|
|
74
75
|
out.write(`Removed memory ${pathResult.value.toString()}.\n`);
|
|
75
76
|
} else {
|
|
76
77
|
const format = rawFormat as OutputFormat;
|
|
77
|
-
const serialized = serializeOutput(
|
|
78
|
+
const serialized = serializeOutput(
|
|
79
|
+
{ kind: 'path', value: { path: pathResult.value.toString() } },
|
|
80
|
+
format
|
|
81
|
+
);
|
|
78
82
|
if (!serialized.ok()) {
|
|
79
83
|
throwCliError({ code: 'SERIALIZE_FAILED', message: serialized.error.message });
|
|
80
84
|
}
|
|
@@ -28,6 +28,7 @@ import { defaultTokenizer, MemoryPath, type CortexContext } from '@yeseh/cortex-
|
|
|
28
28
|
import { type StoreClient } from '@yeseh/cortex-core/store';
|
|
29
29
|
import { serializeOutput, type OutputMemory, type OutputFormat } from '../../output.ts';
|
|
30
30
|
import { createCliCommandContext } from '../../context.ts';
|
|
31
|
+
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Options for the show command.
|
|
@@ -80,7 +81,7 @@ export async function handleShow(
|
|
|
80
81
|
throwCliError(pathResult.error);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
const storeResult = ctx.cortex.getStore(storeName
|
|
84
|
+
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
84
85
|
if (!storeResult.ok()) {
|
|
85
86
|
throwCliError(storeResult.error);
|
|
86
87
|
}
|
|
@@ -126,7 +127,7 @@ export async function handleShow(
|
|
|
126
127
|
};
|
|
127
128
|
|
|
128
129
|
const format = (options.format as OutputFormat) ?? 'yaml';
|
|
129
|
-
const serialized = serializeOutput({kind: 'memory', value: outputMemory}, format);
|
|
130
|
+
const serialized = serializeOutput({ kind: 'memory', value: outputMemory }, format);
|
|
130
131
|
if (!serialized.ok()) {
|
|
131
132
|
throwCliError({ code: 'SERIALIZE_FAILED', message: serialized.error.message });
|
|
132
133
|
}
|
|
@@ -33,6 +33,7 @@ import { resolveInput as resolveCliContent } from '../../utils/input.ts';
|
|
|
33
33
|
import { parseExpiresAt, parseTags } from '../parsing.ts';
|
|
34
34
|
import { createCliCommandContext } from '../../context.ts';
|
|
35
35
|
import { serializeOutput, type OutputFormat } from '../../output.ts';
|
|
36
|
+
import { resolveDefaultStore } from '../../utils/resolve-default-store.ts';
|
|
36
37
|
|
|
37
38
|
/** Options parsed by Commander for the update command */
|
|
38
39
|
export interface UpdateCommandOptions {
|
|
@@ -148,7 +149,7 @@ export async function handleUpdate(
|
|
|
148
149
|
const expiresAt = parseUpdateExpiresAt(options.expiresAt);
|
|
149
150
|
const updates = buildUpdates(content, tags, expiresAt, options.citation);
|
|
150
151
|
|
|
151
|
-
const storeResult = ctx.cortex.getStore(storeName
|
|
152
|
+
const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
|
|
152
153
|
if (!storeResult.ok()) {
|
|
153
154
|
throwCliError(storeResult.error);
|
|
154
155
|
}
|
package/src/run.ts
CHANGED
|
File without changes
|
|
@@ -12,28 +12,25 @@ import { join } from 'node:path';
|
|
|
12
12
|
import type { PromptDeps } from '../../utils/prompts.ts';
|
|
13
13
|
|
|
14
14
|
import { handleInit } from './init.ts';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
saveStore: async () => ({ ok: () => true as const, value: undefined }),
|
|
35
|
-
} as any,
|
|
36
|
-
});
|
|
15
|
+
import { createMockContext, captureOutput } from '../../test-helpers.spec.ts';
|
|
16
|
+
import { FilesystemConfigAdapter } from '@yeseh/cortex-storage-fs';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a real FilesystemConfigAdapter backed by a temp config file and
|
|
20
|
+
* injects it into a mock context so handleInit can construct a real
|
|
21
|
+
* FilesystemStorageAdapter without the adapter factory throwing STORE_NOT_FOUND.
|
|
22
|
+
*/
|
|
23
|
+
async function createInitContext(tempDir: string) {
|
|
24
|
+
const configPath = join(tempDir, 'config.yaml');
|
|
25
|
+
const configAdapter = new FilesystemConfigAdapter(configPath);
|
|
26
|
+
await configAdapter.initializeConfig();
|
|
27
|
+
|
|
28
|
+
const { ctx, stdout, stdin } = createMockContext({ cwd: tempDir });
|
|
29
|
+
// Replace the mock ConfigAdapter with a real one so FilesystemStorageAdapter
|
|
30
|
+
// construction inside handleInit works correctly.
|
|
31
|
+
(ctx as unknown as Record<string, unknown>).config = configAdapter;
|
|
32
|
+
|
|
33
|
+
return { ctx, stdout, stdin };
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
describe('handleInit', () => {
|
|
@@ -48,10 +45,7 @@ describe('handleInit', () => {
|
|
|
48
45
|
});
|
|
49
46
|
|
|
50
47
|
it('should initialize a store and write success message to stdout', async () => {
|
|
51
|
-
const { ctx, stdout } =
|
|
52
|
-
adapter: createInitAdapter(),
|
|
53
|
-
cwd: tempDir,
|
|
54
|
-
});
|
|
48
|
+
const { ctx, stdout } = await createInitContext(tempDir);
|
|
55
49
|
|
|
56
50
|
await handleInit(ctx, undefined, { name: 'my-project', format: 'yaml' });
|
|
57
51
|
|
|
@@ -61,10 +55,7 @@ describe('handleInit', () => {
|
|
|
61
55
|
|
|
62
56
|
it('should use the provided target path as the store path', async () => {
|
|
63
57
|
const customPath = join(tempDir, 'custom-store');
|
|
64
|
-
const { ctx, stdout } =
|
|
65
|
-
adapter: createInitAdapter(),
|
|
66
|
-
cwd: tempDir,
|
|
67
|
-
});
|
|
58
|
+
const { ctx, stdout } = await createInitContext(tempDir);
|
|
68
59
|
|
|
69
60
|
await handleInit(ctx, customPath, { name: 'my-project', format: 'yaml' });
|
|
70
61
|
|
|
@@ -73,10 +64,7 @@ describe('handleInit', () => {
|
|
|
73
64
|
});
|
|
74
65
|
|
|
75
66
|
it('should default to .cortex under cwd when no target path is given', async () => {
|
|
76
|
-
const { ctx, stdout } =
|
|
77
|
-
adapter: createInitAdapter(),
|
|
78
|
-
cwd: tempDir,
|
|
79
|
-
});
|
|
67
|
+
const { ctx, stdout } = await createInitContext(tempDir);
|
|
80
68
|
|
|
81
69
|
await handleInit(ctx, undefined, { name: 'my-project', format: 'yaml' });
|
|
82
70
|
|
|
@@ -86,10 +74,7 @@ describe('handleInit', () => {
|
|
|
86
74
|
});
|
|
87
75
|
|
|
88
76
|
it('should throw InvalidArgumentError for an invalid store name', async () => {
|
|
89
|
-
const { ctx } =
|
|
90
|
-
adapter: createInitAdapter(),
|
|
91
|
-
cwd: tempDir,
|
|
92
|
-
});
|
|
77
|
+
const { ctx } = await createInitContext(tempDir);
|
|
93
78
|
|
|
94
79
|
await expect(handleInit(ctx, undefined, { name: ' ', format: 'yaml' })).rejects.toThrow(
|
|
95
80
|
InvalidArgumentError,
|
|
@@ -97,10 +82,7 @@ describe('handleInit', () => {
|
|
|
97
82
|
});
|
|
98
83
|
|
|
99
84
|
it('should output in JSON format when format option is json', async () => {
|
|
100
|
-
const { ctx, stdout } =
|
|
101
|
-
adapter: createInitAdapter(),
|
|
102
|
-
cwd: tempDir,
|
|
103
|
-
});
|
|
85
|
+
const { ctx, stdout } = await createInitContext(tempDir);
|
|
104
86
|
|
|
105
87
|
await handleInit(ctx, undefined, { name: 'my-project', format: 'json' });
|
|
106
88
|
|
|
@@ -111,10 +93,7 @@ describe('handleInit', () => {
|
|
|
111
93
|
});
|
|
112
94
|
|
|
113
95
|
it('should include the store name in the success output', async () => {
|
|
114
|
-
const { ctx, stdout } =
|
|
115
|
-
adapter: createInitAdapter(),
|
|
116
|
-
cwd: tempDir,
|
|
117
|
-
});
|
|
96
|
+
const { ctx, stdout } = await createInitContext(tempDir);
|
|
118
97
|
|
|
119
98
|
await handleInit(ctx, undefined, { name: 'hello-world', format: 'json' });
|
|
120
99
|
|
|
@@ -124,10 +103,7 @@ describe('handleInit', () => {
|
|
|
124
103
|
});
|
|
125
104
|
|
|
126
105
|
it('should expand tilde in target path', async () => {
|
|
127
|
-
const { ctx, stdout } =
|
|
128
|
-
adapter: createInitAdapter(),
|
|
129
|
-
cwd: tempDir,
|
|
130
|
-
});
|
|
106
|
+
const { ctx, stdout } = await createInitContext(tempDir);
|
|
131
107
|
|
|
132
108
|
// resolveUserPath expands ~ — we just verify it doesn't throw and
|
|
133
109
|
// produces output with a home-like absolute path
|
|
@@ -136,6 +112,19 @@ describe('handleInit', () => {
|
|
|
136
112
|
const out = captureOutput(stdout);
|
|
137
113
|
expect(out).toContain('my-store');
|
|
138
114
|
});
|
|
115
|
+
|
|
116
|
+
it('should fail when the store name already exists', async () => {
|
|
117
|
+
const { ctx } = await createInitContext(tempDir);
|
|
118
|
+
const storePath = join(tempDir, '.cortex');
|
|
119
|
+
|
|
120
|
+
await handleInit(ctx, storePath, { name: 'duplicate-store', format: 'yaml' });
|
|
121
|
+
|
|
122
|
+
// Second init with the same name should throw
|
|
123
|
+
const { ctx: ctx2 } = await createInitContext(tempDir);
|
|
124
|
+
await expect(
|
|
125
|
+
handleInit(ctx2, join(tempDir, '.cortex2'), { name: 'duplicate-store', format: 'yaml' }),
|
|
126
|
+
).rejects.toThrow();
|
|
127
|
+
});
|
|
139
128
|
});
|
|
140
129
|
|
|
141
130
|
describe('handleInit - interactive mode', () => {
|
|
@@ -150,13 +139,12 @@ describe('handleInit - interactive mode', () => {
|
|
|
150
139
|
});
|
|
151
140
|
|
|
152
141
|
it('should call promptDeps.input once (path only) when stdin is a TTY and --name is explicitly given', async () => {
|
|
153
|
-
const inputMock = mock(
|
|
142
|
+
const inputMock = mock(
|
|
143
|
+
async ({ default: d }: { message: string; default?: string }) => d ?? 'prompted-value',
|
|
144
|
+
);
|
|
154
145
|
const promptDeps: PromptDeps = { input: inputMock, confirm: mock(async () => true) };
|
|
155
146
|
|
|
156
|
-
const { ctx, stdin } =
|
|
157
|
-
adapter: createInitAdapter(),
|
|
158
|
-
cwd: tempDir,
|
|
159
|
-
});
|
|
147
|
+
const { ctx, stdin } = await createInitContext(tempDir);
|
|
160
148
|
(stdin as unknown as { isTTY: boolean }).isTTY = true;
|
|
161
149
|
|
|
162
150
|
await handleInit(ctx, undefined, { name: 'my-project', format: 'yaml' }, promptDeps);
|
|
@@ -167,13 +155,12 @@ describe('handleInit - interactive mode', () => {
|
|
|
167
155
|
});
|
|
168
156
|
|
|
169
157
|
it('should call promptDeps.input twice (name + path) when stdin is a TTY and no --name given', async () => {
|
|
170
|
-
const inputMock = mock(
|
|
158
|
+
const inputMock = mock(
|
|
159
|
+
async ({ default: d }: { message: string; default?: string }) => d ?? 'prompted-value',
|
|
160
|
+
);
|
|
171
161
|
const promptDeps: PromptDeps = { input: inputMock, confirm: mock(async () => true) };
|
|
172
162
|
|
|
173
|
-
const { ctx, stdin } =
|
|
174
|
-
adapter: createInitAdapter(),
|
|
175
|
-
cwd: tempDir,
|
|
176
|
-
});
|
|
163
|
+
const { ctx, stdin } = await createInitContext(tempDir);
|
|
177
164
|
(stdin as unknown as { isTTY: boolean }).isTTY = true;
|
|
178
165
|
|
|
179
166
|
// No --name provided, should prompt for both name and path
|
|
@@ -183,13 +170,12 @@ describe('handleInit - interactive mode', () => {
|
|
|
183
170
|
});
|
|
184
171
|
|
|
185
172
|
it('should NOT call promptDeps.input when stdin is NOT a TTY', async () => {
|
|
186
|
-
const inputMock = mock(
|
|
173
|
+
const inputMock = mock(
|
|
174
|
+
async ({ default: d }: { message: string; default?: string }) => d ?? 'prompted-value',
|
|
175
|
+
);
|
|
187
176
|
const promptDeps: PromptDeps = { input: inputMock, confirm: mock(async () => true) };
|
|
188
177
|
|
|
189
|
-
const { ctx } =
|
|
190
|
-
adapter: createInitAdapter(),
|
|
191
|
-
cwd: tempDir,
|
|
192
|
-
});
|
|
178
|
+
const { ctx } = await createInitContext(tempDir);
|
|
193
179
|
// ctx stdin has isTTY = undefined (non-TTY)
|
|
194
180
|
|
|
195
181
|
await handleInit(ctx, undefined, { name: 'my-project', format: 'yaml' }, promptDeps);
|
|
@@ -198,13 +184,12 @@ describe('handleInit - interactive mode', () => {
|
|
|
198
184
|
});
|
|
199
185
|
|
|
200
186
|
it('should skip both prompts when --name and target path are both given explicitly and TTY', async () => {
|
|
201
|
-
const inputMock = mock(
|
|
187
|
+
const inputMock = mock(
|
|
188
|
+
async ({ default: d }: { message: string; default?: string }) => d ?? 'prompted-value',
|
|
189
|
+
);
|
|
202
190
|
const promptDeps: PromptDeps = { input: inputMock, confirm: mock(async () => true) };
|
|
203
191
|
|
|
204
|
-
const { ctx, stdin } =
|
|
205
|
-
adapter: createInitAdapter(),
|
|
206
|
-
cwd: tempDir,
|
|
207
|
-
});
|
|
192
|
+
const { ctx, stdin } = await createInitContext(tempDir);
|
|
208
193
|
(stdin as unknown as { isTTY: boolean }).isTTY = true;
|
|
209
194
|
const customPath = join(tempDir, 'custom-store');
|
|
210
195
|
|
|
@@ -215,16 +200,15 @@ describe('handleInit - interactive mode', () => {
|
|
|
215
200
|
|
|
216
201
|
it('should use prompted store name in the output', async () => {
|
|
217
202
|
const promptedName = 'prompted-store-name';
|
|
218
|
-
const inputMock = mock(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
203
|
+
const inputMock = mock(
|
|
204
|
+
async ({ message, default: d }: { message: string; default?: string }) => {
|
|
205
|
+
if (message.toLowerCase().includes('name')) return promptedName;
|
|
206
|
+
return d ?? 'default-path';
|
|
207
|
+
},
|
|
208
|
+
);
|
|
222
209
|
const promptDeps: PromptDeps = { input: inputMock, confirm: mock(async () => true) };
|
|
223
210
|
|
|
224
|
-
const { ctx, stdin, stdout } =
|
|
225
|
-
adapter: createInitAdapter(),
|
|
226
|
-
cwd: tempDir,
|
|
227
|
-
});
|
|
211
|
+
const { ctx, stdin, stdout } = await createInitContext(tempDir);
|
|
228
212
|
(stdin as unknown as { isTTY: boolean }).isTTY = true;
|
|
229
213
|
|
|
230
214
|
await handleInit(ctx, undefined, { format: 'json' }, promptDeps);
|
|
@@ -24,14 +24,17 @@
|
|
|
24
24
|
|
|
25
25
|
import { Command } from '@commander-js/extra-typings';
|
|
26
26
|
import { resolve } from 'node:path';
|
|
27
|
+
import { stdin, stdout as processStdout } from 'node:process';
|
|
27
28
|
import { resolveStoreName } from '../utils/resolve-store-name.ts';
|
|
28
29
|
import { throwCliError } from '../../errors.ts';
|
|
29
|
-
import { type StoreData } from '@yeseh/cortex-core/store';
|
|
30
|
+
import { type StoreData, initializeStore } from '@yeseh/cortex-core/store';
|
|
30
31
|
import { type CategoryMode, type CortexContext } from '@yeseh/cortex-core';
|
|
31
32
|
import { serializeOutput, type OutputStoreInit, type OutputFormat } from '../../output.ts';
|
|
32
33
|
import { resolveUserPath } from '../../utils/paths.ts';
|
|
33
|
-
import {
|
|
34
|
+
import { createCliConfigContext } from '../../context.ts';
|
|
34
35
|
import { isTTY, defaultPromptDeps, type PromptDeps } from '../../utils/prompts.ts';
|
|
36
|
+
import type { FilesystemConfigAdapter } from '@yeseh/cortex-storage-fs';
|
|
37
|
+
import { FilesystemStorageAdapter } from '@yeseh/cortex-storage-fs';
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
40
|
* Options for the init command.
|
|
@@ -95,7 +98,8 @@ async function promptStoreInitOptions(
|
|
|
95
98
|
let storePath: string;
|
|
96
99
|
if (explicit.path) {
|
|
97
100
|
storePath = resolved.storePath;
|
|
98
|
-
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
99
103
|
const promptedPath = await promptDeps.input({
|
|
100
104
|
message: 'Store path:',
|
|
101
105
|
default: resolved.storePath,
|
|
@@ -123,7 +127,8 @@ async function resolveStoreNameOrEmpty(
|
|
|
123
127
|
): Promise<string> {
|
|
124
128
|
try {
|
|
125
129
|
return await resolveStoreName(cwd, explicitName);
|
|
126
|
-
}
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
127
132
|
// When running in a TTY, only swallow errors (and fall back to prompting)
|
|
128
133
|
// if no explicit name was provided. If the user passed an explicit --name,
|
|
129
134
|
// re-throw so they see the actual invalid-name error.
|
|
@@ -194,11 +199,6 @@ export async function handleInit(
|
|
|
194
199
|
});
|
|
195
200
|
}
|
|
196
201
|
|
|
197
|
-
const clientResult = ctx.cortex.getStore(finalStoreName);
|
|
198
|
-
if (!clientResult.ok()) {
|
|
199
|
-
throwCliError(clientResult.error);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
202
|
const storeData: StoreData = {
|
|
203
203
|
kind: 'filesystem',
|
|
204
204
|
categoryMode: (options.categoryMode as CategoryMode) ?? 'free',
|
|
@@ -209,8 +209,10 @@ export async function handleInit(
|
|
|
209
209
|
description: options.description,
|
|
210
210
|
};
|
|
211
211
|
|
|
212
|
-
const
|
|
213
|
-
|
|
212
|
+
const adapter = new FilesystemStorageAdapter(ctx.config as FilesystemConfigAdapter, {
|
|
213
|
+
rootDirectory: finalStorePath,
|
|
214
|
+
});
|
|
215
|
+
const createResult = await initializeStore(adapter, finalStoreName, storeData);
|
|
214
216
|
if (!createResult.ok()) {
|
|
215
217
|
throwCliError(createResult.error);
|
|
216
218
|
}
|
|
@@ -242,12 +244,26 @@ export const initCommand = new Command('init')
|
|
|
242
244
|
.option('-c, --category-mode <mode>', 'Category mode (free, strict, flat)', 'free')
|
|
243
245
|
.option('-o, --format <format>', 'Output format (yaml, json, toon)', 'yaml')
|
|
244
246
|
.action(async (path, options) => {
|
|
245
|
-
const
|
|
246
|
-
if (!
|
|
247
|
-
throwCliError(
|
|
247
|
+
const configCtx = await createCliConfigContext();
|
|
248
|
+
if (!configCtx.ok()) {
|
|
249
|
+
throwCliError(configCtx.error);
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
|
|
252
|
+
const { configAdapter, effectiveCwd } = configCtx.value;
|
|
253
|
+
|
|
254
|
+
// Build a minimal context for handleInit. The init command cannot go
|
|
255
|
+
// through the full CortexContext/adapterFactory because the store is
|
|
256
|
+
// not yet registered — the adapter factory would throw STORE_NOT_FOUND.
|
|
257
|
+
// handleInit only needs cwd, stdin, stdout, and config (to construct
|
|
258
|
+
// its own adapter directly via initializeStore).
|
|
259
|
+
const ctx = {
|
|
260
|
+
config: configAdapter,
|
|
261
|
+
cwd: effectiveCwd,
|
|
262
|
+
stdin,
|
|
263
|
+
stdout: processStdout,
|
|
264
|
+
} as unknown as CortexContext;
|
|
265
|
+
|
|
266
|
+
await handleInit(ctx, path, {
|
|
251
267
|
name: options.name,
|
|
252
268
|
description: options.description,
|
|
253
269
|
categoryMode: options.categoryMode as CategoryMode,
|