@geekbeer/minion 2.59.0 → 2.60.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/core/lib/permissions.js +279 -0
- package/core/routes/permissions.js +93 -0
- package/docs/api-reference.md +40 -0
- package/linux/server.js +2 -0
- package/package.json +1 -1
- package/rules/core.md +21 -1
- package/win/server.js +2 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Permission Abstraction Layer
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified interface for reading/writing permission configurations
|
|
5
|
+
* across multiple AI coding CLI tools:
|
|
6
|
+
* - Claude Code: ~/.claude/settings.local.json (JSON)
|
|
7
|
+
* - Gemini CLI: ~/.gemini/settings.json (JSON)
|
|
8
|
+
* - Codex CLI: ~/.codex/config.toml (TOML)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs')
|
|
12
|
+
const path = require('path')
|
|
13
|
+
|
|
14
|
+
// ── CLI Definitions ──────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const CLI_DEFS = {
|
|
17
|
+
'claude-code': {
|
|
18
|
+
label: 'Claude Code',
|
|
19
|
+
configDir: '.claude',
|
|
20
|
+
// Read from settings.json (bundled sync target) + merge settings.local.json (user overrides)
|
|
21
|
+
readFiles: ['settings.json', 'settings.local.json'],
|
|
22
|
+
// Write to settings.local.json to survive server restarts (settings.json is overwritten by syncPermissions)
|
|
23
|
+
writeFile: 'settings.local.json',
|
|
24
|
+
format: 'json',
|
|
25
|
+
},
|
|
26
|
+
'gemini': {
|
|
27
|
+
label: 'Gemini CLI',
|
|
28
|
+
configDir: '.gemini',
|
|
29
|
+
readFiles: ['settings.json'],
|
|
30
|
+
writeFile: 'settings.json',
|
|
31
|
+
format: 'json',
|
|
32
|
+
},
|
|
33
|
+
'codex': {
|
|
34
|
+
label: 'Codex CLI',
|
|
35
|
+
configDir: '.codex',
|
|
36
|
+
readFiles: ['config.toml'],
|
|
37
|
+
writeFile: 'config.toml',
|
|
38
|
+
format: 'toml',
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── TOML Helpers (minimal, for Codex config.toml) ────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse a minimal TOML file to extract permissions.allow and permissions.deny arrays.
|
|
46
|
+
* Only supports the subset needed for Codex CLI config.
|
|
47
|
+
*/
|
|
48
|
+
function parseTomlPermissions(content) {
|
|
49
|
+
const allow = []
|
|
50
|
+
const deny = []
|
|
51
|
+
|
|
52
|
+
let currentSection = ''
|
|
53
|
+
for (const rawLine of content.split('\n')) {
|
|
54
|
+
const line = rawLine.trim()
|
|
55
|
+
if (!line || line.startsWith('#')) continue
|
|
56
|
+
|
|
57
|
+
// Section header: [permissions] or [permissions.default] etc.
|
|
58
|
+
const sectionMatch = line.match(/^\[([^\]]+)\]$/)
|
|
59
|
+
if (sectionMatch) {
|
|
60
|
+
currentSection = sectionMatch[1]
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (currentSection !== 'permissions') continue
|
|
65
|
+
|
|
66
|
+
// Key = value
|
|
67
|
+
const kvMatch = line.match(/^(\w+)\s*=\s*(.+)$/)
|
|
68
|
+
if (!kvMatch) continue
|
|
69
|
+
const [, key, rawValue] = kvMatch
|
|
70
|
+
|
|
71
|
+
if (key === 'allow' || key === 'deny') {
|
|
72
|
+
const arr = parseTomlArray(rawValue)
|
|
73
|
+
if (key === 'allow') allow.push(...arr)
|
|
74
|
+
else deny.push(...arr)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { allow, deny }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse a TOML inline array: ["a", "b", "c"]
|
|
83
|
+
*/
|
|
84
|
+
function parseTomlArray(raw) {
|
|
85
|
+
const trimmed = raw.trim()
|
|
86
|
+
if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) return []
|
|
87
|
+
const inner = trimmed.slice(1, -1)
|
|
88
|
+
const items = []
|
|
89
|
+
// Match quoted strings
|
|
90
|
+
const re = /"([^"]*?)"|'([^']*?)'/g
|
|
91
|
+
let m
|
|
92
|
+
while ((m = re.exec(inner)) !== null) {
|
|
93
|
+
items.push(m[1] ?? m[2])
|
|
94
|
+
}
|
|
95
|
+
return items
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Write permissions into a TOML file, preserving non-permissions content.
|
|
100
|
+
*/
|
|
101
|
+
function writeTomlPermissions(existingContent, { allow, deny }) {
|
|
102
|
+
const lines = existingContent ? existingContent.split('\n') : []
|
|
103
|
+
const outputLines = []
|
|
104
|
+
let inPermissionsSection = false
|
|
105
|
+
let permissionsWritten = false
|
|
106
|
+
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
const trimmed = line.trim()
|
|
109
|
+
const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/)
|
|
110
|
+
|
|
111
|
+
if (sectionMatch) {
|
|
112
|
+
if (sectionMatch[1] === 'permissions') {
|
|
113
|
+
inPermissionsSection = true
|
|
114
|
+
// Write our new permissions section
|
|
115
|
+
outputLines.push('[permissions]')
|
|
116
|
+
outputLines.push(`allow = [${allow.map(s => `"${s}"`).join(', ')}]`)
|
|
117
|
+
outputLines.push(`deny = [${deny.map(s => `"${s}"`).join(', ')}]`)
|
|
118
|
+
permissionsWritten = true
|
|
119
|
+
continue
|
|
120
|
+
} else {
|
|
121
|
+
inPermissionsSection = false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (inPermissionsSection) continue // skip old permission lines
|
|
126
|
+
outputLines.push(line)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If no existing [permissions] section, append one
|
|
130
|
+
if (!permissionsWritten) {
|
|
131
|
+
if (outputLines.length > 0 && outputLines[outputLines.length - 1] !== '') {
|
|
132
|
+
outputLines.push('')
|
|
133
|
+
}
|
|
134
|
+
outputLines.push('[permissions]')
|
|
135
|
+
outputLines.push(`allow = [${allow.map(s => `"${s}"`).join(', ')}]`)
|
|
136
|
+
outputLines.push(`deny = [${deny.map(s => `"${s}"`).join(', ')}]`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return outputLines.join('\n')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── JSON Helpers ─────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
function readJsonPermissions(filePath) {
|
|
145
|
+
try {
|
|
146
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
147
|
+
const data = JSON.parse(content)
|
|
148
|
+
const perms = data.permissions || {}
|
|
149
|
+
return {
|
|
150
|
+
allow: Array.isArray(perms.allow) ? perms.allow : [],
|
|
151
|
+
deny: Array.isArray(perms.deny) ? perms.deny : [],
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
return { allow: [], deny: [] }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function writeJsonPermissions(filePath, { allow, deny }) {
|
|
159
|
+
let data = {}
|
|
160
|
+
try {
|
|
161
|
+
data = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
|
|
162
|
+
} catch {
|
|
163
|
+
// File doesn't exist or is invalid — start fresh
|
|
164
|
+
}
|
|
165
|
+
data.permissions = { allow, deny }
|
|
166
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
167
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Detect which CLIs have config directories present.
|
|
174
|
+
* @param {string} homeDir
|
|
175
|
+
* @returns {string[]} Array of cli_type strings
|
|
176
|
+
*/
|
|
177
|
+
function detectInstalledClis(homeDir) {
|
|
178
|
+
return Object.entries(CLI_DEFS)
|
|
179
|
+
.filter(([, def]) => fs.existsSync(path.join(homeDir, def.configDir)))
|
|
180
|
+
.map(([type]) => type)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Read effective permissions for a specific CLI.
|
|
185
|
+
* For Claude Code, merges settings.json + settings.local.json.
|
|
186
|
+
* @param {string} homeDir
|
|
187
|
+
* @param {string} cliType
|
|
188
|
+
* @returns {{ cli_type: string, label: string, allow: string[], deny: string[], config_path: string } | null}
|
|
189
|
+
*/
|
|
190
|
+
function getPermissions(homeDir, cliType) {
|
|
191
|
+
const def = CLI_DEFS[cliType]
|
|
192
|
+
if (!def) return null
|
|
193
|
+
|
|
194
|
+
const configDir = path.join(homeDir, def.configDir)
|
|
195
|
+
if (!fs.existsSync(configDir)) return null
|
|
196
|
+
|
|
197
|
+
let mergedAllow = []
|
|
198
|
+
let mergedDeny = []
|
|
199
|
+
|
|
200
|
+
for (const file of def.readFiles) {
|
|
201
|
+
const filePath = path.join(configDir, file)
|
|
202
|
+
if (!fs.existsSync(filePath)) continue
|
|
203
|
+
|
|
204
|
+
if (def.format === 'json') {
|
|
205
|
+
const perms = readJsonPermissions(filePath)
|
|
206
|
+
// Later files override earlier ones (settings.local.json overrides settings.json)
|
|
207
|
+
if (perms.allow.length > 0 || perms.deny.length > 0) {
|
|
208
|
+
mergedAllow = perms.allow
|
|
209
|
+
mergedDeny = perms.deny
|
|
210
|
+
}
|
|
211
|
+
} else if (def.format === 'toml') {
|
|
212
|
+
try {
|
|
213
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
214
|
+
const perms = parseTomlPermissions(content)
|
|
215
|
+
if (perms.allow.length > 0 || perms.deny.length > 0) {
|
|
216
|
+
mergedAllow = perms.allow
|
|
217
|
+
mergedDeny = perms.deny
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
// Skip unreadable files
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
cli_type: cliType,
|
|
227
|
+
label: def.label,
|
|
228
|
+
allow: mergedAllow,
|
|
229
|
+
deny: mergedDeny,
|
|
230
|
+
config_path: path.join(configDir, def.writeFile),
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Read permissions for all detected CLIs.
|
|
236
|
+
* @param {string} homeDir
|
|
237
|
+
* @returns {Array<{ cli_type: string, label: string, allow: string[], deny: string[], config_path: string }>}
|
|
238
|
+
*/
|
|
239
|
+
function getAllPermissions(homeDir) {
|
|
240
|
+
const results = []
|
|
241
|
+
for (const cliType of Object.keys(CLI_DEFS)) {
|
|
242
|
+
const perms = getPermissions(homeDir, cliType)
|
|
243
|
+
if (perms) results.push(perms)
|
|
244
|
+
}
|
|
245
|
+
return results
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Update permissions for a specific CLI.
|
|
250
|
+
* @param {string} homeDir
|
|
251
|
+
* @param {string} cliType
|
|
252
|
+
* @param {{ allow: string[], deny: string[] }} permissions
|
|
253
|
+
*/
|
|
254
|
+
function setPermissions(homeDir, cliType, { allow, deny }) {
|
|
255
|
+
const def = CLI_DEFS[cliType]
|
|
256
|
+
if (!def) throw new Error(`Unsupported CLI type: ${cliType}`)
|
|
257
|
+
|
|
258
|
+
const configDir = path.join(homeDir, def.configDir)
|
|
259
|
+
const filePath = path.join(configDir, def.writeFile)
|
|
260
|
+
|
|
261
|
+
fs.mkdirSync(configDir, { recursive: true })
|
|
262
|
+
|
|
263
|
+
if (def.format === 'json') {
|
|
264
|
+
writeJsonPermissions(filePath, { allow, deny })
|
|
265
|
+
} else if (def.format === 'toml') {
|
|
266
|
+
let existing = ''
|
|
267
|
+
try { existing = fs.readFileSync(filePath, 'utf-8') } catch {}
|
|
268
|
+
const newContent = writeTomlPermissions(existing, { allow, deny })
|
|
269
|
+
fs.writeFileSync(filePath, newContent, 'utf-8')
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = {
|
|
274
|
+
detectInstalledClis,
|
|
275
|
+
getPermissions,
|
|
276
|
+
getAllPermissions,
|
|
277
|
+
setPermissions,
|
|
278
|
+
CLI_DEFS,
|
|
279
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission management endpoints
|
|
3
|
+
*
|
|
4
|
+
* Provides read/write access to CLI tool permission configurations.
|
|
5
|
+
* Supports Claude Code, Gemini CLI, and Codex CLI.
|
|
6
|
+
*
|
|
7
|
+
* Endpoints:
|
|
8
|
+
* GET /api/config/permissions - Get permissions for all detected CLIs
|
|
9
|
+
* GET /api/config/permissions/:cli_type - Get permissions for a specific CLI
|
|
10
|
+
* POST /api/config/permissions - Update permissions for a specific CLI
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { verifyToken } = require('../lib/auth')
|
|
14
|
+
const { config } = require('../config')
|
|
15
|
+
const { getAllPermissions, getPermissions, setPermissions, CLI_DEFS } = require('../lib/permissions')
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register permission routes as Fastify plugin
|
|
19
|
+
* @param {import('fastify').FastifyInstance} fastify
|
|
20
|
+
*/
|
|
21
|
+
async function permissionRoutes(fastify) {
|
|
22
|
+
|
|
23
|
+
// GET /api/config/permissions - Get permissions for all detected CLIs
|
|
24
|
+
fastify.get('/api/config/permissions', async (request, reply) => {
|
|
25
|
+
if (!verifyToken(request)) {
|
|
26
|
+
reply.code(401)
|
|
27
|
+
return { success: false, error: 'Unauthorized' }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const permissions = getAllPermissions(config.HOME_DIR)
|
|
31
|
+
return { success: true, permissions }
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// GET /api/config/permissions/:cli_type - Get permissions for a specific CLI
|
|
35
|
+
fastify.get('/api/config/permissions/:cli_type', async (request, reply) => {
|
|
36
|
+
if (!verifyToken(request)) {
|
|
37
|
+
reply.code(401)
|
|
38
|
+
return { success: false, error: 'Unauthorized' }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { cli_type } = request.params
|
|
42
|
+
if (!CLI_DEFS[cli_type]) {
|
|
43
|
+
reply.code(400)
|
|
44
|
+
return { success: false, error: `Unsupported CLI type: ${cli_type}. Supported: ${Object.keys(CLI_DEFS).join(', ')}` }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const perms = getPermissions(config.HOME_DIR, cli_type)
|
|
48
|
+
if (!perms) {
|
|
49
|
+
reply.code(404)
|
|
50
|
+
return { success: false, error: `CLI ${cli_type} is not installed on this minion` }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { success: true, ...perms }
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// POST /api/config/permissions - Update permissions for a specific CLI
|
|
57
|
+
fastify.post('/api/config/permissions', async (request, reply) => {
|
|
58
|
+
if (!verifyToken(request)) {
|
|
59
|
+
reply.code(401)
|
|
60
|
+
return { success: false, error: 'Unauthorized' }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { cli_type, allow, deny } = request.body || {}
|
|
64
|
+
|
|
65
|
+
if (!cli_type) {
|
|
66
|
+
reply.code(400)
|
|
67
|
+
return { success: false, error: 'cli_type is required' }
|
|
68
|
+
}
|
|
69
|
+
if (!CLI_DEFS[cli_type]) {
|
|
70
|
+
reply.code(400)
|
|
71
|
+
return { success: false, error: `Unsupported CLI type: ${cli_type}. Supported: ${Object.keys(CLI_DEFS).join(', ')}` }
|
|
72
|
+
}
|
|
73
|
+
if (!Array.isArray(allow) || !Array.isArray(deny)) {
|
|
74
|
+
reply.code(400)
|
|
75
|
+
return { success: false, error: 'allow and deny must be arrays' }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
setPermissions(config.HOME_DIR, cli_type, { allow, deny })
|
|
80
|
+
|
|
81
|
+
// Read back effective permissions to confirm
|
|
82
|
+
const updated = getPermissions(config.HOME_DIR, cli_type)
|
|
83
|
+
console.log(`[Permissions] Updated ${cli_type}: allow=${allow.length}, deny=${deny.length}`)
|
|
84
|
+
|
|
85
|
+
return { success: true, ...updated }
|
|
86
|
+
} catch (err) {
|
|
87
|
+
reply.code(500)
|
|
88
|
+
return { success: false, error: err.message }
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { permissionRoutes }
|
package/docs/api-reference.md
CHANGED
|
@@ -204,6 +204,46 @@ Changes via the config API take effect immediately (no restart required).
|
|
|
204
204
|
|
|
205
205
|
Allowed keys: `LLM_COMMAND`, `REFLECTION_TIME`
|
|
206
206
|
|
|
207
|
+
### Permissions
|
|
208
|
+
|
|
209
|
+
CLIツール(Claude Code, Gemini CLI, Codex CLI)のパーミッション管理。
|
|
210
|
+
CLIツールは自身の設定ファイルを直接編集できないため、このAPIを経由して更新する。
|
|
211
|
+
|
|
212
|
+
| Method | Endpoint | Description |
|
|
213
|
+
|--------|----------|-------------|
|
|
214
|
+
| GET | `/api/config/permissions` | Get permissions for all detected CLIs |
|
|
215
|
+
| GET | `/api/config/permissions/:cli_type` | Get permissions for a specific CLI |
|
|
216
|
+
| POST | `/api/config/permissions` | Update permissions. Body: `{cli_type, allow, deny}` |
|
|
217
|
+
|
|
218
|
+
Supported `cli_type`: `claude-code`, `gemini`, `codex`
|
|
219
|
+
|
|
220
|
+
**GET response**:
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"success": true,
|
|
224
|
+
"permissions": [
|
|
225
|
+
{
|
|
226
|
+
"cli_type": "claude-code",
|
|
227
|
+
"label": "Claude Code",
|
|
228
|
+
"allow": ["Bash", "Read", "Write", "Edit"],
|
|
229
|
+
"deny": ["Bash(sudo *)"],
|
|
230
|
+
"config_path": "/home/minion/.claude/settings.local.json"
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**POST body**:
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"cli_type": "claude-code",
|
|
240
|
+
"allow": ["Bash", "Read", "Write", "Edit", "WebSearch"],
|
|
241
|
+
"deny": ["Bash(sudo *)", "Bash(rm -rf *)"]
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Note: Claude Code の場合、書き込み先は `settings.local.json`(サーバー再起動時に上書きされない)。
|
|
246
|
+
|
|
207
247
|
### Commands
|
|
208
248
|
|
|
209
249
|
| Method | Endpoint | Description |
|
package/linux/server.js
CHANGED
|
@@ -67,6 +67,7 @@ const { variableRoutes } = require('../core/routes/variables')
|
|
|
67
67
|
const { memoryRoutes } = require('../core/routes/memory')
|
|
68
68
|
const { dailyLogRoutes } = require('../core/routes/daily-logs')
|
|
69
69
|
const { sudoersRoutes } = require('../core/routes/sudoers')
|
|
70
|
+
const { permissionRoutes } = require('../core/routes/permissions')
|
|
70
71
|
|
|
71
72
|
// Linux-specific routes
|
|
72
73
|
const { commandRoutes, getProcessManager, getAllowedCommands } = require('./routes/commands')
|
|
@@ -268,6 +269,7 @@ async function registerAllRoutes(app) {
|
|
|
268
269
|
await app.register(memoryRoutes)
|
|
269
270
|
await app.register(dailyLogRoutes)
|
|
270
271
|
await app.register(sudoersRoutes)
|
|
272
|
+
await app.register(permissionRoutes)
|
|
271
273
|
|
|
272
274
|
// Linux-specific routes
|
|
273
275
|
await app.register(commandRoutes)
|
package/package.json
CHANGED
package/rules/core.md
CHANGED
|
@@ -45,7 +45,27 @@ minion-cli --version # バージョン確認
|
|
|
45
45
|
|
|
46
46
|
`http://localhost:8080` — 認証: `Authorization: Bearer $API_TOKEN`
|
|
47
47
|
|
|
48
|
-
主なカテゴリ: Health, Skills, Workflows, Executions, Terminal, Files, Commands
|
|
48
|
+
主なカテゴリ: Health, Skills, Workflows, Executions, Terminal, Files, Commands, Permissions
|
|
49
|
+
|
|
50
|
+
#### Permission Management
|
|
51
|
+
|
|
52
|
+
CLIツール(Claude Code, Gemini CLI, Codex CLI)のパーミッション(allow/denyリスト)をAPI経由で読み書きできる。
|
|
53
|
+
CLIツールは自身の設定ファイルを直接編集できないため、このAPIを使用すること。
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 全CLIのパーミッション取得
|
|
57
|
+
curl -H "Authorization: Bearer $API_TOKEN" http://localhost:8080/api/config/permissions
|
|
58
|
+
|
|
59
|
+
# 特定CLIのパーミッション取得
|
|
60
|
+
curl -H "Authorization: Bearer $API_TOKEN" http://localhost:8080/api/config/permissions/claude-code
|
|
61
|
+
|
|
62
|
+
# パーミッション更新
|
|
63
|
+
curl -X POST -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
64
|
+
http://localhost:8080/api/config/permissions \
|
|
65
|
+
-d '{"cli_type": "claude-code", "allow": ["Bash", "Read", "Write", "Edit"], "deny": ["Bash(sudo *)"]}'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
対応CLI: `claude-code` (.claude/settings.local.json), `gemini` (.gemini/settings.json), `codex` (.codex/config.toml)
|
|
49
69
|
|
|
50
70
|
### HQ API
|
|
51
71
|
|
package/win/server.js
CHANGED
|
@@ -50,6 +50,7 @@ const { authRoutes } = require('../core/routes/auth')
|
|
|
50
50
|
const { variableRoutes } = require('../core/routes/variables')
|
|
51
51
|
const { memoryRoutes } = require('../core/routes/memory')
|
|
52
52
|
const { dailyLogRoutes } = require('../core/routes/daily-logs')
|
|
53
|
+
const { permissionRoutes } = require('../core/routes/permissions')
|
|
53
54
|
|
|
54
55
|
// Validate configuration
|
|
55
56
|
validate()
|
|
@@ -201,6 +202,7 @@ async function registerRoutes(app) {
|
|
|
201
202
|
await app.register(variableRoutes)
|
|
202
203
|
await app.register(memoryRoutes)
|
|
203
204
|
await app.register(dailyLogRoutes)
|
|
205
|
+
await app.register(permissionRoutes)
|
|
204
206
|
|
|
205
207
|
// Windows-specific routes
|
|
206
208
|
await app.register(commandRoutes)
|