axconfig 3.4.2 → 3.5.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.
- package/dist/agents/opencode-permission-builder.d.ts +59 -0
- package/dist/agents/opencode-permission-builder.js +142 -0
- package/dist/agents/opencode-reader.d.ts +9 -0
- package/dist/agents/opencode-reader.js +79 -34
- package/dist/agents/opencode.d.ts +6 -3
- package/dist/agents/opencode.js +40 -110
- package/package.json +2 -8
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
export declare function buildBashPermission(allowPatterns: string[], denyPatterns: string[], bashAllowed: boolean, bashDenied: boolean): OpenCodePermission | Record<string, OpenCodePermission>;
|
|
52
|
+
/**
|
|
53
|
+
* Build tool permission config with optional path patterns.
|
|
54
|
+
*/
|
|
55
|
+
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>;
|
|
56
|
+
/**
|
|
57
|
+
* Build default deny-all permission config for headless security.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildDenyAllConfig(): OpenCodePermissionConfig;
|
|
@@ -0,0 +1,142 @@
|
|
|
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
|
+
export function buildBashPermission(allowPatterns, denyPatterns, bashAllowed, bashDenied) {
|
|
83
|
+
if (bashDenied && allowPatterns.length === 0) {
|
|
84
|
+
return "deny";
|
|
85
|
+
}
|
|
86
|
+
if (allowPatterns.length > 0 || denyPatterns.length > 0) {
|
|
87
|
+
const bashConfig = {};
|
|
88
|
+
for (const pattern of allowPatterns) {
|
|
89
|
+
bashConfig[pattern] = "allow";
|
|
90
|
+
}
|
|
91
|
+
for (const pattern of denyPatterns) {
|
|
92
|
+
bashConfig[pattern] = "deny";
|
|
93
|
+
}
|
|
94
|
+
bashConfig["*"] = bashAllowed ? "allow" : "deny";
|
|
95
|
+
return bashConfig;
|
|
96
|
+
}
|
|
97
|
+
return bashAllowed ? "allow" : "deny";
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build tool permission config with optional path patterns.
|
|
101
|
+
*/
|
|
102
|
+
export function buildToolPermission(canonicalName, opencodeName, allowedTools, deniedTools, allowPathPatterns, denyPathPatterns) {
|
|
103
|
+
const hasAllowPatterns = allowPathPatterns.has(opencodeName);
|
|
104
|
+
const hasDenyPatterns = denyPathPatterns.has(opencodeName);
|
|
105
|
+
// Handle write -> edit mapping (both map to same OpenCode permission)
|
|
106
|
+
const isEditOrWrite = canonicalName === "edit" || canonicalName === "write";
|
|
107
|
+
const effectiveAllowed = isEditOrWrite
|
|
108
|
+
? allowedTools.has("edit") || allowedTools.has("write")
|
|
109
|
+
: allowedTools.has(canonicalName);
|
|
110
|
+
const effectiveDenied = isEditOrWrite
|
|
111
|
+
? deniedTools.has("edit") || deniedTools.has("write")
|
|
112
|
+
: deniedTools.has(canonicalName);
|
|
113
|
+
if (effectiveDenied && !hasDenyPatterns && !hasAllowPatterns) {
|
|
114
|
+
return "deny";
|
|
115
|
+
}
|
|
116
|
+
if (hasAllowPatterns || hasDenyPatterns) {
|
|
117
|
+
const patternConfig = {};
|
|
118
|
+
for (const pattern of allowPathPatterns.get(opencodeName) ?? []) {
|
|
119
|
+
patternConfig[pattern] = "allow";
|
|
120
|
+
}
|
|
121
|
+
for (const pattern of denyPathPatterns.get(opencodeName) ?? []) {
|
|
122
|
+
patternConfig[pattern] = "deny";
|
|
123
|
+
}
|
|
124
|
+
patternConfig["*"] = effectiveAllowed ? "allow" : "deny";
|
|
125
|
+
return patternConfig;
|
|
126
|
+
}
|
|
127
|
+
return effectiveAllowed ? "allow" : "deny";
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Build default deny-all permission config for headless security.
|
|
131
|
+
*/
|
|
132
|
+
export function buildDenyAllConfig() {
|
|
133
|
+
return {
|
|
134
|
+
...BASE_PERMISSION_OVERRIDES,
|
|
135
|
+
read: "deny",
|
|
136
|
+
edit: "deny",
|
|
137
|
+
glob: "deny",
|
|
138
|
+
grep: "deny",
|
|
139
|
+
bash: "deny",
|
|
140
|
+
webfetch: "deny",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -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
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
//
|
|
53
|
-
if (
|
|
54
|
-
|
|
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
|
-
* -
|
|
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";
|
package/dist/agents/opencode.js
CHANGED
|
@@ -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
|
-
* -
|
|
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:
|
|
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
|
-
|
|
71
|
-
|
|
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.
|
|
5
|
+
"version": "3.5.0",
|
|
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-
|
|
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
|
}
|