axconfig 3.4.2 → 3.5.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.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * OpenCode permission config builder helpers.
3
+ *
4
+ * Extracted from opencode.ts to reduce cyclomatic complexity.
5
+ */
6
+ import type { CanonicalTool, PermissionRule } from "../types.js";
7
+ /** OpenCode permission values */
8
+ export type OpenCodePermission = "allow" | "deny" | "ask";
9
+ /** OpenCode permission config type */
10
+ export type OpenCodePermissionConfig = Record<string, OpenCodePermission | Record<string, OpenCodePermission>>;
11
+ /**
12
+ * Base permissions that override OpenCode's interactive defaults.
13
+ *
14
+ * OpenCode v1.1.1 sets these defaults that differ from other agents:
15
+ * - doom_loop: "ask" (interactive prompt when agent repeats same tool 3x)
16
+ * - external_directory: "ask" (interactive prompt for external paths)
17
+ *
18
+ * For axrun, we need consistent behavior across all agents, so we
19
+ * explicitly override these. The interactive "ask" actions are replaced
20
+ * with "allow" since axrun is headless.
21
+ *
22
+ * Trade-off for doom_loop: Setting "allow" means the agent won't be stopped
23
+ * when it appears stuck in a loop. In CI/CD, this relies on job timeouts to
24
+ * prevent runaway execution. The alternative ("deny") would abort the agent,
25
+ * and ("ask") would hang waiting for input. "allow" is the least disruptive
26
+ * for headless operation.
27
+ *
28
+ * Note: OpenCode's default "read" protection (denying .env files) is
29
+ * overridden dynamically by the permission builder logic, not here.
30
+ */
31
+ export declare const BASE_PERMISSION_OVERRIDES: Record<string, OpenCodePermission>;
32
+ /**
33
+ * Collect path patterns from permission rules, grouped by OpenCode permission name.
34
+ *
35
+ * Note: Only read/write/edit rules can have path patterns per the PathPatternRule type.
36
+ * The TOOL_TO_OPENCODE mapping includes glob/grep for tool-level permissions, but these
37
+ * will never appear here since PathRestrictedTool is limited to read/write/edit.
38
+ */
39
+ export declare function collectPathPatterns(rules: PermissionRule[]): Map<string, string[]>;
40
+ /**
41
+ * Collect tool names from permission rules.
42
+ */
43
+ export declare function collectTools(rules: PermissionRule[]): Set<CanonicalTool>;
44
+ /**
45
+ * Collect bash patterns from permission rules.
46
+ */
47
+ export declare function collectBashPatterns(rules: PermissionRule[]): string[];
48
+ /**
49
+ * Build bash permission config from patterns and tool permissions.
50
+ *
51
+ * IMPORTANT: The wildcard "*" rule must be added FIRST in the config object.
52
+ * OpenCode's `disabled()` function uses `findLast()` to find any rule matching
53
+ * the permission name, then checks if that rule has `pattern === "*"` and
54
+ * `action === "deny"`. If the "*" rule is last, bash gets disabled entirely
55
+ * even when specific patterns are allowed. By adding "*" first, `findLast()`
56
+ * returns a specific pattern rule instead, keeping bash available to the model.
57
+ *
58
+ * At execution time, `evaluate()` correctly handles rule precedence because it
59
+ * uses `findLast()` with BOTH permission AND pattern matching - so specific
60
+ * patterns still take priority over the wildcard when the command matches.
61
+ */
62
+ export declare function buildBashPermission(allowPatterns: string[], denyPatterns: string[], bashAllowed: boolean, bashDenied: boolean): OpenCodePermission | Record<string, OpenCodePermission>;
63
+ /**
64
+ * Build tool permission config with optional path patterns.
65
+ *
66
+ * IMPORTANT: Like buildBashPermission, the wildcard "*" rule must be added
67
+ * FIRST to prevent OpenCode's disabled() from hiding the tool entirely.
68
+ * See buildBashPermission comment for detailed explanation.
69
+ */
70
+ export declare function buildToolPermission(canonicalName: CanonicalTool, opencodeName: string, allowedTools: Set<CanonicalTool>, deniedTools: Set<CanonicalTool>, allowPathPatterns: Map<string, string[]>, denyPathPatterns: Map<string, string[]>): OpenCodePermission | Record<string, OpenCodePermission>;
71
+ /**
72
+ * Build default deny-all permission config for headless security.
73
+ */
74
+ export declare function buildDenyAllConfig(): OpenCodePermissionConfig;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * OpenCode permission config builder helpers.
3
+ *
4
+ * Extracted from opencode.ts to reduce cyclomatic complexity.
5
+ */
6
+ /**
7
+ * Mapping from canonical tool names to OpenCode permission names.
8
+ */
9
+ const TOOL_TO_OPENCODE = {
10
+ read: "read",
11
+ edit: "edit",
12
+ write: "edit", // OpenCode uses single "edit" for both
13
+ glob: "glob",
14
+ grep: "grep",
15
+ web: "webfetch",
16
+ bash: "bash",
17
+ };
18
+ /**
19
+ * Base permissions that override OpenCode's interactive defaults.
20
+ *
21
+ * OpenCode v1.1.1 sets these defaults that differ from other agents:
22
+ * - doom_loop: "ask" (interactive prompt when agent repeats same tool 3x)
23
+ * - external_directory: "ask" (interactive prompt for external paths)
24
+ *
25
+ * For axrun, we need consistent behavior across all agents, so we
26
+ * explicitly override these. The interactive "ask" actions are replaced
27
+ * with "allow" since axrun is headless.
28
+ *
29
+ * Trade-off for doom_loop: Setting "allow" means the agent won't be stopped
30
+ * when it appears stuck in a loop. In CI/CD, this relies on job timeouts to
31
+ * prevent runaway execution. The alternative ("deny") would abort the agent,
32
+ * and ("ask") would hang waiting for input. "allow" is the least disruptive
33
+ * for headless operation.
34
+ *
35
+ * Note: OpenCode's default "read" protection (denying .env files) is
36
+ * overridden dynamically by the permission builder logic, not here.
37
+ */
38
+ export const BASE_PERMISSION_OVERRIDES = {
39
+ doom_loop: "allow",
40
+ external_directory: "allow",
41
+ };
42
+ /**
43
+ * Collect path patterns from permission rules, grouped by OpenCode permission name.
44
+ *
45
+ * Note: Only read/write/edit rules can have path patterns per the PathPatternRule type.
46
+ * The TOOL_TO_OPENCODE mapping includes glob/grep for tool-level permissions, but these
47
+ * will never appear here since PathRestrictedTool is limited to read/write/edit.
48
+ */
49
+ export function collectPathPatterns(rules) {
50
+ const patterns = new Map();
51
+ for (const rule of rules) {
52
+ if (rule.type === "path") {
53
+ const opencodePerm = TOOL_TO_OPENCODE[rule.tool];
54
+ if (opencodePerm === undefined)
55
+ continue;
56
+ const existing = patterns.get(opencodePerm) ?? [];
57
+ existing.push(rule.pattern);
58
+ patterns.set(opencodePerm, existing);
59
+ }
60
+ }
61
+ return patterns;
62
+ }
63
+ /**
64
+ * Collect tool names from permission rules.
65
+ */
66
+ export function collectTools(rules) {
67
+ return new Set(rules
68
+ .filter((r) => r.type === "tool")
69
+ .map((r) => r.name));
70
+ }
71
+ /**
72
+ * Collect bash patterns from permission rules.
73
+ */
74
+ export function collectBashPatterns(rules) {
75
+ return rules
76
+ .filter((r) => r.type === "bash")
77
+ .map((r) => r.pattern);
78
+ }
79
+ /**
80
+ * Build bash permission config from patterns and tool permissions.
81
+ *
82
+ * IMPORTANT: The wildcard "*" rule must be added FIRST in the config object.
83
+ * OpenCode's `disabled()` function uses `findLast()` to find any rule matching
84
+ * the permission name, then checks if that rule has `pattern === "*"` and
85
+ * `action === "deny"`. If the "*" rule is last, bash gets disabled entirely
86
+ * even when specific patterns are allowed. By adding "*" first, `findLast()`
87
+ * returns a specific pattern rule instead, keeping bash available to the model.
88
+ *
89
+ * At execution time, `evaluate()` correctly handles rule precedence because it
90
+ * uses `findLast()` with BOTH permission AND pattern matching - so specific
91
+ * patterns still take priority over the wildcard when the command matches.
92
+ */
93
+ export function buildBashPermission(allowPatterns, denyPatterns, bashAllowed, bashDenied) {
94
+ if (bashDenied && allowPatterns.length === 0) {
95
+ return "deny";
96
+ }
97
+ if (allowPatterns.length > 0 || denyPatterns.length > 0) {
98
+ // Add wildcard FIRST so specific patterns come after in ruleset order.
99
+ // This prevents OpenCode's disabled() from hiding bash from the model.
100
+ const bashConfig = {
101
+ "*": bashAllowed ? "allow" : "deny",
102
+ };
103
+ for (const pattern of allowPatterns) {
104
+ bashConfig[pattern] = "allow";
105
+ }
106
+ for (const pattern of denyPatterns) {
107
+ bashConfig[pattern] = "deny";
108
+ }
109
+ return bashConfig;
110
+ }
111
+ return bashAllowed ? "allow" : "deny";
112
+ }
113
+ /**
114
+ * Build tool permission config with optional path patterns.
115
+ *
116
+ * IMPORTANT: Like buildBashPermission, the wildcard "*" rule must be added
117
+ * FIRST to prevent OpenCode's disabled() from hiding the tool entirely.
118
+ * See buildBashPermission comment for detailed explanation.
119
+ */
120
+ export function buildToolPermission(canonicalName, opencodeName, allowedTools, deniedTools, allowPathPatterns, denyPathPatterns) {
121
+ const hasAllowPatterns = allowPathPatterns.has(opencodeName);
122
+ const hasDenyPatterns = denyPathPatterns.has(opencodeName);
123
+ // Handle write -> edit mapping (both map to same OpenCode permission)
124
+ const isEditOrWrite = canonicalName === "edit" || canonicalName === "write";
125
+ const effectiveAllowed = isEditOrWrite
126
+ ? allowedTools.has("edit") || allowedTools.has("write")
127
+ : allowedTools.has(canonicalName);
128
+ const effectiveDenied = isEditOrWrite
129
+ ? deniedTools.has("edit") || deniedTools.has("write")
130
+ : deniedTools.has(canonicalName);
131
+ if (effectiveDenied && !hasDenyPatterns && !hasAllowPatterns) {
132
+ return "deny";
133
+ }
134
+ if (hasAllowPatterns || hasDenyPatterns) {
135
+ // Add wildcard FIRST so specific patterns come after in ruleset order.
136
+ // This prevents OpenCode's disabled() from hiding the tool from the model.
137
+ const patternConfig = {
138
+ "*": effectiveAllowed ? "allow" : "deny",
139
+ };
140
+ for (const pattern of allowPathPatterns.get(opencodeName) ?? []) {
141
+ patternConfig[pattern] = "allow";
142
+ }
143
+ for (const pattern of denyPathPatterns.get(opencodeName) ?? []) {
144
+ patternConfig[pattern] = "deny";
145
+ }
146
+ return patternConfig;
147
+ }
148
+ return effectiveAllowed ? "allow" : "deny";
149
+ }
150
+ /**
151
+ * Build default deny-all permission config for headless security.
152
+ */
153
+ export function buildDenyAllConfig() {
154
+ return {
155
+ ...BASE_PERMISSION_OVERRIDES,
156
+ read: "deny",
157
+ edit: "deny",
158
+ glob: "deny",
159
+ grep: "deny",
160
+ bash: "deny",
161
+ webfetch: "deny",
162
+ };
163
+ }
@@ -2,6 +2,15 @@
2
2
  * OpenCode ConfigReader.
3
3
  *
4
4
  * Reads and writes OpenCode configuration from opencode.json.
5
+ *
6
+ * Supports OpenCode v1.1.1+ permission format with:
7
+ * - Simple string values: "allow" | "deny" | "ask"
8
+ * - Object syntax with pattern matching for path/command patterns
9
+ *
10
+ * Note: The "ask" permission action is treated as "allow" when reading.
11
+ * This is intentional because axrun is designed for headless operation
12
+ * and doesn't support interactive prompts. Configs containing "ask" will
13
+ * be interpreted as "allow" to maintain consistent non-interactive behavior.
5
14
  */
6
15
  import type { ConfigReader } from "../types.js";
7
16
  /** OpenCode ConfigReader */
@@ -2,6 +2,15 @@
2
2
  * OpenCode ConfigReader.
3
3
  *
4
4
  * Reads and writes OpenCode configuration from opencode.json.
5
+ *
6
+ * Supports OpenCode v1.1.1+ permission format with:
7
+ * - Simple string values: "allow" | "deny" | "ask"
8
+ * - Object syntax with pattern matching for path/command patterns
9
+ *
10
+ * Note: The "ask" permission action is treated as "allow" when reading.
11
+ * This is intentional because axrun is designed for headless operation
12
+ * and doesn't support interactive prompts. Configs containing "ask" will
13
+ * be interpreted as "allow" to maintain consistent non-interactive behavior.
5
14
  */
6
15
  import { mkdirSync } from "node:fs";
7
16
  import path from "node:path";
@@ -9,6 +18,17 @@ import { getAgentPathInfo } from "axshared";
9
18
  import { atomicWriteFileSync } from "../atomic-write.js";
10
19
  import { registerConfigReader } from "../reader.js";
11
20
  import { createJsonConfigOperations, readJsonConfig, } from "../read-write-json-config.js";
21
+ /**
22
+ * Mapping from OpenCode permission names to canonical tool names.
23
+ */
24
+ const OPENCODE_TO_TOOL = {
25
+ read: "read",
26
+ edit: ["edit", "write"], // OpenCode's "edit" maps to both canonical tools
27
+ glob: "glob",
28
+ grep: "grep",
29
+ webfetch: "web",
30
+ bash: "bash",
31
+ };
12
32
  /**
13
33
  * Get the opencode.json path for a config directory.
14
34
  */
@@ -23,6 +43,59 @@ function readConfig(configDirectory) {
23
43
  }
24
44
  // Create shared raw config operations
25
45
  const { readRaw, writeRaw, deleteRaw } = createJsonConfigOperations(getConfigPath, "OpenCode");
46
+ /**
47
+ * Check if an action should be treated as "allow".
48
+ * The "ask" action is treated as "allow" for reading purposes since
49
+ * axrun doesn't support interactive prompts.
50
+ */
51
+ function isAllowAction(action) {
52
+ return action === "allow" || action === "ask";
53
+ }
54
+ /**
55
+ * Parse a permission value (simple or object-based) and add rules to the arrays.
56
+ */
57
+ function parsePermissionValue(opencodeName, value, allowRules, denyRules) {
58
+ const canonicalTools = OPENCODE_TO_TOOL[opencodeName];
59
+ if (!canonicalTools)
60
+ return; // Unknown permission, skip
61
+ const tools = Array.isArray(canonicalTools)
62
+ ? canonicalTools
63
+ : [canonicalTools];
64
+ if (typeof value === "string") {
65
+ // Simple value: "allow", "deny", or "ask"
66
+ const rules = isAllowAction(value) ? allowRules : denyRules;
67
+ for (const tool of tools) {
68
+ rules.push({ type: "tool", name: tool });
69
+ }
70
+ }
71
+ else if (typeof value === "object") {
72
+ // Object with patterns
73
+ for (const [pattern, action] of Object.entries(value)) {
74
+ const rules = isAllowAction(action) ? allowRules : denyRules;
75
+ if (pattern === "*") {
76
+ // Catch-all represents tool-level permission
77
+ for (const tool of tools) {
78
+ rules.push({ type: "tool", name: tool });
79
+ }
80
+ continue;
81
+ }
82
+ if (opencodeName === "bash") {
83
+ // Bash uses command patterns
84
+ rules.push({ type: "bash", pattern });
85
+ }
86
+ else {
87
+ // Only read/write/edit support path patterns in axconfig's type system.
88
+ // OpenCode's glob/grep patterns match against glob patterns or regexes,
89
+ // not file paths, so they're intentionally excluded here.
90
+ for (const tool of tools) {
91
+ if (tool === "read" || tool === "write" || tool === "edit") {
92
+ rules.push({ type: "path", tool, pattern });
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
26
99
  /**
27
100
  * Read permissions from OpenCode config.
28
101
  */
@@ -35,41 +108,13 @@ function readPermissions(configDirectory) {
35
108
  }
36
109
  const allowRules = [];
37
110
  const denyRules = [];
38
- // Parse edit permission (covers edit, write)
39
- if (permission.edit === "allow") {
40
- allowRules.push({ type: "tool", name: "edit" }, { type: "tool", name: "write" });
41
- }
42
- else if (permission.edit === "deny") {
43
- denyRules.push({ type: "tool", name: "edit" }, { type: "tool", name: "write" });
44
- }
45
- // Parse webfetch permission
46
- if (permission.webfetch === "allow") {
47
- allowRules.push({ type: "tool", name: "web" });
48
- }
49
- else if (permission.webfetch === "deny") {
50
- denyRules.push({ type: "tool", name: "web" });
111
+ // Parse all known permissions
112
+ for (const [name, value] of Object.entries(permission)) {
113
+ parsePermissionValue(name, value, allowRules, denyRules);
51
114
  }
52
- // Parse bash permission
53
- if (typeof permission.bash === "string") {
54
- if (permission.bash === "allow") {
55
- allowRules.push({ type: "tool", name: "bash" });
56
- }
57
- else if (permission.bash === "deny") {
58
- denyRules.push({ type: "tool", name: "bash" });
59
- }
60
- }
61
- else if (typeof permission.bash === "object") {
62
- // Pattern-based bash config
63
- for (const [pattern, decision] of Object.entries(permission.bash)) {
64
- if (pattern === "*")
65
- continue; // Skip catch-all
66
- if (decision === "allow") {
67
- allowRules.push({ type: "bash", pattern });
68
- }
69
- else if (decision === "deny") {
70
- denyRules.push({ type: "bash", pattern });
71
- }
72
- }
115
+ // Only return config if there are actual rules
116
+ if (allowRules.length === 0 && denyRules.length === 0) {
117
+ return { ok: true, value: undefined };
73
118
  }
74
119
  const permConfig = {
75
120
  allow: allowRules,
@@ -3,10 +3,13 @@
3
3
  *
4
4
  * Translates AxrunConfig into OpenCode's opencode.json format.
5
5
  *
6
- * OpenCode supports:
7
- * - Tool permissions via permission.{edit,bash,webfetch}
6
+ * OpenCode v1.1.1+ supports:
7
+ * - Tool permissions via permission.{read,edit,glob,grep,bash,webfetch,...}
8
8
  * - Bash patterns via permission.bash object with glob patterns
9
- * - Does NOT support path restrictions
9
+ * - Path patterns via permission.{read,edit} objects with file patterns
10
+ *
11
+ * This builder explicitly overrides OpenCode's default permission rules
12
+ * (like .env denial) to ensure consistent behavior across all agents.
10
13
  */
11
14
  import type { ConfigBuilder } from "../types.js";
12
15
  export { openCodeConfigReader } from "./opencode-reader.js";
@@ -3,139 +3,69 @@
3
3
  *
4
4
  * Translates AxrunConfig into OpenCode's opencode.json format.
5
5
  *
6
- * OpenCode supports:
7
- * - Tool permissions via permission.{edit,bash,webfetch}
6
+ * OpenCode v1.1.1+ supports:
7
+ * - Tool permissions via permission.{read,edit,glob,grep,bash,webfetch,...}
8
8
  * - Bash patterns via permission.bash object with glob patterns
9
- * - Does NOT support path restrictions
9
+ * - Path patterns via permission.{read,edit} objects with file patterns
10
+ *
11
+ * This builder explicitly overrides OpenCode's default permission rules
12
+ * (like .env denial) to ensure consistent behavior across all agents.
10
13
  */
11
14
  import { existsSync, mkdirSync, readFileSync } from "node:fs";
12
15
  import path from "node:path";
13
16
  import { buildAgentRuntimeEnvironment } from "axshared";
14
17
  import { atomicWriteFileSync } from "../atomic-write.js";
15
18
  import { registerConfigBuilder } from "../builder.js";
19
+ import { BASE_PERMISSION_OVERRIDES, buildBashPermission, buildDenyAllConfig, buildToolPermission, collectBashPatterns, collectPathPatterns, collectTools, } from "./opencode-permission-builder.js";
16
20
  // Re-export reader
17
21
  export { openCodeConfigReader } from "./opencode-reader.js";
18
- /** OpenCode capabilities */
22
+ /** OpenCode capabilities (v1.1.1+) */
19
23
  const CAPABILITIES = {
20
24
  toolPermissions: true,
21
25
  bashPatterns: true,
22
- pathRestrictions: false, // OpenCode doesn't support path patterns
26
+ pathRestrictions: true, // OpenCode v1.1.1+ supports path patterns
23
27
  canDenyRead: true,
24
28
  };
29
+ /**
30
+ * Build permission config from parsed permissions.
31
+ */
32
+ function buildPermissionConfig(permissions) {
33
+ const permissionConfig = {
34
+ ...BASE_PERMISSION_OVERRIDES,
35
+ };
36
+ const allowedTools = collectTools(permissions.allow);
37
+ const deniedTools = collectTools(permissions.deny);
38
+ const allowPathPatterns = collectPathPatterns(permissions.allow);
39
+ const denyPathPatterns = collectPathPatterns(permissions.deny);
40
+ // Set permissions for standard tools
41
+ const tools = [
42
+ { canonical: "read", opencode: "read" },
43
+ { canonical: "edit", opencode: "edit" },
44
+ { canonical: "glob", opencode: "glob" },
45
+ { canonical: "grep", opencode: "grep" },
46
+ { canonical: "web", opencode: "webfetch" },
47
+ ];
48
+ for (const { canonical, opencode } of tools) {
49
+ permissionConfig[opencode] = buildToolPermission(canonical, opencode, allowedTools, deniedTools, allowPathPatterns, denyPathPatterns);
50
+ }
51
+ // Build bash permission with command patterns
52
+ const allowBash = collectBashPatterns(permissions.allow);
53
+ const denyBash = collectBashPatterns(permissions.deny);
54
+ permissionConfig.bash = buildBashPermission(allowBash, denyBash, allowedTools.has("bash"), deniedTools.has("bash"));
55
+ return permissionConfig;
56
+ }
25
57
  /**
26
58
  * Build OpenCode configuration.
27
59
  */
28
60
  function build(config, output) {
29
61
  mkdirSync(output, { recursive: true });
30
62
  const warnings = [];
31
- const errors = [];
32
- const permissions = config.permissions;
33
- // Check for unsupported rules
34
- if (permissions) {
35
- // Path restrictions not supported - warn for allow, error for deny
36
- for (const rule of permissions.allow) {
37
- if (rule.type === "path") {
38
- warnings.push({
39
- rule,
40
- reason: "OpenCode does not support path restrictions",
41
- suggestions: [
42
- `Use "${rule.tool}" to allow all ${rule.tool} operations`,
43
- "Remove the path restriction",
44
- ],
45
- });
46
- }
47
- }
48
- for (const rule of permissions.deny) {
49
- if (rule.type === "path") {
50
- errors.push({
51
- rule,
52
- reason: "OpenCode does not support path restrictions",
53
- suggestions: [
54
- `Use "${rule.tool}" to deny all ${rule.tool} operations`,
55
- "Use a different agent that supports path restrictions (e.g., claude)",
56
- ],
57
- });
58
- }
59
- }
60
- }
61
- // If there are errors, abort
62
- if (errors.length > 0) {
63
- return { ok: false, errors };
64
- }
65
63
  // OpenCode config is at {configDir}/opencode.json
66
- // The output directory is used directly as the config directory.
67
64
  const configPath = path.join(output, "opencode.json");
68
65
  // Build permission config
69
- const permissionConfig = {};
70
- if (permissions) {
71
- // Collect tool permissions
72
- const allowedTools = new Set(permissions.allow
73
- .filter((r) => r.type === "tool")
74
- .map((r) => r.name));
75
- const deniedTools = new Set(permissions.deny
76
- .filter((r) => r.type === "tool")
77
- .map((r) => r.name));
78
- // Set edit permission (covers read, write, edit)
79
- if (deniedTools.has("edit") || deniedTools.has("write")) {
80
- permissionConfig.edit = "deny";
81
- }
82
- else if (allowedTools.has("edit") || allowedTools.has("write")) {
83
- permissionConfig.edit = "allow";
84
- }
85
- else {
86
- permissionConfig.edit = "deny"; // Default deny
87
- }
88
- // Set webfetch permission
89
- if (deniedTools.has("web")) {
90
- permissionConfig.webfetch = "deny";
91
- }
92
- else if (allowedTools.has("web")) {
93
- permissionConfig.webfetch = "allow";
94
- }
95
- else {
96
- permissionConfig.webfetch = "deny"; // Default deny
97
- }
98
- // Build bash permission with patterns
99
- const allowBash = permissions.allow
100
- .filter((r) => r.type === "bash")
101
- .map((r) => r.pattern);
102
- const denyBash = permissions.deny
103
- .filter((r) => r.type === "bash")
104
- .map((r) => r.pattern);
105
- const bashAllowed = allowedTools.has("bash");
106
- const bashDenied = deniedTools.has("bash");
107
- if (bashDenied) {
108
- // Deny all bash
109
- permissionConfig.bash = "deny";
110
- }
111
- else if (allowBash.length > 0 || denyBash.length > 0) {
112
- // Use pattern-based config
113
- const bashConfig = {};
114
- // Add allow patterns
115
- for (const pattern of allowBash) {
116
- bashConfig[pattern] = "allow";
117
- }
118
- // Add deny patterns
119
- for (const pattern of denyBash) {
120
- bashConfig[pattern] = "deny";
121
- }
122
- // Default deny for unmatched patterns
123
- bashConfig["*"] = "deny";
124
- permissionConfig.bash = bashConfig;
125
- }
126
- else if (bashAllowed) {
127
- permissionConfig.bash = "allow";
128
- }
129
- else {
130
- permissionConfig.bash = "deny"; // Default deny
131
- }
132
- }
133
- else {
134
- // No permissions = deny all
135
- permissionConfig.edit = "deny";
136
- permissionConfig.bash = "deny";
137
- permissionConfig.webfetch = "deny";
138
- }
66
+ const permissionConfig = config.permissions
67
+ ? buildPermissionConfig(config.permissions)
68
+ : buildDenyAllConfig();
139
69
  // Read existing config to preserve non-permission settings
140
70
  let existingConfig = {};
141
71
  if (existsSync(configPath)) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "axconfig",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "3.4.2",
5
+ "version": "3.5.1",
6
6
  "description": "Unified configuration management for AI coding agents - common API for permissions, settings, and config across Claude Code, Codex, Gemini CLI, and OpenCode",
7
7
  "repository": {
8
8
  "type": "git",
@@ -72,24 +72,18 @@
72
72
  "commander": "^14.0.2"
73
73
  },
74
74
  "devDependencies": {
75
- "@eslint/compat": "^2.0.0",
76
- "@eslint/js": "^9.39.2",
77
75
  "@total-typescript/ts-reset": "^0.6.1",
78
76
  "@types/iarna__toml": "^2.0.5",
79
77
  "@types/node": "^25.0.3",
80
78
  "@vitest/coverage-v8": "^4.0.16",
81
- "@vitest/eslint-plugin": "^1.6.5",
82
79
  "eslint": "^9.39.2",
83
- "eslint-config-prettier": "^10.1.8",
84
- "eslint-plugin-unicorn": "^62.0.0",
80
+ "eslint-config-axpoint": "^1.0.0",
85
81
  "fta-check": "^1.5.1",
86
82
  "fta-cli": "^3.0.0",
87
- "globals": "^17.0.0",
88
83
  "knip": "^5.79.0",
89
84
  "prettier": "3.7.4",
90
85
  "semantic-release": "^25.0.2",
91
86
  "typescript": "^5.9.3",
92
- "typescript-eslint": "^8.51.0",
93
87
  "vitest": "^4.0.16"
94
88
  }
95
89
  }