axconfig 1.0.0 → 2.0.0

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 (74) hide show
  1. package/README.md +110 -119
  2. package/dist/agents/claude-code-reader.d.ts +9 -0
  3. package/dist/agents/claude-code-reader.js +160 -0
  4. package/dist/agents/claude-code.d.ts +1 -0
  5. package/dist/agents/claude-code.js +30 -4
  6. package/dist/agents/codex-reader.d.ts +9 -0
  7. package/dist/agents/codex-reader.js +162 -0
  8. package/dist/agents/codex.d.ts +1 -0
  9. package/dist/agents/codex.js +26 -8
  10. package/dist/agents/copilot-reader.d.ts +13 -0
  11. package/dist/agents/copilot-reader.js +120 -0
  12. package/dist/agents/copilot.d.ts +16 -0
  13. package/dist/agents/copilot.js +85 -0
  14. package/dist/agents/gemini-reader.d.ts +9 -0
  15. package/dist/agents/gemini-reader.js +158 -0
  16. package/dist/agents/gemini.d.ts +1 -0
  17. package/dist/agents/gemini.js +28 -5
  18. package/dist/agents/opencode-reader.d.ts +9 -0
  19. package/dist/agents/opencode-reader.js +135 -0
  20. package/dist/agents/opencode.d.ts +1 -0
  21. package/dist/agents/opencode.js +20 -4
  22. package/dist/atomic-write.d.ts +19 -0
  23. package/dist/atomic-write.js +46 -0
  24. package/dist/build-agent-config.d.ts +3 -2
  25. package/dist/builder.d.ts +3 -2
  26. package/dist/cli.d.ts +1 -0
  27. package/dist/cli.js +58 -36
  28. package/dist/commands/create.d.ts +1 -2
  29. package/dist/commands/create.js +6 -60
  30. package/dist/commands/get-raw.d.ts +11 -0
  31. package/dist/commands/get-raw.js +66 -0
  32. package/dist/commands/get.d.ts +11 -0
  33. package/dist/commands/get.js +136 -0
  34. package/dist/commands/set-permissions.d.ts +18 -0
  35. package/dist/commands/set-permissions.js +99 -0
  36. package/dist/commands/set-raw.d.ts +10 -0
  37. package/dist/commands/set-raw.js +58 -0
  38. package/dist/commands/set.d.ts +11 -0
  39. package/dist/commands/set.js +72 -0
  40. package/dist/index.d.ts +10 -27
  41. package/dist/index.js +11 -24
  42. package/dist/object-path.d.ts +38 -0
  43. package/dist/object-path.js +104 -0
  44. package/dist/read-write-json-config.d.ts +26 -0
  45. package/dist/read-write-json-config.js +99 -0
  46. package/dist/read-write-toml-config.d.ts +26 -0
  47. package/dist/read-write-toml-config.js +100 -0
  48. package/dist/reader.d.ts +20 -0
  49. package/dist/reader.js +27 -0
  50. package/dist/resolve-config-path.d.ts +19 -0
  51. package/dist/resolve-config-path.js +32 -0
  52. package/dist/types.d.ts +70 -6
  53. package/dist/types.js +1 -3
  54. package/package.json +5 -3
  55. package/dist/auth/agents/claude-code.d.ts +0 -9
  56. package/dist/auth/agents/claude-code.js +0 -106
  57. package/dist/auth/agents/codex.d.ts +0 -9
  58. package/dist/auth/agents/codex.js +0 -75
  59. package/dist/auth/agents/gemini.d.ts +0 -9
  60. package/dist/auth/agents/gemini.js +0 -97
  61. package/dist/auth/agents/opencode.d.ts +0 -9
  62. package/dist/auth/agents/opencode.js +0 -62
  63. package/dist/auth/check-auth.d.ts +0 -13
  64. package/dist/auth/check-auth.js +0 -24
  65. package/dist/auth/extract-credentials.d.ts +0 -11
  66. package/dist/auth/extract-credentials.js +0 -20
  67. package/dist/auth/get-access-token.d.ts +0 -35
  68. package/dist/auth/get-access-token.js +0 -116
  69. package/dist/auth/types.d.ts +0 -19
  70. package/dist/auth/types.js +0 -4
  71. package/dist/commands/auth.d.ts +0 -19
  72. package/dist/commands/auth.js +0 -87
  73. package/dist/crypto.d.ts +0 -39
  74. package/dist/crypto.js +0 -78
package/README.md CHANGED
@@ -1,38 +1,24 @@
1
1
  # axconfig
2
2
 
3
- Configuration management library and CLI for AI coding agents.
3
+ Unified configuration management for AI coding agents — common API for permissions, settings, and config across Claude Code, Codex, Gemini CLI, and OpenCode.
4
4
 
5
5
  ## Overview
6
6
 
7
- axconfig is the **single source of truth** for all AI coding agent configuration logic. It can be used:
7
+ Different AI coding agents each have their own config formats, permission syntaxes, and settings locations. If you want to control what an agent can do or configure its behavior, you'd need to learn each agent's specific configuration format and file locations.
8
8
 
9
- 1. **As a library** - imported by [axrun](https://github.com/anthropics/axrun) to build agent configs programmatically
10
- 2. **As a CLI** - standalone tool for managing, inspecting, and creating agent configs
9
+ axconfig solves this by providing:
11
10
 
12
- ## Architecture
11
+ 1. **Unified permission syntax** — One way to express permissions (`--allow read,bash:git *`) that works across all agents
12
+ 2. **Common config access** — Get and set configuration properties using a consistent API, regardless of agent
13
+ 3. **Translation to agent-specific formats** — Converts unified config into whatever format each agent expects
14
+ 4. **Capability validation** — Knows what each agent supports and warns/errors appropriately
13
15
 
14
- ```
15
- ┌─────────────────────────────────────────────────────────────┐
16
- │ axrun │
17
- │ (Agent execution: spawns agents, streams events) │
18
- │ │
19
- │ axrun -a claude-code --allow "read,bash:git *" "prompt" │
20
- │ │ │
21
- │ ▼ │
22
- │ ┌────────────────────────┐ │
23
- │ │ axconfig │ │
24
- │ │ (Config translation) │ │
25
- │ └────────────────────────┘ │
26
- │ │ │
27
- │ ▼ │
28
- │ { env, settings } │
29
- │ │ │
30
- │ ▼ │
31
- │ spawn(claude, { env }) │
32
- └─────────────────────────────────────────────────────────────┘
33
- ```
16
+ It can be used:
17
+
18
+ - **As a library** — integrate into your own tools and scripts
19
+ - **As a CLI** standalone tool for managing agent configs
34
20
 
35
- ## Core Responsibilities
21
+ ## Core Functionality
36
22
 
37
23
  ### Permission Parsing
38
24
 
@@ -81,7 +67,12 @@ axconfig validates permissions against agent capabilities:
81
67
  ## Library API
82
68
 
83
69
  ```typescript
84
- import { parsePermissions, getConfigBuilder, buildAgentConfig } from "axconfig";
70
+ import {
71
+ buildAgentConfig,
72
+ getConfigReader,
73
+ parsePermissions,
74
+ resolveConfigPath,
75
+ } from "axconfig";
85
76
 
86
77
  // Parse CLI-style permission strings
87
78
  const permissions = parsePermissions(
@@ -101,128 +92,134 @@ if (result.ok) {
101
92
  // result.env = { CLAUDE_CONFIG_DIR: "/tmp/my-config" }
102
93
  // result.warnings = [...]
103
94
  }
95
+
96
+ // Read existing config
97
+ const reader = getConfigReader("claude-code");
98
+ const configDir = resolveConfigPath("claude-code"); // ~/.claude/
99
+
100
+ const perms = reader.readPermissions(configDir);
101
+ if (perms.ok && perms.value) {
102
+ console.log(perms.value.allow); // PermissionRule[]
103
+ }
104
+
105
+ // Read/write raw config values
106
+ const raw = reader.readRaw(configDir, "permissions.allow");
107
+ reader.writeRaw(configDir, "customSetting", { foo: "bar" });
104
108
  ```
105
109
 
106
110
  ## CLI Commands
107
111
 
108
- ```bash
109
- # List agents and their auth status
110
- axconfig auth list
111
- axconfig auth list --json
112
-
113
- # Export credentials to encrypted file
114
- axconfig auth export --agent claude-code --output creds.json
115
- axconfig auth export --agent claude-code --output creds.json --no-password
112
+ ### Create Config
116
113
 
114
+ ```bash
117
115
  # Create config with permissions (outputs env vars)
118
116
  axconfig create --agent claude-code --output /tmp/config \
119
117
  --allow "read,glob,bash:git *" \
120
118
  --deny "bash:rm *"
121
119
 
122
- # Create config with exported credentials
123
- axconfig create --agent claude-code --output /tmp/config \
124
- --allow read \
125
- --with-credentials creds.json
126
-
127
120
  # Export for shell usage
128
121
  eval $(axconfig create --agent claude-code --output /tmp/config --allow read)
129
- ```
130
122
 
131
- ## Pipeline Examples
123
+ # Export as JSON for parsing
124
+ axconfig create --agent claude-code --output /tmp/cfg --allow read --format json | jq '.env'
125
+ ```
132
126
 
133
- The CLI outputs TSV format for easy processing with standard Unix tools:
127
+ ### Get/Set Unified Settings
134
128
 
135
129
  ```bash
136
- # List all agents and their auth status
137
- axconfig auth list
138
- # AGENT STATUS METHOD
139
- # claude-code authenticated OAuth (max)
140
- # codex authenticated ChatGPT OAuth
141
- # ...
130
+ # Get current settings (uses agent's default config location)
131
+ axconfig get --agent claude-code allow
132
+ # Output: read,glob,bash:git *
142
133
 
143
- # Filter to show only authenticated agents
144
- axconfig auth list | tail -n +2 | awk -F'\t' '$2 == "authenticated"'
134
+ axconfig get --agent claude-code deny
135
+ # Output: bash:rm *
145
136
 
146
- # Count agents by status
147
- axconfig auth list | tail -n +2 | cut -f2 | sort | uniq -c
137
+ axconfig get --agent claude-code model
138
+ # Output: sonnet
148
139
 
149
- # Extract agent names as a list
150
- axconfig auth list | tail -n +2 | cut -f1
140
+ # Get settings as JSON
141
+ axconfig get --agent claude-code allow --format json
142
+ # Output: ["Read", "Glob", {"Bash": {"command": "git", "args": "*"}}]
151
143
 
152
- # Export config as JSON and extract specific fields with jq
153
- axconfig create --agent claude-code --output /tmp/cfg --allow read --format json | jq '.env'
154
-
155
- # Check if a specific agent is authenticated
156
- axconfig auth list --json | jq -e '.[] | select(.agentId == "claude-code") | .authenticated'
157
- ```
144
+ # Set settings (merge with existing by default)
145
+ axconfig set --agent claude-code allow "read,glob"
146
+ axconfig set --agent claude-code deny "bash:rm *"
147
+ axconfig set --agent claude-code model "claude-sonnet-4-20250514"
158
148
 
159
- ## Module Structure
149
+ # Replace instead of merge (for allow/deny)
150
+ axconfig set --agent claude-code allow "read,glob" --replace
160
151
 
152
+ # Set with custom config path
153
+ axconfig set --agent claude-code --path /tmp/cfg allow "read,glob"
161
154
  ```
162
- src/
163
- ├── types.ts # PermissionRule, AxrunConfig, BuildResult, etc.
164
- ├── parse-permissions.ts # parseRule, parseRuleList, parsePermissions
165
- ├── builder.ts # ConfigBuilder registry
166
- ├── build-agent-config.ts # High-level buildAgentConfig function
167
- ├── check-auth.ts # Auth status detection for agents
168
- ├── extract-credentials.ts # Credential extraction for export
169
- ├── crypto.ts # AES-256-GCM encryption for credentials
170
- ├── cli.ts # CLI entry point
171
- └── agents/
172
- ├── claude-code.ts # Claude Code config builder
173
- ├── codex.ts # Codex config builder
174
- ├── gemini.ts # Gemini CLI config builder
175
- └── opencode.ts # OpenCode config builder
176
- ```
177
-
178
- ## Integration with axrun
179
155
 
180
- axrun imports axconfig as a dependency:
156
+ ### Get/Set Raw Config Values
181
157
 
182
- ```typescript
183
- // In axrun/src/cli.ts
184
- import { buildAgentConfig } from "axconfig";
185
-
186
- const configResult = buildAgentConfig({
187
- agentId: options.agent,
188
- allow: options.allow,
189
- deny: options.deny,
190
- output: "/tmp/axrun-config",
191
- });
158
+ ```bash
159
+ # Get raw config value by dotted path
160
+ axconfig get-raw --agent claude-code permissions.allow
161
+ # Output: ["Read", "Glob"]
192
162
 
193
- if (!configResult.ok) {
194
- // Handle errors
195
- }
163
+ # Get raw value as JSON
164
+ axconfig get-raw --agent claude-code permissions --format json
196
165
 
197
- // Spawn agent with config
198
- spawn(agentBin, args, { env: { ...process.env, ...configResult.env } });
166
+ # Set raw config value (JSON or string)
167
+ axconfig set-raw --agent claude-code permissions.allow '["Read", "Glob"]'
168
+ axconfig set-raw --agent opencode permission.edit allow
199
169
  ```
200
170
 
201
- ## Auth Preservation
171
+ ### Pipeline Examples
202
172
 
203
- A key requirement is preserving agent authentication when injecting permissions.
173
+ ```bash
174
+ # Capture settings in shell variables
175
+ ALLOW=$(axconfig get --agent claude-code allow)
176
+ MODEL=$(axconfig get --agent claude-code model)
204
177
 
205
- ### Problem
178
+ # Extract allow rules as JSON and filter with jq
179
+ axconfig get --agent claude-code allow --format json | jq '.'
206
180
 
207
- Agents store auth in their config directories. Naively overriding config paths breaks auth:
181
+ # List all bash permissions, one per line
182
+ axconfig get --agent claude-code allow | tr ',' '\n' | grep 'bash:'
208
183
 
209
- ```bash
210
- # This breaks auth!
211
- CLAUDE_CONFIG_DIR=/tmp/custom claude "prompt"
212
- ```
184
+ # Count unique bash command prefixes
185
+ axconfig get --agent claude-code allow \
186
+ | tr ',' '\n' | grep 'bash:' \
187
+ | cut -d: -f2 | cut -d' ' -f1 | sort | uniq -c | sort -rn
188
+
189
+ # Compare permissions between agents
190
+ diff <(axconfig get -a claude-code allow) <(axconfig get -a gemini allow)
213
191
 
214
- ### Solution
192
+ # Check if a specific permission exists
193
+ axconfig get --agent claude-code allow | grep -q 'bash:git' && echo "git allowed"
215
194
 
216
- Each agent has a preferred method for layering config:
195
+ # Add a new permission to existing allow list
196
+ CURRENT=$(axconfig get --agent claude-code allow)
197
+ axconfig set --agent claude-code allow "$CURRENT,write" --replace
198
+ ```
217
199
 
218
- | Agent | Method |
219
- | ----------- | ---------------------------------------------------- |
220
- | claude-code | `--settings <file>` flag merges with existing config |
221
- | codex | `CODEX_HOME` with copied/symlinked auth files |
222
- | gemini | `GEMINI_CLI_SYSTEM_SETTINGS_PATH` for settings |
223
- | opencode | `OPENCODE_CONFIG` points to merged config |
200
+ ## Module Structure
224
201
 
225
- axconfig handles these differences internally.
202
+ ```
203
+ src/
204
+ ├── types.ts # PermissionRule, ConfigReader, BuildResult, etc.
205
+ ├── parse-permissions.ts # parseRule, parseRuleList, parsePermissions
206
+ ├── builder.ts # ConfigBuilder registry
207
+ ├── reader.ts # ConfigReader registry
208
+ ├── resolve-config-path.ts # Resolve agent config directory
209
+ ├── build-agent-config.ts # High-level buildAgentConfig function
210
+ ├── cli.ts # CLI entry point
211
+ ├── commands/
212
+ │ ├── create.ts # Create command handler
213
+ │ ├── get.ts # Get unified settings
214
+ │ ├── set.ts # Set unified settings
215
+ │ ├── get-raw.ts # Get raw config values
216
+ │ └── set-raw.ts # Set raw config values
217
+ └── agents/
218
+ ├── claude-code.ts # Claude Code config builder + reader
219
+ ├── codex.ts # Codex config builder + reader
220
+ ├── gemini.ts # Gemini CLI config builder + reader
221
+ └── opencode.ts # OpenCode config builder + reader
222
+ ```
226
223
 
227
224
  ## Agent Rule
228
225
 
@@ -235,15 +232,9 @@ Run `npx -y axconfig --help` to learn available options.
235
232
 
236
233
  Use `axconfig` to manage AI agent configurations with unified permission syntax.
237
234
  It translates `--allow` and `--deny` rules to agent-specific formats (Claude Code,
238
- Codex, Gemini CLI, OpenCode) and handles auth preservation automatically.
235
+ Codex, Gemini CLI, OpenCode).
239
236
  ```
240
237
 
241
- ## Development Status
242
-
243
- 🚧 **Work in Progress**
244
-
245
- Migrating config logic from axrun to axconfig.
246
-
247
238
  ## License
248
239
 
249
240
  MIT
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Claude Code ConfigReader.
3
+ *
4
+ * Reads and writes Claude Code configuration from settings.json.
5
+ */
6
+ import type { ConfigReader } from "../types.js";
7
+ /** Claude Code ConfigReader */
8
+ declare const claudeCodeConfigReader: ConfigReader;
9
+ export { claudeCodeConfigReader };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Claude Code ConfigReader.
3
+ *
4
+ * Reads and writes Claude Code configuration from settings.json.
5
+ */
6
+ import { mkdirSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import path from "node:path";
9
+ import { atomicWriteFileSync } from "../atomic-write.js";
10
+ import { registerConfigReader } from "../reader.js";
11
+ import { createJsonConfigOperations, readJsonConfig, } from "../read-write-json-config.js";
12
+ /** Reverse mapping from Claude Code tool names to canonical names */
13
+ const REVERSE_TOOL_MAP = {
14
+ Read: "read",
15
+ Write: "write",
16
+ Edit: "edit",
17
+ Bash: "bash",
18
+ Glob: "glob",
19
+ Grep: "grep",
20
+ WebFetch: "web",
21
+ WebSearch: "web",
22
+ };
23
+ /** Path-restricted tools (lowercase) */
24
+ const PATH_TOOLS = new Set(["read", "write", "edit"]);
25
+ /**
26
+ * Get the settings.json path for a config directory.
27
+ */
28
+ function getSettingsPath(configDirectory) {
29
+ return path.join(configDirectory, "settings.json");
30
+ }
31
+ /**
32
+ * Read and parse settings.json, returning empty object if not found.
33
+ */
34
+ function readSettings(configDirectory) {
35
+ return readJsonConfig(getSettingsPath(configDirectory));
36
+ }
37
+ // Create shared raw config operations
38
+ const { readRaw, writeRaw, deleteRaw } = createJsonConfigOperations(getSettingsPath, "Claude Code");
39
+ /**
40
+ * Parse a Claude Code rule string back to unified format.
41
+ */
42
+ function parseClaudeRule(rule) {
43
+ // Check for pattern format: "Tool(pattern)"
44
+ const parenIndex = rule.indexOf("(");
45
+ if (parenIndex > 0 && rule.endsWith(")")) {
46
+ const toolName = rule.slice(0, parenIndex);
47
+ const pattern = rule.slice(parenIndex + 1, -1);
48
+ // Bash pattern: "Bash(git:*)" → pattern is "git:*", we want "git"
49
+ if (toolName === "Bash") {
50
+ const bashPattern = pattern.endsWith(":*")
51
+ ? pattern.slice(0, -2)
52
+ : pattern;
53
+ return { type: "bash", pattern: bashPattern };
54
+ }
55
+ // Path pattern: "Read(src/**)" → tool is "read", pattern is "src/**"
56
+ const canonicalTool = REVERSE_TOOL_MAP[toolName];
57
+ if (canonicalTool && PATH_TOOLS.has(canonicalTool)) {
58
+ return {
59
+ type: "path",
60
+ tool: canonicalTool,
61
+ pattern,
62
+ };
63
+ }
64
+ return undefined;
65
+ }
66
+ // Simple tool name: "Read" → { type: "tool", name: "read" }
67
+ const canonicalTool = REVERSE_TOOL_MAP[rule];
68
+ if (canonicalTool) {
69
+ return { type: "tool", name: canonicalTool };
70
+ }
71
+ return undefined;
72
+ }
73
+ /**
74
+ * Read permissions from Claude Code config.
75
+ */
76
+ function readPermissions(configDirectory) {
77
+ try {
78
+ const settings = readSettings(configDirectory);
79
+ const permissions = settings.permissions;
80
+ if (!permissions) {
81
+ return { ok: true, value: undefined };
82
+ }
83
+ const allowRules = (permissions.allow ?? [])
84
+ .map((rule) => parseClaudeRule(rule))
85
+ .filter((rule) => rule !== undefined);
86
+ const denyRules = (permissions.deny ?? [])
87
+ .map((rule) => parseClaudeRule(rule))
88
+ .filter((rule) => rule !== undefined);
89
+ const config = {
90
+ allow: allowRules,
91
+ deny: denyRules,
92
+ };
93
+ return { ok: true, value: config };
94
+ }
95
+ catch (error) {
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ return {
98
+ ok: false,
99
+ error: `Failed to read Claude Code config: ${message}`,
100
+ };
101
+ }
102
+ }
103
+ /**
104
+ * Read model from Claude Code config.
105
+ * Returns undefined when no model is explicitly configured.
106
+ */
107
+ function readModel(configDirectory) {
108
+ try {
109
+ const settings = readSettings(configDirectory);
110
+ const model = settings.model;
111
+ if (model === undefined || model === null) {
112
+ return { ok: true, value: undefined };
113
+ }
114
+ if (typeof model !== "string") {
115
+ return { ok: false, error: "Model value is not a string" };
116
+ }
117
+ return { ok: true, value: model };
118
+ }
119
+ catch (error) {
120
+ const message = error instanceof Error ? error.message : String(error);
121
+ return {
122
+ ok: false,
123
+ error: `Failed to read Claude Code model: ${message}`,
124
+ };
125
+ }
126
+ }
127
+ /**
128
+ * Write model to Claude Code config.
129
+ */
130
+ function writeModel(configDirectory, model) {
131
+ try {
132
+ mkdirSync(configDirectory, { recursive: true });
133
+ const settings = readSettings(configDirectory);
134
+ settings.model = model;
135
+ atomicWriteFileSync(getSettingsPath(configDirectory), JSON.stringify(settings, undefined, 2));
136
+ return { ok: true };
137
+ }
138
+ catch (error) {
139
+ const message = error instanceof Error ? error.message : String(error);
140
+ return {
141
+ ok: false,
142
+ error: `Failed to write Claude Code model: ${message}`,
143
+ };
144
+ }
145
+ }
146
+ /** Claude Code ConfigReader */
147
+ const claudeCodeConfigReader = {
148
+ agentId: "claude",
149
+ defaultConfigDir: () => path.join(homedir(), ".claude"),
150
+ envVar: "CLAUDE_CONFIG_DIR",
151
+ readPermissions,
152
+ readModel,
153
+ writeModel,
154
+ readRaw,
155
+ writeRaw,
156
+ deleteRaw,
157
+ };
158
+ // Self-register on import
159
+ registerConfigReader(claudeCodeConfigReader);
160
+ export { claudeCodeConfigReader };
@@ -9,6 +9,7 @@
9
9
  * - Path patterns like "Read(src/**)"
10
10
  */
11
11
  import type { ConfigBuilder } from "../types.js";
12
+ export { claudeCodeConfigReader } from "./claude-code-reader.js";
12
13
  /** Claude Code ConfigBuilder */
13
14
  declare const claudeCodeConfigBuilder: ConfigBuilder;
14
15
  export { claudeCodeConfigBuilder };
@@ -8,9 +8,12 @@
8
8
  * - Bash patterns like "Bash(git:*)"
9
9
  * - Path patterns like "Read(src/**)"
10
10
  */
11
- import { mkdirSync, writeFileSync } from "node:fs";
11
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
12
12
  import path from "node:path";
13
+ import { atomicWriteFileSync } from "../atomic-write.js";
13
14
  import { registerConfigBuilder } from "../builder.js";
15
+ // Re-export reader
16
+ export { claudeCodeConfigReader } from "./claude-code-reader.js";
14
17
  /** Claude Code tool name mapping */
15
18
  const TOOL_MAP = {
16
19
  read: "Read",
@@ -47,23 +50,45 @@ function translateRule(rule) {
47
50
  }
48
51
  }
49
52
  }
53
+ /**
54
+ * Read existing settings.json, returning empty object if not found.
55
+ * Throws if file exists but contains invalid JSON to prevent data loss.
56
+ */
57
+ function readExistingSettings(settingsPath) {
58
+ if (!existsSync(settingsPath)) {
59
+ return {};
60
+ }
61
+ try {
62
+ const content = readFileSync(settingsPath, "utf8");
63
+ return JSON.parse(content);
64
+ }
65
+ catch (error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ throw new Error(`Failed to parse existing settings at ${settingsPath}: ${message}`);
68
+ }
69
+ }
50
70
  /**
51
71
  * Build Claude Code configuration.
72
+ *
73
+ * Preserves existing settings (model, env, etc.) and only updates permissions.
52
74
  */
53
75
  function build(config, output) {
54
76
  mkdirSync(output, { recursive: true });
55
77
  const warnings = [];
56
78
  const permissions = config.permissions;
57
79
  const settingsPath = path.join(output, "settings.json");
80
+ // Read existing settings to preserve non-permission fields
81
+ const existingSettings = readExistingSettings(settingsPath);
58
82
  // If no permissions specified, deny all
59
83
  if (!permissions) {
60
84
  const settings = {
85
+ ...existingSettings,
61
86
  permissions: {
62
87
  allow: [],
63
88
  deny: [],
64
89
  },
65
90
  };
66
- writeFileSync(settingsPath, JSON.stringify(settings, undefined, 2));
91
+ atomicWriteFileSync(settingsPath, JSON.stringify(settings, undefined, 2));
67
92
  return {
68
93
  ok: true,
69
94
  env: { CLAUDE_CONFIG_DIR: output },
@@ -75,12 +100,13 @@ function build(config, output) {
75
100
  const allowRules = permissions.allow.map((rule) => translateRule(rule));
76
101
  const denyRules = permissions.deny.map((rule) => translateRule(rule));
77
102
  const settings = {
103
+ ...existingSettings,
78
104
  permissions: {
79
105
  allow: allowRules,
80
106
  deny: denyRules,
81
107
  },
82
108
  };
83
- writeFileSync(settingsPath, JSON.stringify(settings, undefined, 2));
109
+ atomicWriteFileSync(settingsPath, JSON.stringify(settings, undefined, 2));
84
110
  return {
85
111
  ok: true,
86
112
  env: { CLAUDE_CONFIG_DIR: output },
@@ -89,7 +115,7 @@ function build(config, output) {
89
115
  }
90
116
  /** Claude Code ConfigBuilder */
91
117
  const claudeCodeConfigBuilder = {
92
- agentId: "claude-code",
118
+ agentId: "claude",
93
119
  capabilities: CAPABILITIES,
94
120
  build,
95
121
  };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Codex ConfigReader.
3
+ *
4
+ * Reads and writes Codex configuration from config.toml and .rules files.
5
+ */
6
+ import type { ConfigReader } from "../types.js";
7
+ /** Codex ConfigReader */
8
+ declare const codexConfigReader: ConfigReader;
9
+ export { codexConfigReader };