@yeseh/cortex-cli 0.6.3 → 0.6.5

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 (133) hide show
  1. package/dist/category/commands/create.d.ts +44 -0
  2. package/dist/category/commands/create.d.ts.map +1 -0
  3. package/dist/category/commands/create.spec.d.ts +7 -0
  4. package/dist/category/commands/create.spec.d.ts.map +1 -0
  5. package/dist/category/index.d.ts +19 -0
  6. package/dist/category/index.d.ts.map +1 -0
  7. package/dist/commands/init.d.ts +58 -0
  8. package/dist/commands/init.d.ts.map +1 -0
  9. package/dist/commands/init.spec.d.ts +2 -0
  10. package/dist/commands/init.spec.d.ts.map +1 -0
  11. package/dist/context.d.ts +18 -0
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/context.spec.d.ts +2 -0
  14. package/dist/context.spec.d.ts.map +1 -0
  15. package/dist/create-cli-command.d.ts +23 -0
  16. package/dist/create-cli-command.d.ts.map +1 -0
  17. package/dist/create-cli-command.spec.d.ts +10 -0
  18. package/dist/create-cli-command.spec.d.ts.map +1 -0
  19. package/dist/errors.d.ts +57 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.spec.d.ts +2 -0
  22. package/dist/errors.spec.d.ts.map +1 -0
  23. package/dist/input.d.ts +42 -0
  24. package/dist/input.d.ts.map +1 -0
  25. package/dist/input.spec.d.ts +2 -0
  26. package/dist/input.spec.d.ts.map +1 -0
  27. package/dist/memory/commands/add.d.ts +62 -0
  28. package/dist/memory/commands/add.d.ts.map +1 -0
  29. package/dist/memory/commands/add.spec.d.ts +7 -0
  30. package/dist/memory/commands/add.spec.d.ts.map +1 -0
  31. package/dist/memory/commands/definitions.spec.d.ts +10 -0
  32. package/dist/memory/commands/definitions.spec.d.ts.map +1 -0
  33. package/dist/memory/commands/handlers.spec.d.ts +2 -0
  34. package/dist/memory/commands/handlers.spec.d.ts.map +1 -0
  35. package/dist/memory/commands/list.d.ts +119 -0
  36. package/dist/memory/commands/list.d.ts.map +1 -0
  37. package/dist/memory/commands/list.spec.d.ts +2 -0
  38. package/dist/memory/commands/list.spec.d.ts.map +1 -0
  39. package/dist/memory/commands/move.d.ts +42 -0
  40. package/dist/memory/commands/move.d.ts.map +1 -0
  41. package/dist/memory/commands/move.spec.d.ts +2 -0
  42. package/dist/memory/commands/move.spec.d.ts.map +1 -0
  43. package/dist/memory/commands/remove.d.ts +41 -0
  44. package/dist/memory/commands/remove.d.ts.map +1 -0
  45. package/dist/memory/commands/remove.spec.d.ts +2 -0
  46. package/dist/memory/commands/remove.spec.d.ts.map +1 -0
  47. package/dist/memory/commands/show.d.ts +81 -0
  48. package/dist/memory/commands/show.d.ts.map +1 -0
  49. package/dist/memory/commands/show.spec.d.ts +2 -0
  50. package/dist/memory/commands/show.spec.d.ts.map +1 -0
  51. package/dist/memory/commands/test-helpers.spec.d.ts +19 -0
  52. package/dist/memory/commands/test-helpers.spec.d.ts.map +1 -0
  53. package/dist/memory/commands/update.d.ts +73 -0
  54. package/dist/memory/commands/update.d.ts.map +1 -0
  55. package/dist/memory/commands/update.spec.d.ts +2 -0
  56. package/dist/memory/commands/update.spec.d.ts.map +1 -0
  57. package/dist/memory/index.d.ts +29 -0
  58. package/dist/memory/index.d.ts.map +1 -0
  59. package/dist/memory/index.spec.d.ts +10 -0
  60. package/dist/memory/index.spec.d.ts.map +1 -0
  61. package/dist/memory/parsing.d.ts +3 -0
  62. package/dist/memory/parsing.d.ts.map +1 -0
  63. package/dist/memory/parsing.spec.d.ts +7 -0
  64. package/dist/memory/parsing.spec.d.ts.map +1 -0
  65. package/dist/output.d.ts +87 -0
  66. package/dist/output.d.ts.map +1 -0
  67. package/dist/output.spec.d.ts +2 -0
  68. package/dist/output.spec.d.ts.map +1 -0
  69. package/dist/paths.d.ts +27 -0
  70. package/dist/paths.d.ts.map +1 -0
  71. package/dist/paths.spec.d.ts +7 -0
  72. package/dist/paths.spec.d.ts.map +1 -0
  73. package/dist/program.d.ts +41 -0
  74. package/dist/program.d.ts.map +1 -0
  75. package/dist/program.spec.d.ts +11 -0
  76. package/dist/program.spec.d.ts.map +1 -0
  77. package/dist/run.d.ts +7 -0
  78. package/dist/run.d.ts.map +1 -0
  79. package/dist/run.spec.d.ts +12 -0
  80. package/dist/run.spec.d.ts.map +1 -0
  81. package/dist/store/commands/add.d.ts +73 -0
  82. package/dist/store/commands/add.d.ts.map +1 -0
  83. package/dist/store/commands/add.spec.d.ts +17 -0
  84. package/dist/store/commands/add.spec.d.ts.map +1 -0
  85. package/dist/store/commands/init.d.ts +75 -0
  86. package/dist/store/commands/init.d.ts.map +1 -0
  87. package/dist/store/commands/init.spec.d.ts +7 -0
  88. package/dist/store/commands/init.spec.d.ts.map +1 -0
  89. package/dist/store/commands/list.d.ts +62 -0
  90. package/dist/store/commands/list.d.ts.map +1 -0
  91. package/dist/store/commands/list.spec.d.ts +7 -0
  92. package/dist/store/commands/list.spec.d.ts.map +1 -0
  93. package/dist/store/commands/prune.d.ts +92 -0
  94. package/dist/store/commands/prune.d.ts.map +1 -0
  95. package/dist/store/commands/prune.spec.d.ts +7 -0
  96. package/dist/store/commands/prune.spec.d.ts.map +1 -0
  97. package/dist/store/commands/reindexs.d.ts +54 -0
  98. package/dist/store/commands/reindexs.d.ts.map +1 -0
  99. package/dist/store/commands/reindexs.spec.d.ts +7 -0
  100. package/dist/store/commands/reindexs.spec.d.ts.map +1 -0
  101. package/dist/store/commands/remove.d.ts +63 -0
  102. package/dist/store/commands/remove.d.ts.map +1 -0
  103. package/dist/store/commands/remove.spec.d.ts +17 -0
  104. package/dist/store/commands/remove.spec.d.ts.map +1 -0
  105. package/dist/store/index.d.ts +32 -0
  106. package/dist/store/index.d.ts.map +1 -0
  107. package/dist/store/index.spec.d.ts +9 -0
  108. package/dist/store/index.spec.d.ts.map +1 -0
  109. package/dist/store/utils/resolve-store-name.d.ts +30 -0
  110. package/dist/store/utils/resolve-store-name.d.ts.map +1 -0
  111. package/dist/store/utils/resolve-store-name.spec.d.ts +2 -0
  112. package/dist/store/utils/resolve-store-name.spec.d.ts.map +1 -0
  113. package/dist/test-helpers.spec.d.ts +224 -0
  114. package/dist/test-helpers.spec.d.ts.map +1 -0
  115. package/dist/tests/cli.integration.spec.d.ts +11 -0
  116. package/dist/tests/cli.integration.spec.d.ts.map +1 -0
  117. package/dist/toon.d.ts +197 -0
  118. package/dist/toon.d.ts.map +1 -0
  119. package/dist/toon.spec.d.ts +9 -0
  120. package/dist/toon.spec.d.ts.map +1 -0
  121. package/dist/utils/git.d.ts +20 -0
  122. package/dist/utils/git.d.ts.map +1 -0
  123. package/dist/utils/git.spec.d.ts +7 -0
  124. package/dist/utils/git.spec.d.ts.map +1 -0
  125. package/package.json +3 -3
  126. package/src/observability.spec.ts +8 -21
  127. package/src/observability.ts +26 -7
  128. package/src/program.ts +1 -1
  129. package/src/run.ts +0 -0
  130. package/src/tests/cli.integration.spec.ts +79 -270
  131. package/src/utils/input.ts +4 -9
  132. package/src/utils/resolve-default-store.spec.ts +1 -1
  133. package/src/utils/resolve-default-store.ts +2 -6
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Shared test helpers for CLI package unit tests.
3
+ *
4
+ * Exports reusable primitives for result construction, stream capture,
5
+ * clock fixtures, CLI error assertions, and mock adapter/context factories.
6
+ * All other spec files in this package import from this module.
7
+ *
8
+ * @module cli/_test-helpers
9
+ */
10
+ import { PassThrough } from 'node:stream';
11
+ import type { StorageAdapter, MemoryStorage, IndexStorage, CategoryStorage, ConfigStores, CortexContext } from '@yeseh/cortex-core';
12
+ /**
13
+ * Constructs a minimal ok-shaped result for use in mock return values.
14
+ *
15
+ * @param value - The success value
16
+ * @returns A plain object compatible with ok result checks
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const result = okResult('hello');
21
+ * expect(result.ok()).toBe(true);
22
+ * expect(result.value).toBe('hello');
23
+ * ```
24
+ */
25
+ export declare const okResult: <T>(value: T) => {
26
+ ok: () => true;
27
+ value: T;
28
+ };
29
+ /**
30
+ * Constructs a minimal err-shaped result for use in mock return values.
31
+ *
32
+ * @param error - The error value
33
+ * @returns A plain object compatible with err result checks
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const result = errResult({ code: 'NOT_FOUND', message: 'not found' });
38
+ * expect(result.ok()).toBe(false);
39
+ * ```
40
+ */
41
+ export declare const errResult: <E>(error: E) => {
42
+ ok: () => false;
43
+ error: E;
44
+ };
45
+ /**
46
+ * Creates a writable PassThrough stream and a helper to capture written text.
47
+ *
48
+ * @returns An object containing the stream and a `getOutput` function
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const { stream, getOutput } = createWritableCapture();
53
+ * stream.write('hello');
54
+ * expect(getOutput()).toBe('hello');
55
+ * ```
56
+ */
57
+ export declare function createWritableCapture(): {
58
+ stream: PassThrough;
59
+ getOutput: () => string;
60
+ };
61
+ /**
62
+ * Creates a readable PassThrough stream pre-loaded with text.
63
+ *
64
+ * @param text - The text to pre-load into the stream
65
+ * @returns A PassThrough stream with the given text already written
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const stream = createReadableFromText('user input\n');
70
+ * ```
71
+ */
72
+ export declare function createReadableFromText(text: string): PassThrough;
73
+ /** ISO 8601 string for the fixed test clock date */
74
+ export declare const FIXED_NOW_ISO = "2025-06-01T12:00:00.000Z";
75
+ /** Fixed Date instance for use in tests */
76
+ export declare const FIXED_NOW: Date;
77
+ /**
78
+ * Returns a `now` function that always returns the given ISO date.
79
+ *
80
+ * @param iso - ISO 8601 string (defaults to {@link FIXED_NOW_ISO})
81
+ * @returns A zero-argument function returning the fixed Date
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const ctx = createMockContext({ now: fixedNow('2025-01-01T00:00:00.000Z') });
86
+ * ```
87
+ */
88
+ export declare const fixedNow: (iso?: string) => (() => Date);
89
+ /**
90
+ * Asserts that the given function throws an `InvalidArgumentError`.
91
+ *
92
+ * @param fn - Sync or async function expected to throw
93
+ * @param messagePart - Optional substring the error message must contain
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * await expectInvalidArgumentError(
98
+ * () => parseMemoryPath(''),
99
+ * 'must not be empty',
100
+ * );
101
+ * ```
102
+ */
103
+ export declare function expectInvalidArgumentError(fn: () => Promise<unknown> | unknown, messagePart?: string): Promise<void>;
104
+ /**
105
+ * Asserts that the given function throws a `CommanderError`.
106
+ *
107
+ * @param fn - Sync or async function expected to throw
108
+ * @param codePart - Optional substring the error's `.code` must contain
109
+ * @param messagePart - Optional substring the error message must contain
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * await expectCommanderError(
114
+ * () => handleAdd(ctx, args, 'missing-store'),
115
+ * 'commander.storeNotFound',
116
+ * );
117
+ * ```
118
+ */
119
+ export declare function expectCommanderError(fn: () => Promise<unknown> | unknown, codePart?: string, messagePart?: string): Promise<void>;
120
+ /**
121
+ * Creates a minimal mock MemoryStorage with sensible defaults.
122
+ *
123
+ * @param overrides - Partial overrides for individual methods
124
+ * @returns A fully-typed MemoryStorage mock
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const memories = createMockMemoryStorage({
129
+ * load: async () => ok(sampleMemory),
130
+ * });
131
+ * ```
132
+ */
133
+ export declare function createMockMemoryStorage(overrides?: Partial<MemoryStorage>): MemoryStorage;
134
+ /**
135
+ * Creates a minimal mock IndexStorage with sensible defaults.
136
+ *
137
+ * @param overrides - Partial overrides for individual methods
138
+ * @returns A fully-typed IndexStorage mock
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const indexes = createMockIndexStorage({
143
+ * load: async () => ok({ memories: [], subcategories: [] }),
144
+ * });
145
+ * ```
146
+ */
147
+ export declare function createMockIndexStorage(overrides?: Partial<IndexStorage>): IndexStorage;
148
+ /**
149
+ * Creates a minimal mock CategoryStorage with sensible defaults.
150
+ *
151
+ * @param overrides - Partial overrides for individual methods
152
+ * @returns A fully-typed CategoryStorage mock
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * const categories = createMockCategoryStorage({
157
+ * exists: async () => ok(true),
158
+ * });
159
+ * ```
160
+ */
161
+ export declare function createMockCategoryStorage(overrides?: Partial<CategoryStorage>): CategoryStorage;
162
+ /**
163
+ * Creates a minimal mock StorageAdapter composed of mock sub-adapters.
164
+ *
165
+ * @param overrides - Partial overrides for individual sub-adapters
166
+ * @returns A fully-typed StorageAdapter mock
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const adapter = createMockStorageAdapter({
171
+ * memories: createMockMemoryStorage({ load: async () => ok(null) }),
172
+ * });
173
+ * ```
174
+ */
175
+ export declare function createMockStorageAdapter(overrides?: Partial<StorageAdapter>): StorageAdapter;
176
+ /** Options for {@link createMockContext} */
177
+ export interface MockContextOptions {
178
+ /** Override the storage adapter (defaults to {@link createMockStorageAdapter}) */
179
+ adapter?: StorageAdapter;
180
+ /** Override the store configuration (defaults to a single in-memory mock store) */
181
+ stores?: ConfigStores;
182
+ /** Override the clock (defaults to {@link fixedNow}) */
183
+ now?: () => Date;
184
+ /** Override the current working directory */
185
+ cwd?: string;
186
+ }
187
+ /** Result of {@link createMockContext} */
188
+ export interface MockContextResult {
189
+ ctx: CortexContext;
190
+ stdout: PassThrough;
191
+ stdin: PassThrough;
192
+ }
193
+ /**
194
+ * Creates a {@link CortexContext} backed by a real Cortex.init() with a mock adapter.
195
+ *
196
+ * @param overrides - Optional overrides for adapter, stores, clock, and cwd
197
+ * @returns An object with the context and the raw stdout/stdin PassThrough streams
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * const { ctx, stdout } = createMockContext();
202
+ * await handleAdd(ctx, args, 'default', {});
203
+ * expect(captureOutput(stdout)).toContain('Memory added');
204
+ * ```
205
+ */
206
+ export declare function createMockContext(overrides?: MockContextOptions): MockContextResult;
207
+ /**
208
+ * Reads all buffered output from a PassThrough stream synchronously.
209
+ *
210
+ * Drains already-buffered data without consuming future writes.
211
+ * Useful for asserting output after a handler has finished writing.
212
+ *
213
+ * @param stream - The PassThrough stream to drain
214
+ * @returns The concatenated string of all buffered chunks
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const { ctx, stdout } = createMockContext();
219
+ * await handleList(ctx, {}, 'default', {});
220
+ * expect(captureOutput(stdout)).toContain('No memories found');
221
+ * ```
222
+ */
223
+ export declare function captureOutput(stream: PassThrough): string;
224
+ //# sourceMappingURL=test-helpers.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-helpers.spec.d.ts","sourceRoot":"","sources":["../src/test-helpers.spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EACR,cAAc,EACd,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,aAAa,EAChB,MAAM,oBAAoB,CAAC;AAO5B;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG;IAAE,EAAE,EAAE,MAAM,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAG/D,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG;IAAE,EAAE,EAAE,MAAM,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAGjE,CAAC;AAMH;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,IAAI;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,MAAM,CAAA;CAAE,CAOxF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAIhE;AAMD,oDAAoD;AACpD,eAAO,MAAM,aAAa,6BAA6B,CAAC;AAExD,2CAA2C;AAC3C,eAAO,MAAM,SAAS,MAA0B,CAAC;AAEjD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,GAChB,MAAK,MAAsB,KAAG,CAAC,MAAM,IAAI,CAEzB,CAAC;AAMtB;;;;;;;;;;;;;GAaG;AACH,wBAAsB,0BAA0B,CAC5C,EAAE,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,EACpC,WAAW,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,oBAAoB,CACtC,EAAE,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,EACpC,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,aAAa,CAY7F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,YAAY,CAQ1F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CACrC,SAAS,GAAE,OAAO,CAAC,eAAe,CAAM,GACzC,eAAe,CAQjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,GAAE,OAAO,CAAC,cAAc,CAAM,GAAG,cAAc,CAkBhG;AAED,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IAC/B,kFAAkF;IAClF,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,mFAAmF;IACnF,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,0CAA0C;AAC1C,MAAM,WAAW,iBAAiB;IAC9B,GAAG,EAAE,aAAa,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,CA2BnF;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAOzD"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Comprehensive integration tests for the Cortex CLI with Commander.js.
3
+ *
4
+ * These tests spawn the CLI as a subprocess using Bun shell to test
5
+ * the CLI like a user would interact with it.
6
+ *
7
+ * The tests use local store resolution by creating a project directory
8
+ * with a `.cortex/memory` subdirectory structure.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=cli.integration.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.integration.spec.d.ts","sourceRoot":"","sources":["../../src/tests/cli.integration.spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
package/dist/toon.d.ts ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * TOON (Token-Oriented Object Notation) encoder.
3
+ *
4
+ * A compact serialization format optimized for LLM context consumption,
5
+ * achieving approximately 40% token reduction compared to JSON while
6
+ * improving parsing accuracy from 70% to 74% in LLM benchmarks.
7
+ *
8
+ * ## Key Features
9
+ *
10
+ * - **Tab delimiters** - Uses tabs between key-value pairs instead of JSON's
11
+ * commas and braces, maximizing token efficiency
12
+ * - **Key folding** - Collapses nested object paths to dotted notation
13
+ * (e.g., `user.profile.name` instead of nested objects)
14
+ * - **Tabular arrays** - Uniform object arrays use a compact header+rows format,
15
+ * dramatically reducing repetition
16
+ *
17
+ * ## Format Specification
18
+ *
19
+ * | JSON Construct | TOON Equivalent |
20
+ * |----------------|-----------------|
21
+ * | `{"key": "value"}` | `key:value` |
22
+ * | `{"a": 1, "b": 2}` | `a:1\tb:2` |
23
+ * | Nested object | `parent.child:value` (with key folding) |
24
+ * | Uniform array | `items[N]{col1\tcol2}:\n\tval1\tval2` |
25
+ *
26
+ * ## Token Efficiency
27
+ *
28
+ * TOON reduces token count through:
29
+ * 1. Eliminating JSON syntax tokens (`{`, `}`, `[`, `]`, `,`, `"`)
30
+ * 2. Using single-character delimiters (tabs, colons)
31
+ * 3. Deduplicating keys in tabular array format
32
+ *
33
+ * Typical savings:
34
+ * - Simple objects: ~30% reduction
35
+ * - Nested objects with key folding: ~40% reduction
36
+ * - Arrays of uniform objects: ~50% reduction
37
+ *
38
+ * @module cli/toon
39
+ *
40
+ * @example Basic object encoding
41
+ * ```ts
42
+ * import { encode } from './toon';
43
+ *
44
+ * encode({ name: 'test', count: 42 });
45
+ * // Output: "name:test\tcount:42"
46
+ * ```
47
+ *
48
+ * @example Key folding for nested objects
49
+ * ```ts
50
+ * encode(
51
+ * { user: { name: 'Alice', role: 'admin' } },
52
+ * { keyFolding: 'safe' }
53
+ * );
54
+ * // Output: "user.name:Alice\tuser.role:admin"
55
+ * //
56
+ * // Without key folding:
57
+ * // "user:{name:Alice\trole:admin}"
58
+ * ```
59
+ *
60
+ * @example Tabular arrays (automatic for uniform object arrays)
61
+ * ```ts
62
+ * encode({
63
+ * items: [
64
+ * { id: 1, name: 'Widget' },
65
+ * { id: 2, name: 'Gadget' },
66
+ * ]
67
+ * });
68
+ * // Output:
69
+ * // "items[2]{id\tname}:
70
+ * // \t1\tWidget
71
+ * // \t2\tGadget"
72
+ * //
73
+ * // Equivalent JSON (32 tokens):
74
+ * // {"items":[{"id":1,"name":"Widget"},{"id":2,"name":"Gadget"}]}
75
+ * // TOON version (18 tokens): ~44% reduction
76
+ * ```
77
+ *
78
+ * @see {@link ToonOptions} for configuration options
79
+ * @see {@link encode} for the main encoding function
80
+ */
81
+ /**
82
+ * Configuration options for TOON encoding.
83
+ *
84
+ * @example Using custom delimiter
85
+ * ```ts
86
+ * encode({ a: 1, b: 2 }, { delimiter: '|' });
87
+ * // Output: "a:1|b:2"
88
+ * ```
89
+ *
90
+ * @example Enabling key folding
91
+ * ```ts
92
+ * encode({ config: { debug: true } }, { keyFolding: 'safe' });
93
+ * // Output: "config.debug:true"
94
+ * ```
95
+ */
96
+ export interface ToonOptions {
97
+ /**
98
+ * Character used to separate key-value pairs.
99
+ *
100
+ * The tab character (`\t`) is used by default as it provides optimal
101
+ * token efficiency in most LLM tokenizers (single token per delimiter).
102
+ *
103
+ * @default '\t'
104
+ */
105
+ delimiter?: string;
106
+ /**
107
+ * Controls how nested objects are serialized.
108
+ *
109
+ * - `'none'` - Nested objects are serialized inline with braces:
110
+ * `parent:{child:value}`
111
+ * - `'safe'` - Nested paths are collapsed to dotted notation:
112
+ * `parent.child:value`
113
+ *
114
+ * Key folding (`'safe'`) typically produces more compact output and is
115
+ * easier for LLMs to parse, but may cause key collisions if object
116
+ * keys contain dots.
117
+ *
118
+ * @default 'none'
119
+ */
120
+ keyFolding?: 'safe' | 'none';
121
+ }
122
+ /**
123
+ * Encodes a value to TOON format.
124
+ *
125
+ * TOON (Token-Oriented Object Notation) is a compact serialization format
126
+ * designed to minimize token consumption in LLM contexts while remaining
127
+ * human-readable and easily parseable by language models.
128
+ *
129
+ * @param value - The value to encode (must be JSON-serializable)
130
+ * @param options - Encoding options for customizing output format
131
+ * @returns TOON-encoded string representation
132
+ *
133
+ * @example Basic object encoding
134
+ * ```ts
135
+ * const data = { name: 'test', count: 42 };
136
+ * encode(data);
137
+ * // Returns: "name:test\tcount:42"
138
+ * //
139
+ * // Equivalent JSON: {"name":"test","count":42}
140
+ * // Token savings: ~35%
141
+ * ```
142
+ *
143
+ * @example Nested objects with key folding
144
+ * ```ts
145
+ * const nested = {
146
+ * user: {
147
+ * profile: { name: 'Alice', email: 'alice@example.com' },
148
+ * settings: { theme: 'dark' }
149
+ * }
150
+ * };
151
+ *
152
+ * // Without key folding (default):
153
+ * encode(nested);
154
+ * // Returns: "user:{profile:{name:Alice\temail:alice@example.com}\tsettings:{theme:dark}}"
155
+ *
156
+ * // With key folding:
157
+ * encode(nested, { keyFolding: 'safe' });
158
+ * // Returns: "user.profile.name:Alice\tuser.profile.email:alice@example.com\tuser.settings.theme:dark"
159
+ * ```
160
+ *
161
+ * @example Tabular arrays (automatic for uniform object arrays)
162
+ * ```ts
163
+ * const users = {
164
+ * users: [
165
+ * { id: 1, name: 'Alice', role: 'admin' },
166
+ * { id: 2, name: 'Bob', role: 'user' },
167
+ * { id: 3, name: 'Charlie', role: 'user' },
168
+ * ]
169
+ * };
170
+ *
171
+ * encode(users);
172
+ * // Returns:
173
+ * // "users[3]{id\tname\trole}:
174
+ * // \t1\tAlice\tadmin
175
+ * // \t2\tBob\tuser
176
+ * // \t3\tCharlie\tuser"
177
+ * //
178
+ * // Equivalent JSON (87 chars, ~25 tokens):
179
+ * // {"users":[{"id":1,"name":"Alice","role":"admin"},{"id":2,"name":"Bob","role":"user"},{"id":3,"name":"Charlie","role":"user"}]}
180
+ * //
181
+ * // TOON (67 chars, ~15 tokens): ~40% token reduction
182
+ * ```
183
+ *
184
+ * @example Mixed content
185
+ * ```ts
186
+ * encode({
187
+ * title: 'Report',
188
+ * metadata: { version: 1 },
189
+ * tags: ['urgent', 'review'] // Non-uniform arrays use JSON format
190
+ * }, { keyFolding: 'safe' });
191
+ * // Returns: "title:Report\tmetadata.version:1\ttags:[\"urgent\",\"review\"]"
192
+ * ```
193
+ *
194
+ * @see {@link ToonOptions} for available configuration options
195
+ */
196
+ export declare const encode: (value: unknown, options?: ToonOptions) => string;
197
+ //# sourceMappingURL=toon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toon.d.ts","sourceRoot":"","sources":["../src/toon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+EG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,WAAW;IACxB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAChC;AAkQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,OAAO,EAAE,UAAU,WAAW,KAAG,MAO9D,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Unit tests for toon.ts
3
+ *
4
+ * TOON (Token-Oriented Object Notation) encoder.
5
+ *
6
+ * @module cli/toon.spec
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=toon.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toon.spec.d.ts","sourceRoot":"","sources":["../src/toon.spec.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Executes a git command and returns the trimmed stdout.
3
+ */
4
+ export declare const runGitCommand: (args: string[], cwd: string) => Promise<{
5
+ ok: true;
6
+ value: string;
7
+ } | {
8
+ ok: false;
9
+ }>;
10
+ /**
11
+ * Detects the git repository name from the current working directory.
12
+ *
13
+ * Uses `git rev-parse --show-toplevel` to find the repository root,
14
+ * then extracts the directory name as the repository name.
15
+ *
16
+ * @param cwd - The current working directory to check for git repository
17
+ * @returns The repository directory name, or `null` if not in a git repository
18
+ */
19
+ export declare const detectGitRepoName: (cwd: string) => Promise<string | null>;
20
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,eAAO,MAAM,aAAa,GACtB,MAAM,MAAM,EAAE,EACd,KAAK,MAAM,KACZ,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAoBrD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAQ1E,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Unit tests for utils/git.ts
3
+ *
4
+ * @module cli/utils/git.spec
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=git.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.spec.d.ts","sourceRoot":"","sources":["../../src/utils/git.spec.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeseh/cortex-cli",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
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": "workspace:*",
38
- "@yeseh/cortex-storage-fs": "workspace:*",
37
+ "@yeseh/cortex-core": "0.6.4",
38
+ "@yeseh/cortex-storage-fs": "0.6.4",
39
39
  "@commander-js/extra-typings": "^14.0.0",
40
40
  "@inquirer/prompts": "^8.0.0",
41
41
  "@toon-format/toon": "^1.0.0",
@@ -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