@rahul05ranjan/dhruv-cli 1.6.0 → 1.6.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/CHANGELOG.md CHANGED
@@ -1,23 +1,9 @@
1
- # [1.4.0](https://github.com/rahul05ranjan/dhruv-cli/compare/v1.5.0...v1.4.0) (2026-09-18)
1
+ # [1.4.0](https://github.com/rahul05ranjan/dhruv-cli/compare/v1.6.0...v1.4.0) (2026-09-21)
2
2
 
3
3
 
4
4
  ### Bug Fixes
5
5
 
6
- * **test:** allow health --json contract test to inspect stdout on offline service exit code ([6231027](https://github.com/rahul05ranjan/dhruv-cli/commit/623102722d5c3ac2604dfffa276868a3b7eecdb8))
7
-
8
-
9
- ### Features
10
-
11
- * **ai:** ensure complete and streamable responses across commands (closes [#70](https://github.com/rahul05ranjan/dhruv-cli/issues/70)) ([285655e](https://github.com/rahul05ranjan/dhruv-cli/commit/285655e49eab1cbe87492cba2602a27a4bfecdfe))
12
- * **catalog:** unified command catalog, menu, and shell completions (closes [#79](https://github.com/rahul05ranjan/dhruv-cli/issues/79)) ([13a9c83](https://github.com/rahul05ranjan/dhruv-cli/commit/13a9c836a52933357b9ca187c787e3b9ae8f4948))
13
- * **cli:** standardize exit codes, timeouts, and cancellation (closes [#72](https://github.com/rahul05ranjan/dhruv-cli/issues/72)) ([d075d16](https://github.com/rahul05ranjan/dhruv-cli/commit/d075d16d1cfb3c000d156bcd238e3c261f0fc66a))
14
- * **cli:** standardize reliable json output contract (closes [#71](https://github.com/rahul05ranjan/dhruv-cli/issues/71)) ([e0de862](https://github.com/rahul05ranjan/dhruv-cli/commit/e0de86224b54dcf43652b5daf3f4b403d6aa7d8f))
15
- * **detection:** expand project detection and diagnostics (closes [#77](https://github.com/rahul05ranjan/dhruv-cli/issues/77)) ([221414a](https://github.com/rahul05ranjan/dhruv-cli/commit/221414a7c1156a650c9c746b5345b871e5274c57))
16
- * **diagnostics:** improve first-run model readiness and health diagnostics (closes [#78](https://github.com/rahul05ranjan/dhruv-cli/issues/78)) ([95cd3a8](https://github.com/rahul05ranjan/dhruv-cli/commit/95cd3a8d772024847a86ba53fb6391f8024ab962))
17
- * **generate:** previewable generation and multi-language test support (closes [#75](https://github.com/rahul05ranjan/dhruv-cli/issues/75)) ([c7f3dbe](https://github.com/rahul05ranjan/dhruv-cli/commit/c7f3dbe3902ee903220a40a4fae051b3a03797ac))
18
- * **metrics:** persistent invocation metrics with reset and export (closes [#76](https://github.com/rahul05ranjan/dhruv-cli/issues/76)) ([0b5c4db](https://github.com/rahul05ranjan/dhruv-cli/commit/0b5c4db69e85d10f6afeca1d26e8d9a089ce6e4c))
19
- * **review:** project-aware review and diff scanning (closes [#73](https://github.com/rahul05ranjan/dhruv-cli/issues/73)) ([290e4a1](https://github.com/rahul05ranjan/dhruv-cli/commit/290e4a18f21dfa9ea5a1f5fb51e2f8d98d83df53))
20
- * **security:** safe credential redaction and strict checks (closes [#74](https://github.com/rahul05ranjan/dhruv-cli/issues/74)) ([2467d37](https://github.com/rahul05ranjan/dhruv-cli/commit/2467d37f4404f7e16e2a10cfed860a76661d6c60))
6
+ * **config:** keep one-off global flags in-memory and avoid writing local config (closes [#92](https://github.com/rahul05ranjan/dhruv-cli/issues/92)) ([3dd3f99](https://github.com/rahul05ranjan/dhruv-cli/commit/3dd3f99babe42d3804bfa438f38a3ddd6e1f1d72))
21
7
  # Changelog
22
8
 
23
9
  ## [1.1.1](https://github.com/rahul05ranjan/dhruv-cli/compare/v1.1.0...v1.1.1) (2025-06-29)
@@ -1,7 +1,10 @@
1
1
  import { describe, expect, it } from '@jest/globals';
2
2
  import { execFile } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
- import { resolve } from 'node:path';
4
+ import { resolve, join } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { mkdtempSync, rmSync, existsSync } from 'node:fs';
7
+ import { tmpdir } from 'node:os';
5
8
 
6
9
  const execFileAsync = promisify(execFile);
7
10
  const repoRoot = resolve(__dirname, '..');
@@ -167,4 +170,34 @@ describe('CLI output contract', () => {
167
170
  expect(parsed).toHaveProperty('ok');
168
171
  expect(parsed.command).toBe('health');
169
172
  });
173
+
174
+ it('does not persist global flags (--json, --model, --verbose, --timeout) to .dhruv-config.json', async () => {
175
+ const tempDir = mkdtempSync(join(tmpdir(), 'dhruv-session-flags-'));
176
+ const tsNodeLoader = pathToFileURL(resolve(repoRoot, 'node_modules/ts-node/esm.mjs')).href;
177
+ try {
178
+ const result = await execFileAsync(
179
+ process.execPath,
180
+ ['--loader', tsNodeLoader, resolve(repoRoot, sourceEntry), 'metrics', '--json'],
181
+ {
182
+ cwd: tempDir,
183
+ env: {
184
+ ...process.env,
185
+ TS_NODE_PROJECT: resolve(repoRoot, 'tsconfig.json'),
186
+ DHRUV_METRICS_ENABLED: 'false',
187
+ },
188
+ },
189
+ );
190
+
191
+ const parsed = JSON.parse(result.stdout.trim()) as Record<string, unknown>;
192
+ expect(parsed).toMatchObject({
193
+ ok: true,
194
+ command: 'metrics',
195
+ });
196
+
197
+ const localConfigFile = join(tempDir, '.dhruv-config.json');
198
+ expect(existsSync(localConfigFile)).toBe(false);
199
+ } finally {
200
+ rmSync(tempDir, { recursive: true, force: true });
201
+ }
202
+ });
170
203
  });
@@ -5,7 +5,13 @@ import {
5
5
  setAIClient,
6
6
  InMemoryAIClient,
7
7
  } from '../src/core/ai';
8
- import { loadConfig, saveConfig } from '../src/config/config';
8
+ import {
9
+ loadConfig,
10
+ saveConfig,
11
+ setSessionConfig,
12
+ resetSessionConfig,
13
+ getSessionConfig,
14
+ } from '../src/config/config';
9
15
  import { createSpinner } from '../src/utils/ux';
10
16
  import { detectProjectType, detectProjectDetails } from '../src/utils/projectType';
11
17
  import { getSystemMessage } from '../src/core/prompts';
@@ -134,6 +140,58 @@ describe('Dhruv CLI Core Systems', () => {
134
140
  fs.unlinkSync(configPath);
135
141
  }
136
142
  });
143
+
144
+ it('should apply in-memory session overrides without writing to disk', () => {
145
+ resetSessionConfig();
146
+ const configPath = path.join(process.cwd(), '.dhruv-config.json');
147
+ const existedBefore = fs.existsSync(configPath);
148
+
149
+ setSessionConfig({
150
+ model: 'session-model',
151
+ responseFormat: 'json',
152
+ verbose: true,
153
+ timeoutMs: 12345,
154
+ });
155
+
156
+ expect(getSessionConfig()).toEqual({
157
+ model: 'session-model',
158
+ responseFormat: 'json',
159
+ verbose: true,
160
+ timeoutMs: 12345,
161
+ });
162
+
163
+ const loaded = loadConfig();
164
+ expect(loaded.model).toBe('session-model');
165
+ expect(loaded.responseFormat).toBe('json');
166
+ expect(loaded.verbose).toBe(true);
167
+ expect(loaded.timeoutMs).toBe(12345);
168
+
169
+ if (!existedBefore) {
170
+ expect(fs.existsSync(configPath)).toBe(false);
171
+ }
172
+
173
+ resetSessionConfig();
174
+ expect(getSessionConfig()).toEqual({});
175
+ const afterReset = loadConfig();
176
+ expect(afterReset.model).not.toBe('session-model');
177
+ });
178
+
179
+ it('should not persist session overrides when saving configuration', () => {
180
+ resetSessionConfig();
181
+ const configPath = path.join(process.cwd(), '.dhruv-config.json');
182
+ if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
183
+
184
+ setSessionConfig({ responseFormat: 'json' });
185
+ saveConfig({ model: 'persistent-model' });
186
+
187
+ const fileContent = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
188
+ expect(fileContent.model).toBe('persistent-model');
189
+ // Must not snapshot the in-memory responseFormat into the saved file
190
+ expect(fileContent.responseFormat).not.toBe('json');
191
+
192
+ if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
193
+ resetSessionConfig();
194
+ });
137
195
  });
138
196
 
139
197
  describe('Project Type Detection', () => {
@@ -6,6 +6,9 @@ export interface DhruvConfig {
6
6
  timeoutMs: number;
7
7
  theme?: 'default' | 'dark' | 'light' | 'mono';
8
8
  }
9
+ export declare function setSessionConfig(config: Partial<DhruvConfig>): void;
10
+ export declare function resetSessionConfig(): void;
11
+ export declare function getSessionConfig(): Partial<DhruvConfig>;
9
12
  export declare function loadConfig(): DhruvConfig;
10
13
  export declare function saveConfig(config: Partial<DhruvConfig>, options?: {
11
14
  scope?: ConfigScope;
@@ -10,6 +10,16 @@ const defaultConfig = {
10
10
  timeoutMs: 45000,
11
11
  theme: 'default',
12
12
  };
13
+ let sessionConfig = {};
14
+ export function setSessionConfig(config) {
15
+ sessionConfig = { ...sessionConfig, ...config };
16
+ }
17
+ export function resetSessionConfig() {
18
+ sessionConfig = {};
19
+ }
20
+ export function getSessionConfig() {
21
+ return { ...sessionConfig };
22
+ }
13
23
  function readConfigFile(file) {
14
24
  if (fs.existsSync(file)) {
15
25
  try {
@@ -25,11 +35,17 @@ function readConfigFile(file) {
25
35
  return defaultConfig;
26
36
  }
27
37
  export function loadConfig() {
28
- if (fs.existsSync(LOCAL_CONFIG_FILE))
29
- return readConfigFile(LOCAL_CONFIG_FILE);
30
- if (fs.existsSync(GLOBAL_CONFIG_FILE))
31
- return readConfigFile(GLOBAL_CONFIG_FILE);
32
- return defaultConfig;
38
+ let base;
39
+ if (fs.existsSync(LOCAL_CONFIG_FILE)) {
40
+ base = readConfigFile(LOCAL_CONFIG_FILE);
41
+ }
42
+ else if (fs.existsSync(GLOBAL_CONFIG_FILE)) {
43
+ base = readConfigFile(GLOBAL_CONFIG_FILE);
44
+ }
45
+ else {
46
+ base = defaultConfig;
47
+ }
48
+ return validateAndMergeConfig({ ...base, ...sessionConfig });
33
49
  }
34
50
  function validateAndMergeConfig(config) {
35
51
  const validatedConfig = { ...defaultConfig };
@@ -57,7 +73,7 @@ function validateAndMergeConfig(config) {
57
73
  export function saveConfig(config, options = {}) {
58
74
  const scope = options.scope ?? 'local';
59
75
  const file = scope === 'global' ? GLOBAL_CONFIG_FILE : LOCAL_CONFIG_FILE;
60
- const current = scope === 'global' ? readConfigFile(GLOBAL_CONFIG_FILE) : loadConfig();
76
+ const current = fs.existsSync(file) ? readConfigFile(file) : defaultConfig;
61
77
  if (scope === 'global')
62
78
  fs.mkdirSync(path.dirname(file), { recursive: true });
63
79
  fs.writeFileSync(file, JSON.stringify({ ...current, ...config }, null, 2));
package/dist/index.js CHANGED
@@ -110,9 +110,9 @@ program
110
110
  config.responseFormat = 'json';
111
111
  if (opts.timeout)
112
112
  config.timeoutMs = Number(opts.timeout);
113
- // Save config for session
113
+ // Set in-memory config overrides for the session
114
114
  const configModule = await import('./config/config.js');
115
- configModule.saveConfig(config);
115
+ configModule.setSessionConfig(config);
116
116
  }
117
117
  });
118
118
  async function loadPlugins(program) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rahul05ranjan/dhruv-cli",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "AI-powered CLI assistant for developers using Ollama",
5
5
  "keywords": [
6
6
  "ai",
@@ -23,6 +23,20 @@ const defaultConfig: DhruvConfig = {
23
23
  theme: 'default',
24
24
  };
25
25
 
26
+ let sessionConfig: Partial<DhruvConfig> = {};
27
+
28
+ export function setSessionConfig(config: Partial<DhruvConfig>): void {
29
+ sessionConfig = { ...sessionConfig, ...config };
30
+ }
31
+
32
+ export function resetSessionConfig(): void {
33
+ sessionConfig = {};
34
+ }
35
+
36
+ export function getSessionConfig(): Partial<DhruvConfig> {
37
+ return { ...sessionConfig };
38
+ }
39
+
26
40
  function readConfigFile(file: string): DhruvConfig {
27
41
  if (fs.existsSync(file)) {
28
42
  try {
@@ -38,9 +52,15 @@ function readConfigFile(file: string): DhruvConfig {
38
52
  }
39
53
 
40
54
  export function loadConfig(): DhruvConfig {
41
- if (fs.existsSync(LOCAL_CONFIG_FILE)) return readConfigFile(LOCAL_CONFIG_FILE);
42
- if (fs.existsSync(GLOBAL_CONFIG_FILE)) return readConfigFile(GLOBAL_CONFIG_FILE);
43
- return defaultConfig;
55
+ let base: DhruvConfig;
56
+ if (fs.existsSync(LOCAL_CONFIG_FILE)) {
57
+ base = readConfigFile(LOCAL_CONFIG_FILE);
58
+ } else if (fs.existsSync(GLOBAL_CONFIG_FILE)) {
59
+ base = readConfigFile(GLOBAL_CONFIG_FILE);
60
+ } else {
61
+ base = defaultConfig;
62
+ }
63
+ return validateAndMergeConfig({ ...base, ...sessionConfig });
44
64
  }
45
65
 
46
66
  function validateAndMergeConfig(config: Partial<DhruvConfig>): DhruvConfig {
@@ -76,7 +96,7 @@ function validateAndMergeConfig(config: Partial<DhruvConfig>): DhruvConfig {
76
96
  export function saveConfig(config: Partial<DhruvConfig>, options: { scope?: ConfigScope } = {}) {
77
97
  const scope = options.scope ?? 'local';
78
98
  const file = scope === 'global' ? GLOBAL_CONFIG_FILE : LOCAL_CONFIG_FILE;
79
- const current = scope === 'global' ? readConfigFile(GLOBAL_CONFIG_FILE) : loadConfig();
99
+ const current = fs.existsSync(file) ? readConfigFile(file) : defaultConfig;
80
100
  if (scope === 'global') fs.mkdirSync(path.dirname(file), { recursive: true });
81
101
  fs.writeFileSync(file, JSON.stringify({ ...current, ...config }, null, 2));
82
102
  }
package/src/index.ts CHANGED
@@ -123,9 +123,9 @@ program
123
123
  if (opts.verbose) config.verbose = true;
124
124
  if (opts.json) config.responseFormat = 'json';
125
125
  if (opts.timeout) config.timeoutMs = Number(opts.timeout);
126
- // Save config for session
126
+ // Set in-memory config overrides for the session
127
127
  const configModule = await import('./config/config.js');
128
- configModule.saveConfig(config);
128
+ configModule.setSessionConfig(config);
129
129
  }
130
130
  });
131
131