@push.rocks/smartconfig 6.0.1 → 6.1.1

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/readme.md CHANGED
@@ -1,517 +1,322 @@
1
1
  # @push.rocks/smartconfig 🚀
2
2
 
3
- **Supercharge your npm projects with powerful configuration management, tool orchestration, and persistent key-value storage.**
3
+ A comprehensive TypeScript configuration management library providing centralized tool configs, persistent key-value storage, and powerful environment variable mapping with automatic type conversions.
4
+
5
+ ## Issue Reporting and Security
6
+
7
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
4
8
 
5
9
  ## Install 📦
6
10
 
7
11
  ```bash
8
- # Using npm
9
12
  npm install @push.rocks/smartconfig --save
10
-
11
- # Using pnpm (recommended)
13
+ # or
12
14
  pnpm add @push.rocks/smartconfig
13
15
  ```
14
16
 
15
- ## Overview 🎯
17
+ ## Quick Start ⚡
18
+
19
+ ```typescript
20
+ import { Smartconfig, AppData, KeyValueStore } from '@push.rocks/smartconfig';
16
21
 
17
- `@push.rocks/smartconfig` is your Swiss Army knife for npm project configuration. It eliminates configuration sprawl by centralizing tool settings, providing intelligent key-value storage, and offering powerful environment variable mapping with automatic type conversions.
22
+ // 1. Read tool config from .smartconfig.json
23
+ const sc = new Smartconfig();
24
+ const eslintOpts = sc.dataFor('eslint', { extends: 'standard' });
18
25
 
19
- ### Why smartconfig?
26
+ // 2. Map env vars to typed config (with auto-conversion)
27
+ const appData = await AppData.createAndInit<{ port: number; debug: boolean }>({
28
+ envMapping: {
29
+ port: 'PORT',
30
+ debug: 'boolean:DEBUG',
31
+ },
32
+ });
20
33
 
21
- - **🎛️ Centralized Configuration**: Manage all your tool configs in one `.smartconfig.json` file
22
- - **💾 Persistent Storage**: Smart key-value store with multiple storage strategies
23
- - **🔐 Environment Mapping**: Sophisticated env var handling with automatic type conversion
24
- - **🏗️ TypeScript First**: Full type safety and IntelliSense support
25
- - **⚡ Zero Config**: Works out of the box with sensible defaults
26
- - **🔄 Reactive**: Built-in change detection and observables
34
+ // 3. Persist data between runs
35
+ const kv = new KeyValueStore({ typeArg: 'userHomeDir', identityArg: 'myapp' });
36
+ await kv.writeKey('lastRun', Date.now());
37
+ ```
27
38
 
28
- ## Core Concepts 🏗️
39
+ ## Three Core Classes 🏗️
29
40
 
30
- ### 1. Smartconfig Configuration Management
41
+ ### 1. `Smartconfig` — Centralized Tool Configuration
31
42
 
32
- Stop scattering configuration across dozens of files. Centralize everything in `.smartconfig.json`:
43
+ Reads a `.smartconfig.json` file from a project directory and merges its contents with your defaults. One file, every tool.
33
44
 
34
45
  ```typescript
35
46
  import { Smartconfig } from '@push.rocks/smartconfig';
36
47
 
37
- // Initialize with current directory
38
- const smartconfig = new Smartconfig();
48
+ const sc = new Smartconfig(); // uses cwd
49
+ const sc2 = new Smartconfig('/my/project'); // or specify a path
39
50
 
40
- // Or specify a custom path
41
- const smartconfig = new Smartconfig('/path/to/project');
42
-
43
- // Get merged configuration for any tool
44
- const eslintConfig = smartconfig.dataFor<EslintConfig>('eslint', {
45
- // Default values if not in .smartconfig.json
46
- extends: 'standard',
47
- rules: {}
51
+ const prettierConfig = sc.dataFor<PrettierConfig>('prettier', {
52
+ semi: false,
53
+ singleQuote: true,
48
54
  });
49
55
  ```
50
56
 
51
- **.smartconfig.json example:**
57
+ **`.smartconfig.json`** example:
58
+
52
59
  ```json
53
60
  {
54
- "eslint": {
55
- "extends": "@company/eslint-config",
56
- "rules": {
57
- "no-console": "warn"
58
- }
59
- },
60
61
  "prettier": {
61
- "semi": false,
62
- "singleQuote": true
62
+ "semi": true,
63
+ "printWidth": 120
64
+ },
65
+ "eslint": {
66
+ "extends": "@company/eslint-config"
63
67
  }
64
68
  }
65
69
  ```
66
70
 
67
- ### 2. KeyValueStore - Persistent Data Storage
71
+ Values from the file override the defaults you pass in. Missing keys fall back to your defaults.
68
72
 
69
- A flexible key-value store that persists data between script executions:
73
+ **Properties:**
74
+ - `smartconfigJsonExists: boolean` — whether `.smartconfig.json` was found
75
+ - `smartconfigJsonData: any` — the parsed JSON contents
76
+
77
+ **Methods:**
78
+ - `dataFor<T>(toolName: string, defaults: T): T` — returns merged config
79
+
80
+ ---
81
+
82
+ ### 2. `KeyValueStore<T>` — Persistent Data Storage
83
+
84
+ A generic, typed key-value store that persists JSON to disk (or stays in-memory for tests). Supports change detection via RxJS observables.
70
85
 
71
86
  ```typescript
72
87
  import { KeyValueStore } from '@push.rocks/smartconfig';
73
88
 
74
- interface UserSettings {
89
+ interface Settings {
75
90
  username: string;
76
- apiKey: string;
77
- preferences: {
78
- theme: 'light' | 'dark';
79
- notifications: boolean;
80
- };
91
+ theme: 'light' | 'dark';
81
92
  }
82
93
 
83
- // Different storage strategies
84
- const kvStore = new KeyValueStore<UserSettings>({
85
- typeArg: 'userHomeDir', // Store in user's home directory
94
+ // Store in ~/.smartconfig/kv/
95
+ const kv = new KeyValueStore<Settings>({
96
+ typeArg: 'userHomeDir',
86
97
  identityArg: 'myApp',
87
- mandatoryKeys: ['username', 'apiKey']
98
+ mandatoryKeys: ['username'],
88
99
  });
89
100
 
90
- // Write operations
91
- await kvStore.writeKey('username', 'john_doe');
92
- await kvStore.writeKey('preferences', {
93
- theme: 'dark',
94
- notifications: true
95
- });
101
+ await kv.writeKey('username', 'jane');
102
+ await kv.writeKey('theme', 'dark');
96
103
 
97
- // Read operations
98
- const username = await kvStore.readKey('username');
99
- const allData = await kvStore.readAll();
104
+ const user = await kv.readKey('username'); // 'jane'
105
+ const all = await kv.readAll(); // { username: 'jane', theme: 'dark' }
100
106
 
101
- // Check for missing mandatory keys
102
- const missingKeys = await kvStore.getMissingMandatoryKeys();
103
- if (missingKeys.length > 0) {
104
- console.log('Missing required configuration:', missingKeys);
105
- }
106
-
107
- // Wait for keys to be present
108
- await kvStore.waitForKeysPresent(['apiKey']);
107
+ // React to changes
108
+ kv.changeSubject.subscribe((data) => console.log('changed:', data));
109
109
  ```
110
110
 
111
- **Storage Types:**
112
- - `userHomeDir`: Store in user's home directory
113
- - `custom`: Specify your own path
114
- - `ephemeral`: In-memory only (perfect for testing)
111
+ **Storage types:**
112
+
113
+ | `typeArg` | Where it goes | Use case |
114
+ |-----------|--------------|----------|
115
+ | `'userHomeDir'` | `~/.smartconfig/kv/<identity>.json` | CLI tools, per-user state |
116
+ | `'custom'` | Your path (file or directory) | App data, project-local state |
117
+ | `'ephemeral'` | Memory only — nothing on disk | Tests |
118
+
119
+ **Methods:**
120
+
121
+ | Method | Description |
122
+ |--------|------------|
123
+ | `readKey(key)` | Read a single value |
124
+ | `writeKey(key, value)` | Write a single value |
125
+ | `readAll()` | Read everything |
126
+ | `writeAll(obj)` | Merge an object into the store |
127
+ | `deleteKey(key)` | Remove a key |
128
+ | `reset()` | Wipe all keys (synced to disk) |
129
+ | `wipe()` | Delete the backing file entirely |
130
+ | `getMissingMandatoryKeys()` | Returns keys declared mandatory but not yet set |
131
+ | `waitForKeysPresent(keys)` | Returns a Promise that resolves once all listed keys exist |
132
+ | `waitForAndGetKey(key)` | Waits for a key, then returns its value |
133
+
134
+ ---
115
135
 
116
- ### 3. AppData - Advanced Environment Management 🌟
136
+ ### 3. `AppData<T>` Environment Variable Mapping 🌟
117
137
 
118
- The crown jewel of smartconfig - sophisticated environment variable mapping with automatic type conversion:
138
+ The flagship class. Maps environment variables (or hardcoded values) into a typed config object with automatic type conversions, nested object support, and smart storage path selection.
119
139
 
120
140
  ```typescript
121
141
  import { AppData } from '@push.rocks/smartconfig';
122
142
 
123
- interface AppConfig {
143
+ interface Config {
124
144
  apiUrl: string;
125
145
  apiKey: string;
126
- port: number;
127
146
  features: {
128
147
  analytics: boolean;
129
148
  payment: boolean;
130
149
  };
131
- cache: {
132
- ttl: number;
133
- redis: {
134
- host: string;
135
- password: string;
136
- };
150
+ redis: {
151
+ host: string;
152
+ password: string;
137
153
  };
138
154
  }
139
155
 
140
- const appData = await AppData.createAndInit<AppConfig>({
141
- dirPath: '/app/config', // Optional: defaults to smart path selection
142
- requiredKeys: ['apiKey', 'apiUrl'],
156
+ const appData = await AppData.createAndInit<Config>({
157
+ requiredKeys: ['apiKey'],
143
158
  envMapping: {
144
- apiUrl: 'API_URL', // Simple mapping
145
- apiKey: 'hard:development-key-123', // Hardcoded value
146
- port: 'hard:3000', // Hardcoded number
159
+ apiUrl: 'API_URL', // plain env var
160
+ apiKey: 'hard:dev-key-123', // hardcoded fallback
147
161
  features: {
148
- analytics: 'boolean:ENABLE_ANALYTICS', // Force boolean conversion
149
- payment: 'hard_boolean:true' // Hardcoded boolean
162
+ analytics: 'boolean:ENABLE_ANALYTICS', // converts "true"/"false" → boolean
163
+ payment: 'hard_boolean:true', // hardcoded boolean
164
+ },
165
+ redis: {
166
+ host: 'REDIS_HOST',
167
+ password: 'base64:REDIS_PASSWORD_B64', // base64-decode at load time
150
168
  },
151
- cache: {
152
- ttl: 'json:CACHE_CONFIG', // Parse JSON from env var
153
- redis: {
154
- host: 'REDIS_HOST',
155
- password: 'base64:REDIS_PASSWORD_B64' // Decode base64
156
- }
157
- }
158
169
  },
159
170
  overwriteObject: {
160
- // Force these values regardless of env vars
161
- port: 8080
162
- }
171
+ apiUrl: 'http://localhost:3000', // force override after env mapping
172
+ },
163
173
  });
164
174
 
165
175
  const store = await appData.getKvStore();
166
- const apiUrl = await store.readKey('apiUrl');
176
+ const url = await store.readKey('apiUrl');
167
177
  ```
168
178
 
169
- ## AppData Special Cases & Conversions 🎯
179
+ #### Mapping Prefixes
170
180
 
171
- ### Environment Variable Prefixes
181
+ | Prefix | What it does | Example mapping | Result |
182
+ |--------|-------------|-----------------|--------|
183
+ | *(none)* | Raw env var as string | `'MY_VAR'` | `process.env.MY_VAR` |
184
+ | `hard:` | Hardcoded string | `'hard:hello'` | `"hello"` |
185
+ | `boolean:` | Env var → `true`/`false` | `'boolean:FLAG'` | `true` or `false` |
186
+ | `json:` | Env var → `JSON.parse()` | `'json:CONFIG'` | parsed object |
187
+ | `base64:` | Env var → base64 decode | `'base64:SECRET'` | decoded string |
188
+ | `hard_boolean:` | Hardcoded boolean | `'hard_boolean:false'` | `false` |
189
+ | `hard_json:` | Hardcoded JSON | `'hard_json:{"a":1}'` | `{ a: 1 }` |
190
+ | `hard_base64:` | Hardcoded base64 | `'hard_base64:SGVsbG8='` | `"Hello"` |
172
191
 
173
- AppData supports sophisticated type conversion through prefixes:
192
+ Suffix detection also works: a mapping ending in `_JSON` or `_BASE64` triggers the corresponding transform automatically.
174
193
 
175
- | Prefix | Description | Example | Result |
176
- |--------|-------------|---------|--------|
177
- | `hard:` | Hardcoded value | `hard:myvalue` | `"myvalue"` |
178
- | `hard_boolean:` | Hardcoded boolean | `hard_boolean:true` | `true` |
179
- | `hard_json:` | Hardcoded JSON | `hard_json:{"key":"value"}` | `{key: "value"}` |
180
- | `hard_base64:` | Hardcoded base64 | `hard_base64:SGVsbG8=` | `"Hello"` |
181
- | `boolean:` | Env var as boolean | `boolean:FEATURE_FLAG` | `true/false` |
182
- | `json:` | Parse env var as JSON | `json:CONFIG_JSON` | Parsed object |
183
- | `base64:` | Decode env var from base64 | `base64:SECRET_B64` | Decoded string |
194
+ #### Boolean Conversion Rules
184
195
 
185
- ### Automatic Suffix Detection
196
+ The `boolean:` prefix (and `hard_boolean:`) recognizes:
186
197
 
187
- Variables ending with certain suffixes get automatic conversion:
198
+ - **true**: `"true"`, `"1"`, `"yes"`, `"y"`, `"on"` (case-insensitive)
199
+ - **false**: `"false"`, `"0"`, `"no"`, `"n"`, `"off"` (case-insensitive)
188
200
 
189
- ```typescript
190
- {
191
- envMapping: {
192
- // Automatically parsed as JSON if MY_CONFIG_JSON="{"enabled":true}"
193
- config: 'MY_CONFIG_JSON',
194
-
195
- // Automatically decoded from base64 if SECRET_KEY_BASE64="SGVsbG8="
196
- secret: 'SECRET_KEY_BASE64'
197
- }
198
- }
199
- ```
201
+ #### Nested Objects
200
202
 
201
- ### Complex Examples
203
+ Mapping values can be objects — they are resolved recursively:
202
204
 
203
205
  ```typescript
204
- const appData = await AppData.createAndInit({
205
- envMapping: {
206
- // Simple environment variable
207
- apiUrl: 'API_URL',
208
-
209
- // Hardcoded values with type conversion
210
- debugMode: 'hard_boolean:false',
211
- maxRetries: 'hard:5',
212
- defaultConfig: 'hard_json:{"timeout":30,"retries":3}',
213
-
214
- // Environment variables with conversion
215
- features: 'json:FEATURE_FLAGS', // Expects: {"feature1":true,"feature2":false}
216
- isProduction: 'boolean:IS_PROD', // Expects: "true" or "false"
217
- apiSecret: 'base64:API_SECRET', // Expects: base64 encoded string
218
-
219
- // Nested structures
220
- database: {
221
- host: 'DB_HOST',
222
- port: 'hard:5432',
223
- credentials: {
224
- user: 'DB_USER',
225
- password: 'base64:DB_PASSWORD_ENCODED',
226
- ssl: 'boolean:DB_USE_SSL'
227
- }
228
- }
206
+ envMapping: {
207
+ database: {
208
+ host: 'DB_HOST',
209
+ port: 'hard:5432',
210
+ credentials: {
211
+ user: 'DB_USER',
212
+ password: 'base64:DB_PASS_B64',
213
+ ssl: 'boolean:DB_SSL',
214
+ },
229
215
  },
230
-
231
- // Override any env mappings
232
- overwriteObject: {
233
- debugMode: true, // Force debug mode regardless of env
234
- database: {
235
- host: 'localhost' // Force localhost for development
236
- }
237
- }
238
- });
239
- ```
240
-
241
- ### Boolean Conversion Rules
242
-
243
- AppData intelligently handles boolean conversions:
244
-
245
- 1. **String "true"/"false"**: Converted to boolean
246
- 2. **With `boolean:` prefix**: Any env var value is converted (`"true"` → `true`, anything else → `false`)
247
- 3. **With `hard_boolean:` prefix**: Hardcoded boolean value
248
- 4. **Regular env vars**: Strings remain strings unless prefixed
249
-
250
- ```typescript
251
- // Environment: FEATURE_A="true", FEATURE_B="yes", FEATURE_C="1"
252
- {
253
- envMapping: {
254
- featureA: 'FEATURE_A', // Result: "true" (string)
255
- featureB: 'boolean:FEATURE_B', // Result: false (only "true" → true)
256
- featureC: 'boolean:FEATURE_C', // Result: false (only "true" → true)
257
- featureD: 'hard_boolean:true' // Result: true (hardcoded)
258
- }
259
- }
260
- ```
261
-
262
- ### Static Helper Functions
263
-
264
- AppData provides convenient static methods for directly accessing and converting environment variables without creating an instance:
265
-
266
- ```typescript
267
- import { AppData } from '@push.rocks/smartconfig';
268
-
269
- // Get environment variable as boolean
270
- const isEnabled = await AppData.valueAsBoolean('FEATURE_ENABLED');
271
- // Returns: true if "true", false otherwise
272
-
273
- // Get environment variable as parsed JSON
274
- interface Config {
275
- timeout: number;
276
- retries: number;
277
216
  }
278
- const config = await AppData.valueAsJson<Config>('SERVICE_CONFIG');
279
- // Returns: Parsed object or undefined
280
-
281
- // Get environment variable as base64 decoded string
282
- const secret = await AppData.valueAsBase64('ENCODED_SECRET');
283
- // Returns: Decoded string or undefined
284
-
285
- // Get environment variable as string
286
- const apiUrl = await AppData.valueAsString('API_URL');
287
- // Returns: String value or undefined
288
-
289
- // Get environment variable as number
290
- const port = await AppData.valueAsNumber('PORT');
291
- // Returns: Number value or undefined
292
217
  ```
293
218
 
294
- These static methods are perfect for:
295
- - Quick environment variable access without setup
296
- - Simple type conversions in utility functions
297
- - One-off configuration checks
298
- - Scenarios where you don't need the full AppData instance
299
-
300
- ## Advanced Patterns 🎨
301
-
302
- ### Reactive Configuration
219
+ #### Smart Storage Path
303
220
 
304
- Subscribe to configuration changes:
221
+ When no `dirPath` is specified, AppData auto-selects:
305
222
 
306
- ```typescript
307
- const kvStore = new KeyValueStore<Config>({
308
- typeArg: 'custom',
309
- identityArg: 'myApp'
310
- });
223
+ 1. `/app/data` — if it exists (containers)
224
+ 2. `/data` if it exists (alternate container path)
225
+ 3. `.nogit/appdata` — local dev fallback
311
226
 
312
- // Subscribe to changes
313
- kvStore.changeSubject.subscribe((newData) => {
314
- console.log('Configuration changed:', newData);
315
- });
227
+ Or pass `ephemeral: true` for zero disk I/O (great for tests).
316
228
 
317
- // Changes trigger notifications
318
- await kvStore.writeKey('theme', 'dark');
319
- ```
229
+ #### Static Helpers
320
230
 
321
- ### Testing with Ephemeral Storage
322
-
323
- Perfect for unit tests - no file system pollution:
231
+ Quick one-shot env var reads without creating an AppData instance:
324
232
 
325
233
  ```typescript
326
- const testStore = new KeyValueStore<TestData>({
327
- typeArg: 'ephemeral',
328
- identityArg: 'test'
329
- });
330
-
331
- // All operations work normally, but nothing persists to disk
332
- await testStore.writeKey('testKey', 'testValue');
234
+ const isEnabled = await AppData.valueAsBoolean('FEATURE_FLAG');
235
+ const config = await AppData.valueAsJson<MyType>('CONFIG_JSON');
236
+ const secret = await AppData.valueAsBase64('ENCODED_SECRET');
237
+ const url = await AppData.valueAsString('API_URL');
238
+ const port = await AppData.valueAsNumber('PORT');
333
239
  ```
334
240
 
335
- ### Smart Path Resolution
336
-
337
- AppData automatically selects the best storage location:
241
+ #### Instance Methods
338
242
 
339
- 1. Checks for `/app/data` (containerized environments)
340
- 2. Falls back to `/data` (alternate container path)
341
- 3. Uses `.nogit/appdata` (local development)
243
+ | Method | Description |
244
+ |--------|------------|
245
+ | `getKvStore()` | Returns the underlying `KeyValueStore<T>` |
246
+ | `logMissingKeys()` | Logs and returns any required keys that are missing |
247
+ | `waitForAndGetKey(key)` | Blocks until a key is present, then returns it |
342
248
 
343
- ```typescript
344
- // Automatic path selection
345
- const appData = await AppData.createAndInit({
346
- // No dirPath specified - smart detection
347
- requiredKeys: ['apiKey']
348
- });
249
+ ---
349
250
 
350
- // Or force ephemeral for testing
351
- const testData = await AppData.createAndInit({
352
- ephemeral: true, // No disk persistence
353
- requiredKeys: ['testKey']
354
- });
355
- ```
251
+ ## Security 🔐
356
252
 
357
- ### Waiting for Configuration
253
+ AppData automatically redacts sensitive values in its console logs. Keys matching patterns like `secret`, `token`, `password`, `api`, `auth`, `jwt`, etc. are truncated. JWT tokens (starting with `eyJ`) are also detected and shortened. Your actual stored values are never modified — only log output is redacted.
358
254
 
359
- Block until required configuration is available:
360
-
361
- ```typescript
362
- const appData = await AppData.createAndInit<Config>({
363
- requiredKeys: ['apiKey', 'apiUrl']
364
- });
365
-
366
- // Wait for specific key
367
- const apiKey = await appData.waitForAndGetKey('apiKey');
368
-
369
- // Check missing keys
370
- const missingKeys = await appData.logMissingKeys();
371
- // Logs: "The following mandatory keys are missing in the appdata:
372
- // -> apiKey,
373
- // -> apiUrl"
374
- ```
255
+ ---
375
256
 
376
257
  ## Real-World Example 🌍
377
258
 
378
- Here's a complete example of a CLI tool using smartconfig:
379
-
380
259
  ```typescript
381
260
  import { Smartconfig, AppData, KeyValueStore } from '@push.rocks/smartconfig';
382
261
 
383
262
  interface CliConfig {
384
263
  githubToken: string;
385
- openaiKey: string;
386
264
  model: 'gpt-3' | 'gpt-4';
387
- cache: {
388
- enabled: boolean;
389
- ttl: number;
390
- };
391
- }
392
-
393
- class MyCLI {
394
- private smartconfig: Smartconfig;
395
- private appData: AppData<CliConfig>;
396
- private cache: KeyValueStore<{[key: string]: any}>;
397
-
398
- async initialize() {
399
- // Load tool configuration
400
- this.smartconfig = new Smartconfig();
401
- const config = this.smartconfig.dataFor<any>('mycli', {
402
- defaultModel: 'gpt-3'
403
- });
404
-
405
- // Setup app data with env mapping
406
- this.appData = await AppData.createAndInit<CliConfig>({
407
- requiredKeys: ['githubToken', 'openaiKey'],
408
- envMapping: {
409
- githubToken: 'GITHUB_TOKEN',
410
- openaiKey: 'base64:OPENAI_KEY_ENCODED',
411
- model: 'hard:gpt-4',
412
- cache: {
413
- enabled: 'boolean:ENABLE_CACHE',
414
- ttl: 'hard:3600'
415
- }
416
- }
417
- });
418
-
419
- // Initialize cache
420
- this.cache = new KeyValueStore({
421
- typeArg: 'userHomeDir',
422
- identityArg: 'mycli-cache'
423
- });
424
-
425
- // Check for missing configuration
426
- const missingKeys = await this.appData.logMissingKeys();
427
- if (missingKeys.length > 0) {
428
- console.error('Please configure the missing keys');
429
- process.exit(1);
430
- }
431
- }
432
-
433
- async run() {
434
- await this.initialize();
435
-
436
- const config = await this.appData.getKvStore();
437
- const settings = await config.readAll();
438
-
439
- console.log(`Using model: ${settings.model}`);
440
- console.log(`Cache enabled: ${settings.cache.enabled}`);
441
-
442
- // Use the configuration...
443
- }
265
+ cache: { enabled: boolean; ttl: number };
444
266
  }
445
267
 
446
- // Run the CLI
447
- const cli = new MyCLI();
448
- cli.run();
449
- ```
450
-
451
- ## API Reference 📚
452
-
453
- ### Smartconfig Class
454
-
455
- ```typescript
456
- new Smartconfig(cwdArg?: string)
457
- ```
458
- - `cwdArg`: Optional working directory path
459
-
460
- **Methods:**
461
- - `dataFor<T>(toolName: string, defaultOptions: T): T` - Get merged configuration
462
-
463
- ### KeyValueStore Class
268
+ // Tool-level config from .smartconfig.json
269
+ const sc = new Smartconfig();
270
+ const toolDefaults = sc.dataFor('mycli', { defaultModel: 'gpt-3' });
464
271
 
465
- ```typescript
466
- new KeyValueStore<T>(options: {
467
- typeArg: 'custom' | 'userHomeDir' | 'ephemeral';
468
- identityArg: string;
469
- customPath?: string;
470
- mandatoryKeys?: Array<keyof T>;
471
- })
472
- ```
272
+ // Env-mapped runtime config
273
+ const appData = await AppData.createAndInit<CliConfig>({
274
+ requiredKeys: ['githubToken'],
275
+ envMapping: {
276
+ githubToken: 'GITHUB_TOKEN',
277
+ model: 'hard:gpt-4',
278
+ cache: {
279
+ enabled: 'boolean:ENABLE_CACHE',
280
+ ttl: 'hard:3600',
281
+ },
282
+ },
283
+ });
473
284
 
474
- **Methods:**
475
- - `readKey(key: string): Promise<T>` - Read single value
476
- - `writeKey(key: string, value: T): Promise<void>` - Write single value
477
- - `readAll(): Promise<T>` - Read all values
478
- - `writeAll(data: T): Promise<void>` - Write all values
479
- - `deleteKey(key: string): Promise<void>` - Delete a key
480
- - `getMissingMandatoryKeys(): Promise<string[]>` - Check missing required keys
481
- - `waitForKeysPresent(keys: string[]): Promise<void>` - Wait for keys
285
+ // Persistent user-level cache
286
+ const cache = new KeyValueStore({
287
+ typeArg: 'userHomeDir',
288
+ identityArg: 'mycli-cache',
289
+ });
482
290
 
483
- ### AppData Class
291
+ // Check mandatory keys
292
+ const missing = await appData.logMissingKeys();
293
+ if (missing.length > 0) {
294
+ console.error('Missing config — set these env vars and retry.');
295
+ process.exit(1);
296
+ }
484
297
 
485
- ```typescript
486
- await AppData.createAndInit<T>(options: {
487
- dirPath?: string;
488
- requiredKeys?: Array<keyof T>;
489
- ephemeral?: boolean;
490
- envMapping?: PartialDeep<T>;
491
- overwriteObject?: PartialDeep<T>;
492
- })
298
+ const store = await appData.getKvStore();
299
+ const settings = await store.readAll();
300
+ console.log(`Model: ${settings.model}, Cache: ${settings.cache.enabled}`);
493
301
  ```
494
302
 
495
- **Methods:**
496
- - `getKvStore(): Promise<KeyValueStore<T>>` - Get underlying store
497
- - `logMissingKeys(): Promise<Array<keyof T>>` - Log and return missing keys
498
- - `waitForAndGetKey<K>(key: K): Promise<T[K]>` - Wait for and retrieve key
499
-
500
303
  ## License and Legal Information
501
304
 
502
- This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
305
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
503
306
 
504
307
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
505
308
 
506
309
  ### Trademarks
507
310
 
508
- This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
311
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
312
+
313
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
509
314
 
510
315
  ### Company Information
511
316
 
512
- Task Venture Capital GmbH
513
- Registered at District court Bremen HRB 35230 HB, Germany
317
+ Task Venture Capital GmbH
318
+ Registered at District Court Bremen HRB 35230 HB, Germany
514
319
 
515
- For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
320
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
516
321
 
517
- By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
322
+ By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.