@yeseh/cortex-cli 0.6.0 → 0.6.4

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 (145) hide show
  1. package/package.json +3 -3
  2. package/src/category/commands/create.ts +7 -3
  3. package/src/context.ts +15 -20
  4. package/src/memory/commands/add.ts +2 -1
  5. package/src/memory/commands/list.ts +2 -1
  6. package/src/memory/commands/move.ts +6 -2
  7. package/src/memory/commands/remove.ts +6 -2
  8. package/src/memory/commands/show.ts +3 -2
  9. package/src/memory/commands/update.ts +2 -1
  10. package/src/observability.spec.ts +8 -21
  11. package/src/observability.ts +26 -7
  12. package/src/program.ts +1 -1
  13. package/src/run.ts +0 -0
  14. package/src/store/commands/init.spec.ts +62 -78
  15. package/src/store/commands/init.ts +31 -15
  16. package/src/store/commands/prune.ts +4 -4
  17. package/src/store/commands/reindexs.ts +4 -3
  18. package/src/tests/cli.integration.spec.ts +136 -0
  19. package/src/utils/input.ts +9 -4
  20. package/src/utils/resolve-default-store.spec.ts +135 -0
  21. package/src/utils/resolve-default-store.ts +74 -0
  22. package/dist/category/commands/create.d.ts +0 -44
  23. package/dist/category/commands/create.d.ts.map +0 -1
  24. package/dist/category/commands/create.spec.d.ts +0 -7
  25. package/dist/category/commands/create.spec.d.ts.map +0 -1
  26. package/dist/category/index.d.ts +0 -19
  27. package/dist/category/index.d.ts.map +0 -1
  28. package/dist/commands/init.d.ts +0 -58
  29. package/dist/commands/init.d.ts.map +0 -1
  30. package/dist/commands/init.spec.d.ts +0 -2
  31. package/dist/commands/init.spec.d.ts.map +0 -1
  32. package/dist/context.d.ts +0 -18
  33. package/dist/context.d.ts.map +0 -1
  34. package/dist/context.spec.d.ts +0 -2
  35. package/dist/context.spec.d.ts.map +0 -1
  36. package/dist/create-cli-command.d.ts +0 -23
  37. package/dist/create-cli-command.d.ts.map +0 -1
  38. package/dist/create-cli-command.spec.d.ts +0 -10
  39. package/dist/create-cli-command.spec.d.ts.map +0 -1
  40. package/dist/errors.d.ts +0 -57
  41. package/dist/errors.d.ts.map +0 -1
  42. package/dist/errors.spec.d.ts +0 -2
  43. package/dist/errors.spec.d.ts.map +0 -1
  44. package/dist/input.d.ts +0 -42
  45. package/dist/input.d.ts.map +0 -1
  46. package/dist/input.spec.d.ts +0 -2
  47. package/dist/input.spec.d.ts.map +0 -1
  48. package/dist/memory/commands/add.d.ts +0 -62
  49. package/dist/memory/commands/add.d.ts.map +0 -1
  50. package/dist/memory/commands/add.spec.d.ts +0 -7
  51. package/dist/memory/commands/add.spec.d.ts.map +0 -1
  52. package/dist/memory/commands/definitions.spec.d.ts +0 -10
  53. package/dist/memory/commands/definitions.spec.d.ts.map +0 -1
  54. package/dist/memory/commands/handlers.spec.d.ts +0 -2
  55. package/dist/memory/commands/handlers.spec.d.ts.map +0 -1
  56. package/dist/memory/commands/list.d.ts +0 -119
  57. package/dist/memory/commands/list.d.ts.map +0 -1
  58. package/dist/memory/commands/list.spec.d.ts +0 -2
  59. package/dist/memory/commands/list.spec.d.ts.map +0 -1
  60. package/dist/memory/commands/move.d.ts +0 -42
  61. package/dist/memory/commands/move.d.ts.map +0 -1
  62. package/dist/memory/commands/move.spec.d.ts +0 -2
  63. package/dist/memory/commands/move.spec.d.ts.map +0 -1
  64. package/dist/memory/commands/remove.d.ts +0 -41
  65. package/dist/memory/commands/remove.d.ts.map +0 -1
  66. package/dist/memory/commands/remove.spec.d.ts +0 -2
  67. package/dist/memory/commands/remove.spec.d.ts.map +0 -1
  68. package/dist/memory/commands/show.d.ts +0 -81
  69. package/dist/memory/commands/show.d.ts.map +0 -1
  70. package/dist/memory/commands/show.spec.d.ts +0 -2
  71. package/dist/memory/commands/show.spec.d.ts.map +0 -1
  72. package/dist/memory/commands/test-helpers.spec.d.ts +0 -19
  73. package/dist/memory/commands/test-helpers.spec.d.ts.map +0 -1
  74. package/dist/memory/commands/update.d.ts +0 -73
  75. package/dist/memory/commands/update.d.ts.map +0 -1
  76. package/dist/memory/commands/update.spec.d.ts +0 -2
  77. package/dist/memory/commands/update.spec.d.ts.map +0 -1
  78. package/dist/memory/index.d.ts +0 -29
  79. package/dist/memory/index.d.ts.map +0 -1
  80. package/dist/memory/index.spec.d.ts +0 -10
  81. package/dist/memory/index.spec.d.ts.map +0 -1
  82. package/dist/memory/parsing.d.ts +0 -3
  83. package/dist/memory/parsing.d.ts.map +0 -1
  84. package/dist/memory/parsing.spec.d.ts +0 -7
  85. package/dist/memory/parsing.spec.d.ts.map +0 -1
  86. package/dist/output.d.ts +0 -87
  87. package/dist/output.d.ts.map +0 -1
  88. package/dist/output.spec.d.ts +0 -2
  89. package/dist/output.spec.d.ts.map +0 -1
  90. package/dist/paths.d.ts +0 -27
  91. package/dist/paths.d.ts.map +0 -1
  92. package/dist/paths.spec.d.ts +0 -7
  93. package/dist/paths.spec.d.ts.map +0 -1
  94. package/dist/program.d.ts +0 -41
  95. package/dist/program.d.ts.map +0 -1
  96. package/dist/program.spec.d.ts +0 -11
  97. package/dist/program.spec.d.ts.map +0 -1
  98. package/dist/run.d.ts +0 -7
  99. package/dist/run.d.ts.map +0 -1
  100. package/dist/run.spec.d.ts +0 -12
  101. package/dist/run.spec.d.ts.map +0 -1
  102. package/dist/store/commands/add.d.ts +0 -73
  103. package/dist/store/commands/add.d.ts.map +0 -1
  104. package/dist/store/commands/add.spec.d.ts +0 -17
  105. package/dist/store/commands/add.spec.d.ts.map +0 -1
  106. package/dist/store/commands/init.d.ts +0 -75
  107. package/dist/store/commands/init.d.ts.map +0 -1
  108. package/dist/store/commands/init.spec.d.ts +0 -7
  109. package/dist/store/commands/init.spec.d.ts.map +0 -1
  110. package/dist/store/commands/list.d.ts +0 -62
  111. package/dist/store/commands/list.d.ts.map +0 -1
  112. package/dist/store/commands/list.spec.d.ts +0 -7
  113. package/dist/store/commands/list.spec.d.ts.map +0 -1
  114. package/dist/store/commands/prune.d.ts +0 -92
  115. package/dist/store/commands/prune.d.ts.map +0 -1
  116. package/dist/store/commands/prune.spec.d.ts +0 -7
  117. package/dist/store/commands/prune.spec.d.ts.map +0 -1
  118. package/dist/store/commands/reindexs.d.ts +0 -54
  119. package/dist/store/commands/reindexs.d.ts.map +0 -1
  120. package/dist/store/commands/reindexs.spec.d.ts +0 -7
  121. package/dist/store/commands/reindexs.spec.d.ts.map +0 -1
  122. package/dist/store/commands/remove.d.ts +0 -63
  123. package/dist/store/commands/remove.d.ts.map +0 -1
  124. package/dist/store/commands/remove.spec.d.ts +0 -17
  125. package/dist/store/commands/remove.spec.d.ts.map +0 -1
  126. package/dist/store/index.d.ts +0 -32
  127. package/dist/store/index.d.ts.map +0 -1
  128. package/dist/store/index.spec.d.ts +0 -9
  129. package/dist/store/index.spec.d.ts.map +0 -1
  130. package/dist/store/utils/resolve-store-name.d.ts +0 -30
  131. package/dist/store/utils/resolve-store-name.d.ts.map +0 -1
  132. package/dist/store/utils/resolve-store-name.spec.d.ts +0 -2
  133. package/dist/store/utils/resolve-store-name.spec.d.ts.map +0 -1
  134. package/dist/test-helpers.spec.d.ts +0 -224
  135. package/dist/test-helpers.spec.d.ts.map +0 -1
  136. package/dist/tests/cli.integration.spec.d.ts +0 -11
  137. package/dist/tests/cli.integration.spec.d.ts.map +0 -1
  138. package/dist/toon.d.ts +0 -197
  139. package/dist/toon.d.ts.map +0 -1
  140. package/dist/toon.spec.d.ts +0 -9
  141. package/dist/toon.spec.d.ts.map +0 -1
  142. package/dist/utils/git.d.ts +0 -20
  143. package/dist/utils/git.d.ts.map +0 -1
  144. package/dist/utils/git.spec.d.ts +0 -7
  145. 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.0",
3
+ "version": "0.6.4",
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": "0.6.0",
38
- "@yeseh/cortex-storage-fs": "0.6.0",
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({ kind: 'created-category', value: payload }, rawFormat as OutputFormat);
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 ?? 'global'));
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 found. Available stores: ${Object.keys(stores).join(', ')}`,
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(`Store '${storeName}' has no path configured in properties.`);
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: settingsResult.value,
125
- stores: storesResult.value,
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 ?? 'global');
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 ?? 'global');
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 ?? 'global');
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
- { kind: 'moved-memory', value: { from: fromResult.value.toString(), to: toResult.value.toString() } },
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 ?? 'global');
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({kind: 'path', value: { path: pathResult.value.toString() }}, format);
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 ?? 'global');
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 ?? 'global');
152
+ const storeResult = ctx.cortex.getStore(resolveDefaultStore(ctx, storeName));
152
153
  if (!storeResult.ok()) {
153
154
  throwCliError(storeResult.error);
154
155
  }
@@ -27,22 +27,17 @@ describe('createCliLogger', () => {
27
27
  });
28
28
 
29
29
  describe('info()', () => {
30
- it('should write info log to stderr as JSON', () => {
30
+ it('should write human-friendly info log to stderr', () => {
31
31
  const logger = createCliLogger();
32
32
  logger.info('hello world');
33
33
  expect(stderrLines.length).toBe(1);
34
- const parsed = JSON.parse(stderrLines[0]!);
35
- expect(parsed.level).toBe('info');
36
- expect(parsed.msg).toBe('hello world');
37
- expect(parsed.ts).toBeDefined();
34
+ expect(stderrLines[0]).toBe('INFO: hello world\n');
38
35
  });
39
36
 
40
37
  it('should include metadata in the log line', () => {
41
38
  const logger = createCliLogger();
42
39
  logger.info('test', { store: 'default', count: 5 });
43
- const parsed = JSON.parse(stderrLines[0]!);
44
- expect(parsed.store).toBe('default');
45
- expect(parsed.count).toBe(5);
40
+ expect(stderrLines[0]).toBe('INFO: test store=default count=5\n');
46
41
  });
47
42
  });
48
43
 
@@ -51,9 +46,7 @@ describe('createCliLogger', () => {
51
46
  const logger = createCliLogger();
52
47
  logger.warn('warning message');
53
48
  expect(stderrLines.length).toBe(1);
54
- const parsed = JSON.parse(stderrLines[0]!);
55
- expect(parsed.level).toBe('warn');
56
- expect(parsed.msg).toBe('warning message');
49
+ expect(stderrLines[0]).toBe('WARN: warning message\n');
57
50
  });
58
51
  });
59
52
 
@@ -62,25 +55,20 @@ describe('createCliLogger', () => {
62
55
  const logger = createCliLogger();
63
56
  logger.error('something failed', new Error('boom'));
64
57
  expect(stderrLines.length).toBe(1);
65
- const parsed = JSON.parse(stderrLines[0]!);
66
- expect(parsed.level).toBe('error');
67
- expect(parsed.msg).toBe('something failed');
68
- expect(parsed.error).toBe('boom');
58
+ expect(stderrLines[0]).toBe('ERROR: something failed error=boom\n');
69
59
  });
70
60
 
71
61
  it('should handle string error argument', () => {
72
62
  const logger = createCliLogger();
73
63
  logger.error('failed', 'string error');
74
- const parsed = JSON.parse(stderrLines[0]!);
75
- expect(parsed.error).toBe('string error');
64
+ expect(stderrLines[0]).toBe('ERROR: failed error="string error"\n');
76
65
  });
77
66
 
78
67
  it('should handle missing error argument', () => {
79
68
  const logger = createCliLogger();
80
69
  logger.error('failed');
81
70
  expect(stderrLines.length).toBe(1);
82
- const parsed = JSON.parse(stderrLines[0]!);
83
- expect(parsed.error).toBeUndefined();
71
+ expect(stderrLines[0]).toBe('ERROR: failed\n');
84
72
  });
85
73
  });
86
74
 
@@ -97,8 +85,7 @@ describe('createCliLogger', () => {
97
85
  const logger = createCliLogger();
98
86
  logger.debug('debug message');
99
87
  expect(stderrLines.length).toBe(1);
100
- const parsed = JSON.parse(stderrLines[0]!);
101
- expect(parsed.level).toBe('debug');
88
+ expect(stderrLines[0]).toBe('DEBUG: debug message\n');
102
89
  });
103
90
 
104
91
  it('should write debug output when DEBUG includes cortex alongside other values', () => {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * CLI observability — plain ConsoleLogger writing structured JSON to stderr.
2
+ * CLI observability — plain ConsoleLogger writing human-readable lines to stderr.
3
3
  *
4
4
  * No OTel SDK dependency — keeps the CLI binary small.
5
5
  * Debug output is gated by the `DEBUG=cortex` environment variable.
@@ -11,7 +11,7 @@ import type { Logger } from '@yeseh/cortex-core';
11
11
  /**
12
12
  * Creates a plain console logger for CLI usage.
13
13
  *
14
- * Writes structured JSON log lines to stderr (not stdout) to avoid
14
+ * Writes human-readable log lines to stderr (not stdout) to avoid
15
15
  * polluting piped command output. Debug output is gated by the
16
16
  * `DEBUG=cortex` environment variable.
17
17
  *
@@ -21,7 +21,7 @@ import type { Logger } from '@yeseh/cortex-core';
21
21
  * ```typescript
22
22
  * const logger = createCliLogger();
23
23
  * logger.info('Starting command', { store: 'global' });
24
- * // → {"ts":"2024-01-01T00:00:00.000Z","level":"info","msg":"Starting command","store":"global"}
24
+ * // → INFO: Starting command store=global
25
25
  * ```
26
26
  *
27
27
  * @example
@@ -34,10 +34,27 @@ export const createCliLogger = (): Logger => {
34
34
  const debugEnabled =
35
35
  typeof process.env.DEBUG === 'string' && process.env.DEBUG.includes('cortex');
36
36
 
37
+ const stringifyMetaValue = (value: unknown): string => {
38
+ if (typeof value === 'string') {
39
+ return value.includes(' ') ? JSON.stringify(value) : value;
40
+ }
41
+ if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
42
+ return String(value);
43
+ }
44
+ return JSON.stringify(value);
45
+ };
46
+
47
+ const formatMeta = (meta?: Record<string, unknown>): string => {
48
+ if (!meta || Object.keys(meta).length === 0) return '';
49
+ return Object.entries(meta)
50
+ .map(([key, value]) => `${key}=${stringifyMetaValue(value)}`)
51
+ .join(' ');
52
+ };
53
+
37
54
  const write = (level: string, msg: string, meta?: Record<string, unknown>): void => {
38
- process.stderr.write(
39
- JSON.stringify({ ts: new Date().toISOString(), level, msg, ...meta }) + '\n'
40
- );
55
+ const line = `${level.toUpperCase()}: ${msg}`;
56
+ const metaText = formatMeta(meta);
57
+ process.stderr.write(metaText.length > 0 ? `${line} ${metaText}\n` : `${line}\n`);
41
58
  };
42
59
 
43
60
  return {
@@ -53,7 +70,9 @@ export const createCliLogger = (): Logger => {
53
70
  error(msg: string, err?: Error | unknown, meta?: Record<string, unknown>): void {
54
71
  const errMeta =
55
72
  err instanceof Error
56
- ? { error: err.message, stack: err.stack }
73
+ ? debugEnabled
74
+ ? { error: err.message, stack: err.stack }
75
+ : { error: err.message }
57
76
  : err !== null && err !== undefined
58
77
  ? { error: String(err) }
59
78
  : {};
package/src/program.ts CHANGED
@@ -65,7 +65,7 @@ export const runProgram = async (): Promise<void> => {
65
65
  // This catch handles any unexpected errors that slip through.
66
66
  const logger = createCliLogger();
67
67
  if (error instanceof Error) {
68
- logger.error(`Error: ${error.message}`, error);
68
+ logger.error(error.message);
69
69
  }
70
70
  else {
71
71
  logger.error('An unexpected error occurred');
package/src/run.ts CHANGED
File without changes