feat: read approvalMode from config file (#298)
This PR implements support for reading the approvalMode setting from the user's config file (`~/.codex/config.json` or `~/.codex/config.yaml`), allowing users to set a persistent default approval mode without needing to specify command-line flags for each session. Changes: - Added approvalMode to the AppConfig type in config.ts - Updated loadConfig() to read the approval mode from the config file - Modified saveConfig() to persist the approval mode setting - Updated CLI logic to respect the config-defined approval mode (while maintaining CLI flag priority) - Added comprehensive tests for approval mode config functionality - Updated README to document the new config option in both YAML and JSON formats - additions to `.gitignore` for other CLI tools Motivation: As a user who regularly works with CLI-tools, I found it odd to have to alias this with the command flags I wanted when `approvalMode` simply wasn't being parsed even though it was an optional prop in `config.ts`. This change allows me (and other users) to set the preference once in the config file, streamlining daily usage while maintaining the ability to override via command-line flags when needed. Testing: I've added a new test case loads and saves approvalMode correctly that verifies: - Reading the approvalMode from the config file works correctly - Saving the approvalMode to the config file works as expected - The value persists through load/save operations All tests related to the implementation are passing.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -26,6 +26,10 @@ result
|
|||||||
*.swp
|
*.swp
|
||||||
*~
|
*~
|
||||||
|
|
||||||
|
# cli tools
|
||||||
|
CLAUDE.md
|
||||||
|
.claude/
|
||||||
|
|
||||||
# caches
|
# caches
|
||||||
.cache/
|
.cache/
|
||||||
.turbo/
|
.turbo/
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -281,11 +281,12 @@ pnpm link
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Codex looks for config files in **`~/.codex/`**.
|
Codex looks for config files in **`~/.codex/`** (either YAML or JSON format).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ~/.codex/config.yaml
|
# ~/.codex/config.yaml
|
||||||
model: o4-mini # Default model
|
model: o4-mini # Default model
|
||||||
|
approvalMode: suggest # or auto-edit, full-auto
|
||||||
fullAutoErrorMode: ask-user # or ignore-and-continue
|
fullAutoErrorMode: ask-user # or ignore-and-continue
|
||||||
notify: true # Enable desktop notifications for responses
|
notify: true # Enable desktop notifications for responses
|
||||||
safeCommands:
|
safeCommands:
|
||||||
@@ -293,6 +294,16 @@ safeCommands:
|
|||||||
- yarn lint # Automatically approve yarn lint
|
- yarn lint # Automatically approve yarn lint
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// ~/.codex/config.json
|
||||||
|
{
|
||||||
|
"model": "o4-mini",
|
||||||
|
"approvalMode": "suggest",
|
||||||
|
"fullAutoErrorMode": "ask-user",
|
||||||
|
"notify": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
You can also define custom instructions:
|
You can also define custom instructions:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ if (quietMode) {
|
|||||||
imagePaths: imagePaths || [],
|
imagePaths: imagePaths || [],
|
||||||
approvalPolicy: autoApproveEverything
|
approvalPolicy: autoApproveEverything
|
||||||
? AutoApprovalMode.FULL_AUTO
|
? AutoApprovalMode.FULL_AUTO
|
||||||
: AutoApprovalMode.SUGGEST,
|
: config.approvalMode || AutoApprovalMode.SUGGEST,
|
||||||
additionalWritableRoots,
|
additionalWritableRoots,
|
||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
@@ -364,14 +364,15 @@ if (quietMode) {
|
|||||||
// it is more dangerous than --fullAuto we deliberately give it lower
|
// it is more dangerous than --fullAuto we deliberately give it lower
|
||||||
// priority so a user specifying both flags still gets the safer behaviour.
|
// priority so a user specifying both flags still gets the safer behaviour.
|
||||||
// 3. --autoEdit – automatically approve edits, but prompt for commands.
|
// 3. --autoEdit – automatically approve edits, but prompt for commands.
|
||||||
// 4. Default – suggest mode (prompt for everything).
|
// 4. config.approvalMode - use the approvalMode setting from ~/.codex/config.json.
|
||||||
|
// 5. Default – suggest mode (prompt for everything).
|
||||||
|
|
||||||
const approvalPolicy: ApprovalPolicy =
|
const approvalPolicy: ApprovalPolicy =
|
||||||
cli.flags.fullAuto || cli.flags.approvalMode === "full-auto"
|
cli.flags.fullAuto || cli.flags.approvalMode === "full-auto"
|
||||||
? AutoApprovalMode.FULL_AUTO
|
? AutoApprovalMode.FULL_AUTO
|
||||||
: cli.flags.autoEdit || cli.flags.approvalMode === "auto-edit"
|
: cli.flags.autoEdit || cli.flags.approvalMode === "auto-edit"
|
||||||
? AutoApprovalMode.AUTO_EDIT
|
? AutoApprovalMode.AUTO_EDIT
|
||||||
: AutoApprovalMode.SUGGEST;
|
: config.approvalMode || AutoApprovalMode.SUGGEST;
|
||||||
|
|
||||||
preloadModels();
|
preloadModels();
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export type AppConfig = {
|
|||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
model: string;
|
model: string;
|
||||||
instructions: string;
|
instructions: string;
|
||||||
|
approvalMode?: AutoApprovalMode;
|
||||||
fullAutoErrorMode?: FullAutoErrorMode;
|
fullAutoErrorMode?: FullAutoErrorMode;
|
||||||
memory?: MemoryConfig;
|
memory?: MemoryConfig;
|
||||||
/** Whether to enable desktop notifications for responses */
|
/** Whether to enable desktop notifications for responses */
|
||||||
@@ -275,6 +276,7 @@ export const loadConfig = (
|
|||||||
: DEFAULT_AGENTIC_MODEL),
|
: DEFAULT_AGENTIC_MODEL),
|
||||||
instructions: combinedInstructions,
|
instructions: combinedInstructions,
|
||||||
notify: storedConfig.notify === true,
|
notify: storedConfig.notify === true,
|
||||||
|
approvalMode: storedConfig.approvalMode,
|
||||||
safeCommands: storedConfig.safeCommands ?? [],
|
safeCommands: storedConfig.safeCommands ?? [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -391,6 +393,7 @@ export const saveConfig = (
|
|||||||
// Create the config object to save
|
// Create the config object to save
|
||||||
const configToSave: StoredConfig = {
|
const configToSave: StoredConfig = {
|
||||||
model: config.model,
|
model: config.model,
|
||||||
|
approvalMode: config.approvalMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add history settings if they exist
|
// Add history settings if they exist
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type * as fsType from "fs";
|
import type * as fsType from "fs";
|
||||||
|
|
||||||
import { loadConfig, saveConfig } from "../src/utils/config.js"; // parent import first
|
import { loadConfig, saveConfig } from "../src/utils/config.js"; // parent import first
|
||||||
|
import { AutoApprovalMode } from "../src/utils/auto-approval-mode.js";
|
||||||
import { tmpdir } from "os";
|
import { tmpdir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { test, expect, beforeEach, afterEach, vi } from "vitest";
|
import { test, expect, beforeEach, afterEach, vi } from "vitest";
|
||||||
@@ -107,3 +108,43 @@ test("loads user instructions + project doc when codex.md is present", () => {
|
|||||||
userInstr + "\n\n--- project-doc ---\n\n" + projectDoc,
|
userInstr + "\n\n--- project-doc ---\n\n" + projectDoc,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("loads and saves approvalMode correctly", () => {
|
||||||
|
// Setup config with approvalMode
|
||||||
|
memfs[testConfigPath] = JSON.stringify(
|
||||||
|
{
|
||||||
|
model: "mymodel",
|
||||||
|
approvalMode: AutoApprovalMode.AUTO_EDIT,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
memfs[testInstructionsPath] = "test instructions";
|
||||||
|
|
||||||
|
// Load config and verify approvalMode
|
||||||
|
const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
|
||||||
|
disableProjectDoc: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check approvalMode was loaded correctly
|
||||||
|
expect(loadedConfig.approvalMode).toBe(AutoApprovalMode.AUTO_EDIT);
|
||||||
|
|
||||||
|
// Modify approvalMode and save
|
||||||
|
const updatedConfig = {
|
||||||
|
...loadedConfig,
|
||||||
|
approvalMode: AutoApprovalMode.FULL_AUTO,
|
||||||
|
};
|
||||||
|
|
||||||
|
saveConfig(updatedConfig, testConfigPath, testInstructionsPath);
|
||||||
|
|
||||||
|
// Verify saved config contains updated approvalMode
|
||||||
|
expect(memfs[testConfigPath]).toContain(
|
||||||
|
`"approvalMode": "${AutoApprovalMode.FULL_AUTO}"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load again and verify updated value
|
||||||
|
const reloadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
|
||||||
|
disableProjectDoc: true,
|
||||||
|
});
|
||||||
|
expect(reloadedConfig.approvalMode).toBe(AutoApprovalMode.FULL_AUTO);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user